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:
.text(expr)— sets the text content of the element.value(expr)— sets the microdata value, using the appropriate attribute for the tag (hreffor<a>,srcfor<img>,datetimefor<time>,contentfor<meta>, and text content for everything else).attr(name, expr)— sets a named attribute to the result ofexpr
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:
${[itemprop="name"]}.first().text()— the product name as text${[itemprop="price"]}.first().value()— the price via microdata rules (thevalueattribute of a<data>element, or text content of a<span>)${[itemprop="url"]}.first().value()— the URL via microdata rules (thehrefof an<a>, thecontentof a<meta>, etc.)
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: