Constructing HTML

The guides so far have focused on reading from the document store — selecting elements, extracting values, filtering and aggregating lists. This guide covers the other direction: building new HTML elements from within an expression.

The new keyword

The new keyword followed by a tag name and a pair of braces creates an element. Inside the braces you describe the element's content.

new p { text: "Hello, World" }
new h1 { text: "My Title" }
new div {}

The first example produces <p>Hello, World</p>. The second produces <h1>My Title</h1>. The third produces <div></div> — an empty element with no children.

The special text: property inside the braces sets the element's text content. The quotes are required for a literal string.

ID and classes

The construction syntax borrows CSS shorthand notation to describe the element's shape. An #id sets the element's ID. A .class adds a class. You can combine them freely:

new p.intro#main { text: "Welcome" }

produces:

<p class="intro" id="main">Welcome</p>

Multiple classes:

new div.card.featured {}

produces <div class="card featured"></div>.

Attributes

Attributes are specified in square brackets, just like CSS attribute selectors. A name-value pair produces a valued attribute. A bare name produces a boolean attribute — one that is either present or absent with no value.

new input[type="text"][name="email"][required] {}

produces <input type="text" name="email" required>.

Namespaced attributes

Namespaced attributes use a prefix|name syntax:

new div[p|transient][p|ttl="3600"] {}

produces <div p:transient p:ttl="3600"></div>.

Dynamic attribute values

When an attribute value is a Sessel expression rather than a quoted literal, the expression is evaluated at runtime:

new a[href=${[itemprop="url"]}.first().value()] { text: "Read more" }

The href is set to the result of the expression ${[itemprop="url"]}.first().value() — whatever URL is in the document's microdata. Quoted values are always treated as literals; unquoted values are always treated as expressions.

Children

For elements with multiple children, list them inside the braces separated by commas:

new article.post {
  new h1 { text: "My Post" },
  new p.body { text: "Content here." },
  new footer { new small { text: "Posted today" } }
}

This produces:

<article class="post">
  <h1>My Post</h1>
  <p class="body">Content here.</p>
  <footer><small>Posted today</small></footer>
</article>

When an element has only text content, use text:. When it has child elements, list them directly. You can mix both — the children are appended in order.

Null skipping and conditional children

Null children are silently skipped — they produce no output. This enables conditional children using the ternary operator or if expressions:

new ul {
  showDrafts ? new li { text: "Drafts" } : null,
  new li { text: "Published" }
}

When showDrafts is false, the first child evaluates to null and is skipped, producing <ul><li>Published</li></ul>.

if without an else returns null when the condition is falsy, so it works the same way:

new ul {
  if (showDrafts) { new li { text: "Drafts" } },
  new li { text: "Published" }
}

For multi-branch conditional children, if is clearer than nested ternaries:

new div {
  if (role == "admin") {
    new span.badge { text: "Admin" }
  } else if (role == "editor") {
    new span.badge { text: "Editor" }
  }
}

List flattening

List children are flattened one level, so map() can produce sibling children naturally:

new ul {
  ${[itemprop="category"]}.map(el => new li { text: el.text() })
}

Each element returned by map() becomes a direct child of the <ul>. For deeper nesting, use the flatten() method explicitly.

Void elements

HTML void elements — br, hr, img, input, meta, link, and others — are serialized without a closing tag:

new br {}
new img[src="/photo.jpg"][alt="Photo"] {}

produces <br> and <img src="/photo.jpg" alt="Photo"> respectively. The braces are still required even though these elements cannot have children.

Mixing queried and constructed elements

Queried elements — those returned from the document store via a selector — can be used as children alongside constructed elements. They are embedded verbatim, preserving their tag, attributes, and all descendant content.

new div.wrapper { ${h1}.first() }

This wraps whatever <h1> is in the document inside a new <div class="wrapper">.

You can mix queried and constructed children freely:

new div {
  ${h1}.first(),
  new p { text: "Added content" }
}

The first child is the queried <h1> as it exists in the store; the second child is a newly constructed <p>.

Method chaining on constructed elements

Setter methods let you set content after construction. They follow an arity convention — the same method name reads with no arguments and writes with arguments. Each setter returns the modified element, so calls chain naturally.

new img {}.value("/images/banner.jpg").attr("alt", "Banner")

The three setter methods:

These are most useful when you need to set a value that depends on a complex expression:

new h1 {}.text(${[itemprop="name"]}.first().value() ?? "Untitled")

A complete example

Building a product card from queried microdata:

new div.card {
  new h2 { text: ${[itemprop="name"]}.first().text() },
  new span.price { text: ${[itemprop="price"]}.first().value() },
  new a[href=${[itemprop="url"]}.first().value()] { text: "View product" }
}

The three children each pull a value from the document's microdata:

The result is a complete card element whose content is drawn entirely from the document store.

Next steps

This guide covered element construction — the new keyword, tag shorthand, attributes, children in braces, text:, null skipping, list flattening, and setter methods.

The next guide introduces named values and how to compose large expressions from smaller, reusable parts:

For detailed reference material: