@zoxon/mater
v1.0.0
Published
Foundational TemplateResult runtime for JSX and tagged-template UI.
Downloads
16
Readme
Mater
Mater is a foundational runtime for declarative UI. It turns JSX and tagged-template descriptions into concrete output such as HTML strings or DOM fragments — no framework, no virtual DOM.
Why Mater?
In Latin, Mater means "mother"—the source from which something is born. It also shares its root with materia ("matter"), the substance from which things are made.
The library embodies both ideas: it is the foundational runtime from which user interfaces take shape, transforming declarative descriptions into concrete output.
Public API
Install:
pnpm add @zoxon/materimport { html, raw, render } from '@zoxon/mater'
import { html, render } from '@zoxon/mater/browser'html\...`→TemplateResult`
Tagged template literal. Interpolated values are auto-escaped. Returns a TemplateResult — compose freely, render at the boundary.
const view = html`<button class=${'btn'} disabled=${true}>${label}</button>`render(result) → string
Converts a TemplateResult to a plain HTML string. Use at system boundaries (DOM insertion, serialization).
const parser = new DOMParser()
const doc = parser.parseFromString(render(view), 'text/html')
container.replaceChildren(...Array.from(doc.body.childNodes))raw(htmlString) → RawHtml
Marks a string as pre-rendered, trusted HTML. Bypasses escaping. Use only for developer-supplied content — never for user input.
html`<div>${raw(trustedHtml)}</div>`TemplateResult projections
| Property | Returns | Description |
|---|---|---|
| .html | SafeHtml | Memoized rendered string (internal) |
| .dom | DocumentFragment | Fresh DOM fragment (browser only) |
| .element | Element | Single root element (browser only, throws for multi-root) |
Interpolation rules
| Value type | Behaviour |
|---|---|
| string, number | Escaped and inserted as text |
| TemplateResult | Composed inline — no escaping |
| RawHtml | Inserted verbatim — no escaping |
| null, undefined, false | Omitted |
| true | Boolean attribute (in attribute position) |
| Array / Iterable | Each item rendered recursively |
| Object with attrs= | Spread as attribute map |
Attribute sugar
// scalar
html`<input type=${'text'} name=${'email'} />`
// boolean
html`<input disabled=${true} required=${false} />`
// → <input disabled />
// class array (clsx)
html`<button class=${['btn', active && 'btn--active']}>OK</button>`
// attrs spread
html`<input attrs=${{ disabled: true, value: 'A&B' }} />`
// → <input disabled value="A&B" />attrs= wins over duplicate scalar attributes on the same element.
Component pattern
Components return TemplateResult. Use render() only at the string boundary.
// component
export function Button({ label, disabled }: ButtonProps): TemplateResult {
return html`<button class="button" disabled=${disabled}>${label}</button>`
}
// composition — no raw(), no .html.value
export function Card({ title, body }: CardProps): TemplateResult {
return html`<div class="card">
<h2>${title}</h2>
${Button({ label: 'OK', disabled: false })}
<div>${raw(body)}</div>
</div>`
}
// boundary — convert once, use the string
const htmlString = render(Card({ title: 'Hi', body: trustedHtml }))Browser projections
Import from the /browser entrypoint to access DOM projections:
import { html } from '@zoxon/mater/browser'
const view = html`<button>${'Save'}</button>`
view.dom // DocumentFragment
view.element // Element (single root)view.dom always returns a fresh clone. view.element throws if the template has more than one root element.
Package layout
src/
index.ts universal entrypoint (string rendering)
browser.ts browser entrypoint (+ DOM projections)
core/
escape.ts HTML character escaping
class-value.ts clsx normalization for class=
raw-html.ts RawHtml class + raw()
safe-html.ts SafeHtml wrapper
template-result.ts TemplateResult with projections
types.ts shared value types
render/
string.ts authoritative string renderer
dom.ts browser DOM projection helpers
finalize.ts render() public finalizer