DOM

html``#

tagged template literals, CSP-safe, parsed once per template by a real HTML tokenizer. Prefixes everyone knows:

Syntax Meaning
@click=${fn} addEventListener (also onclick=${fn})
.value=${sig} element property<input .value>, <select .value>, <my-chart .points=${obj}>
?disabled=${sig} boolean attribute (toggleAttribute)
bind:value=${sig} two-way binding (bind:checked for checkboxes)
:title=${sig} reactive attribute
title="a ${x} ${y}" several values in one attribute
<input ${ref}> ref() or (el) => … inside a tag
<canvas ${attach(el => …)}> init + cleanup + reactivity for third-party widgets, runs after mount
@submit.prevent, @keydown.enter, @click.outside, @keydown.esc.window, @input.debounce.300, @scroll.passive event modifiers (also prevent(fn) / stop(fn) / self(fn) helpers)
class=${{ active, done: () => … }}, class=${['btn', size]} clsx-style classes with a diff: static and third-party classes are never touched, works on SVG
style=${{ '--w': () => w.value + 'px', color }} per-property styles; custom properties go through setProperty (cssVars(el, { x }) for signal → CSS variable)
${when(users, { loading, error, data })} three states of a resource in one place
${() => cond ? html : null} reactive children: a function may return text, a node, a template or an array — each run gets its own scope

Bare value/checked/selected on form fields are treated as properties, so <input value=${sig}> keeps working after the user has typed.

show()#

conditional rendering with scope-per-branch (no leaks); { keep: true } hides instead of destroying (like v-show)

Components return their template#

mount(el, ({ html, slot }) => html\…`)replaces the element's content;slot()/slot('[slot=footer]')` hand server-rendered children to the template

Server HTML#

swap(el, response, { mode: 'morph' }) inserts server-rendered fragments the right way: islands in the replaced subtree are disposed, new ones hydrated, data-cloak removed, focus and caret restored; morph patches in place (id-aware, live islands keep their DOM and remount only when their data-* props change); a full page in the response is reduced to the target's selector

boost()#

MPA navigation without reload: fetch the next page, morph <main>, View Transitions, prefetch on hover; islands outside the root survive; data-no-boost opts out; a router() keeps its own routes

tpl('#card')#

a server <template> with data-slot becomes a render function for list()/show(): one markup for the first render and the client (no HTML parsing, CSP-safe)

adopt(el)html``#

bind a template to already server-rendered DOM with zero mutations (text, attributes, events, refs); dev warning when the server partial drifts from the template

Island props#

register(name, setup, { types: { count: Number, on: Boolean, tags: JSON } }); data-* stay strings unless typed (IDs never lose precision), <script type="application/json"> inside an island (Django json_script) or data-aegis-props="#id" lands in data.props; jsonScript('#config')

Lazy islands#

register('chart', { load: () => import('/js/islands/chart.js') }) or <div data-aegis="chart" data-aegis-src="/js/islands/chart.js" data-aegis-load="visible">: island code is fetched only when needed (400px before a visible island enters the viewport); data-aegis-prefetch="/api/…" warms its data too

Routing 2.0#

nested routes with layout and a live outlet, async handler/loader awaited inside intercept() (native loading indicator, scroll after data, races cancelled via signal), guard/redirect as route data, r.search('page', { parse: Number }) two-way URL signals that do not recreate the route scope, lazy routes via load: () => import(…) with hover/visible preload, aria-current + activeClass on server-rendered links, transition: true View Transitions with back/forward direction; hash links, forms, downloads, data-aegis-reload and unknown paths go to the server

Forms 2.0#

wireForm validates through the Constraint Validation API (noValidate, setCustomValidity, browser-localised messages, :user-invalid), accepts a Standard Schema (zod/valibot/arktype) next to per-field rules, async rules with validating[key], debounce and stale-response cancellation, nested names (items[0][qty], address.city) in values/setErrors, setInitial(resource.data) / commit() / dirtyFields / changes / guardUnload(), server submit with FormData (422 → field errors, 303 → navigation); form(defaults, { rules, schema }) for virtual forms

hydrate(root, { watch: true })#

islands inserted by htmx/Turbo/jQuery come alive, removed ones are destroyed; register() auto-hydrates the document; aegis:hydrate / aegis:hydrated / aegis:destroy events; eager islands mount in ~8 ms slices between tasks

Event handlers are batched#

on(), delegate(), @click: five signal writes in a handler, one flush

Effects run under their owner#

whatever created the effect owns everything it creates on every re-run (getOwner() / runWithOwner() for code after await)

list()#

keyed reconciliation with prefix/suffix trim, DocumentFragment batch insert; a row is exactly the nodes you return (<tr> inside <tbody>, <option> inside <select>, <dt>+<dd>) — no wrapper elements; a row re-renders when the object behind its key is replaced, index is a lazy signal, duplicate/missing keys never drop rows

bind()#

two-way binding (checkbox, radio, select, number)

text(), attr(), cls(), style()#

single-property reactive bindings

clsMap(), styleMap()#

multi-property reactive bindings

portal()#

render to a different DOM location

ref()#

DOM element references