@llui/dom

Runtime for the LLui web framework -- The Elm Architecture with compile-time bitmask optimization.

No virtual DOM. view() runs once at mount, building real DOM nodes with reactive bindings that update surgically when state changes.

Install

pnpm add @llui/dom

Quick Start

import { component, mountApp, div, button } from '@llui/dom'

type State = { count: number }
type Msg = { type: 'inc' } | { type: 'dec' }

const Counter = component<State, Msg, never>({
  name: 'Counter',
  init: () => [{ count: 0 }, []],
  update: (state, msg) => {
    switch (msg.type) {
      case 'inc':
        return [{ ...state, count: state.count + 1 }, []]
      case 'dec':
        return [{ ...state, count: state.count - 1 }, []]
    }
  },
  view: ({ send, text }) => [
    button({ onClick: () => send({ type: 'dec' }) }, [text('-')]),
    text((s) => String(s.count)),
    button({ onClick: () => send({ type: 'inc' }) }, [text('+')]),
  ],
})

mountApp(document.getElementById('app')!, Counter)

View<S, M> -- the helper bundle

view receives a single View<S, M> bag. Destructure what you need -- send plus any state-bound helpers. TypeScript infers S from the component definition, so no per-call generics:

view: ({ send, text, show, each, branch, memo }) => [
  text(s => s.label),                    // s is State -- inferred
  ...show({ when: s => s.visible, render: () => [...] }),
  ...each({ items: s => s.items, key: i => i.id, render: ({ item }) => [...] }),
]

Element helpers (div, button, span, etc.) stay as imports -- they're stateless and don't need the S binding.

API

Core

Export Purpose
component(def) Create a component definition
mountApp(el, def) Mount a component to a DOM element
hydrateApp(el, def) Hydrate server-rendered HTML
mountAtAnchor(anchor, def) Mount a component relative to a comment anchor
hydrateAtAnchor(anchor, def) Hydrate server-rendered HTML relative to a comment anchor
flush() Synchronously flush all pending updates
createView(send) Create a full View bundle (for tests/dynamic use)

View Primitives

Primitive Purpose
text(accessor) Reactive text node
show({ when, render }) Conditional rendering
branch({ on, cases, default? }) Multi-case switching with optional default
scope({ on, render }) Keyed subtree rebuild on key change
each({ items, key, render }) Keyed list rendering
portal({ target, render }) Render into a different DOM location
memo(accessor) Memoized derived value
sample(selector) One-shot imperative state read (no binding)
selector(field) O(1) one-of-N selection binding
onMount(callback) Lifecycle hook (runs once after mount)
errorBoundary(opts) Catch render errors
foreign({ create, update }) Integrate non-LLui libraries
slice(h, selector) View over a sub-slice of state

Composition

Export Purpose
combine({ slice: reducer, ... }, top?) Route messages of shape ${slice}/${action} to slice reducers
mergeHandlers(...handlers) Combine multiple update handlers
sliceHandler({ get, set, narrow, sub }) Route messages to a state slice
subApp({ reason, def, data?, onHandle? }) Embed an isolated TEA loop (escape hatch — requires non-empty reason string)

Context

Export Purpose
createContext(defaultValue) Create a context
provide(ctx, accessor, children) Provide value to subtree
useContext(ctx) Read context value

Element Helpers

50+ typed element constructors: div, span, button, input, a, h1-h6, table, tr, td, ul, li, img, form, label, select, textarea, canvas, video, nav, header, footer, section, article, p, pre, code, and more.

SSR

Export Purpose
renderToString(def) Render component to HTML string
initSsrDom() Initialize jsdom for SSR (from @llui/dom/ssr)

Sub-path Exports

import { installDevTools } from '@llui/dom/devtools' // dev-only, tree-shaken
import { initSsrDom } from '@llui/dom/ssr' // server-only
import { replaceComponent } from '@llui/dom/hmr' // HMR support

Performance

Competitive with Solid and Svelte on js-framework-benchmark. 5.8 KB gzipped.

Functions

component()

function component<S, M, E = never, D = void>(
  def: ComponentDef<S, M, E, D>,
): ComponentDef<S, M, E, D>

createView()

Create a View<S, M> bundle for a component's view callback. Delegates straight to the underlying primitives — zero per-call overhead.

function createView<S, M>(send: Send<M>): View<S, M>

mountApp()

function mountApp<S, M, E>(
  container: HTMLElement,
  def: ComponentDef<S, M, E>,
  data?: unknown,
  _options?: MountOptions,
): AppHandle

hydrateApp()

function hydrateApp<S, M, E>(
  container: HTMLElement,
  def: ComponentDef<S, M, E>,
  serverState: S,
): AppHandle

flush()

function flush(): void

renderToString()

Render a component to an HTML string for SSR. Evaluates view() against the initial state (or provided data), serializes the DOM to HTML, and adds data-llui-hydrate markers on nodes with reactive bindings. Call initSsrDom() once before using this on the server.

function renderToString<S, M, E>(def: ComponentDef<S, M, E>, initialState?: S): string

mergeHandlers()

Compose multiple update handlers into one. Each handler returns [newState, effects] if it handled the message, or null to pass through. The first handler that returns non-null wins.

function mergeHandlers<S, M, E>(
  ...handlers: Array<(state: S, msg: M) => [S, E[]] | null>
): (state: S, msg: M) => [S, E[]]

createContext()

Create a typed context key. Pass a default value to make consumers without a provider resolve to it; omit to make unprovided consumption throw.

const ThemeContext = createContext<'light' | 'dark'>('light')
function createContext<T>(defaultValue?: T, name?: string): Context<T>

provide()

Provide a reactive value for ctx to every descendant rendered inside children. The accessor (s: S) => T is evaluated lazily at binding read time, so providers can thread state slices down without prop drilling.

view: ({ send }) => [
  provide(
    ThemeContext,
    (s: State) => s.theme,
    () => [header(send), main(send)],
  ),
]

Nested providers shadow outer ones within their subtree. The outer value is restored after children() returns, so sibling subtrees aren't affected.

function provide<S, T>(ctx: Context<T>, accessor: (s: S) => T, children: () => Node[]): Node[]

useContext()

Read a context accessor within a view or view-function. Walks the scope chain from the current render point to find the nearest provider. Returns an (s: S) => T accessor that can be passed to bindings (text/class/etc.).

export function themedCard(): Node[] {
  const theme = useContext(ThemeContext)
  return div({ class: (s) => `card theme-${theme(s)}` }, [...])
}
function useContext<S, T>(ctx: Context<T>): (s: S) => T

sliceHandler()

Lens-style adapter that lifts a sub-component's update into a handler that operates on a parent's full state and message type. Pairs with mergeHandlers to compose sub-components into a parent component's reducer. Full form — explicit lens for custom state paths or message shapes:

sliceHandler({
  get: (s) => s.confirm,
  set: (s, v) => ({ ...s, confirm: v }),
  narrow: (m) => (m.type === 'confirm' ? m.msg : null),
  sub: dialog.update,
})

Shorthand — when the state key matches the message's type field and the parent message wraps the child message in a msg property:

// Equivalent to the full form above.
// Derives get/set/narrow from the key string.
sliceHandler('confirm', dialog.update)

The shorthand assumes the convention:

  • state[key] holds the sub-state
  • Messages matching this slice have shape { type: key; msg: SubMsg }
function sliceHandler<S, M, E, SubS, SubM>(
  keyOrOpts:
    | string
    | {
        get: (state: S) => SubS
        set: (state: S, slice: SubS) => S
        narrow: (msg: M) => SubM | null
        sub: (slice: SubS, msg: SubM) => [SubS, E[]]
      },
  sub?: (slice: SubS, msg: SubM) => [SubS, E[]],
): (state: S, msg: M) => [S, E[]] | null

composeModules()

Create a merged handler from a map of component modules. Each module's update is wired via sliceHandler(key, module.update) convention: state[key] holds the sub-state, messages match { type: key; msg: SubMsg }. Returns a handler compatible with mergeHandlers.

function composeModules<S, M, E>(
  modules: Record<string, { update: (state: never, msg: never) => [unknown, unknown[]] }>,
): (state: S, msg: M) => [S, E[]] | null

text()

function text<S>(accessor: ((s: S) => string) | (() => string) | string, mask?: number): Text

branch()

function branch<S, M = unknown>(opts: BranchOptions<S, M>): Node[]

scope()

Rebuild a subtree when a derived key changes. Sugar over branch({ on, cases: {}, default: render }).

function scope<S, M = unknown>(opts: ScopeOptions<S, M>): Node[]

interface ScopeOptions<S, M> {
  on: (s: S) => string
  render: (h: View<S, M>) => Node[]
  enter?: (nodes: Node[]) => void | Promise<void>
  leave?: (nodes: Node[]) => void | Promise<void>
}

Each time on(state) returns a new value (compared with Object.is), the current arm's Lifetime is disposed and render(h) runs against a fresh one. Combine with sample() for a whole-state snapshot read at rebuild time — see the cookbook recipe "Rebuild a subtree when a derived value changes".

sample()

One-shot imperative read of current state inside a render context. No binding is created.

function sample<S, R>(selector: (s: S) => R): R

sample() is a construction-time primitive. It throws if called outside a render context, and it also throws if called from inside an accessor — each.key, each.items, branch.on, show.when, scope.on, child.props, foreign.props, or a binding accessor like text(s => …). Accessors run during the update phase (no render context) and must be pure functions of their parameter; sample() reads outside the parameter, which the compiler's mask analysis can't see. The lint rule llui/no-sample-in-accessor catches the same antipattern at edit time. To depend on outer state inside an accessor, lift the dep into the parameter (e.g. for each.key, bake it into the items map: items: (s) => s.rows.map(it => ({ it, rev: s.rev }))). Also available as h.sample(...) on the View bag.

each()

function each<S, T, M = unknown>(opts: EachOptions<S, T, M>): Node[]

virtualEach()

Virtualized list — renders only the rows visible in the scroll viewport. Use for lists with 1k+ items where a regular each() would be too slow. Current limitations:

  • Fixed row height (itemHeight) — dynamic heights not supported
  • No transitions / animations
  • No cross-container reuse (items outside view are fully disposed)
view: ({ text }) => [
  ...virtualEach({
    items: (s) => s.rows,
    key: (r) => r.id,
    itemHeight: 40,
    containerHeight: 600,
    render: ({ item }) => [div({ class: 'row' }, [text(item.label)])],
  }),
]
function virtualEach<S, T, M = unknown>(opts: VirtualEachOptions<S, T, M>): Node[]

show()

function show<S, M = unknown>(opts: ShowOptions<S, M>): Node[]

slice()

Build a View<Sub, M> that composes a selector into every state-bound accessor. Used to write view-functions over a sub-slice of parent state:

import { slice } from '@llui/dom'
view: (h) => {
  const formView = slice(h, (s) => s.form)
  return [...formView.show({ when: f => f.valid, render: (h) => [...] })]
}

Kept as a standalone function rather than a method on the View bundle so apps that don't use it don't pay for its bundle cost — tree-shaken when unused.

function slice<Root, Sub, M>(
  h: View<Root, M> | { send: Send<M> },
  lift: (r: Root) => Sub,
): View<Sub, M>

portal()

function portal(opts: PortalOptions): Node[]

foreign()

function foreign<S, M, T extends Record<string, unknown>, Instance>(
  opts: ForeignOptions<S, M, T, Instance>,
): Node[]

combine()

Compose slice reducers by routing messages of shape ${slice}/${action} to the matching slice. The slice reducer receives the message with msg.type rewritten to the un-prefixed form. Preserves top-level state reference equality when slices return unchanged, which keeps the path-keyed dirty walker precise.

function combine<S, M extends { type: string }, E>(
  slices: { [SliceKey: string]: (state: any, msg: any) => [any, E[]] },
  top?: (state: S, msg: M) => [S, E[]],
): (state: S, msg: M) => [S, E[]]

subApp()

Embed an independent TEA loop with its own state lifetime. The framework gives you the embedded app's AppHandle through onHandle for imperative send / getState. The reason field is required and non-empty; it surfaces in the rendered DOM as data-llui-sub-app-reason and is enforced at lint time by llui/subapp-requires-reason. Reserved for cases where the embedded code's state lifetime must be distinct from the host's — third-party bundled apps, demo embeds, separately-versioned libraries. Not a tool for decomposing complex views; use view functions for that.

function subApp<S, M, E, D = void>(opts: {
  reason: string
  def: ComponentDef<S, M, E, D>
  data?: D
  onHandle?: (handle: AppHandle) => void
}): Node[]

lazy()

Load a component asynchronously. Renders fallback immediately, then swaps in the loaded component when the loader's Promise resolves. If the loader rejects, renders error (or nothing if no error handler is provided).

view: ({ text }) => [
  ...lazy({
    loader: () => import('./Chart').then((m) => m.default),
    fallback: ({ text }) => [div([text('Loading chart...')])],
    error: (err, { text }) => [div([text(`Failed: ${err.message}`)])],
  }),
]

The loaded component's S, M, E types are internal — lazy() only needs the D (init data) type to match. LazyDef<D> is a type-erased shape that any ComponentDef<S, M, E, D> satisfies structurally, avoiding the View<S, M> invariance trap that would otherwise require user-side casts. If the parent scope is disposed before the loader resolves, the load is cancelled — the loaded component is never mounted.

function lazy<S, M, D = undefined>(opts: LazyOptions<S, M, D>): Node[]

memo()

function memo<S, T>(accessor: (s: S) => T, mask?: number): (s: S) => T

selector()

Optimized "one-of-N" reactive binding — O(1) updates instead of O(n). Uses a generation counter instead of per-row disposers:

  • bind() tags entries with the current generation
  • Individual row removal bumps the generation for that entry (set gen = -1)
  • Bulk clear bumps the generation and clears the registry via registerOnClear
  • updateSelector() skips stale entries (gen !== current) and compacts lazily Zero per-row closure allocations. Zero Set.delete calls on disposal.
function selector<S, V>(field: (s: S) => V): SelectorInstance<V>

onMount()

function onMount(callback: (el: Element) => (() => void) | void): void

errorBoundary()

function errorBoundary(opts: {
  render: () => Node[]
  fallback: (error: Error) => Node[]
  onError?: (error: Error) => void
}): Node[]

applyField()

Apply a field update to state immutably. Returns a new state object with the specified field updated. Usage in update(): case 'setField': return [applyField(state, msg.field, msg.value), []]

function applyField<S extends Record<string, unknown>, K extends keyof S>(
  state: S,
  field: K,
  value: S[K],
): S

elSplit()

function elSplit(
  tag: string,
  staticFn: ((el: HTMLElement) => void) | null,
  events: Array<[string, EventListener]> | null,
  bindings: Array<[number, BindingKind, string, (state: never) => unknown]> | null,
  children: Node[] | null,
): HTMLElement

elTemplate()

Clone a cached HTML template and apply a patch function. The patch function receives the cloned root element and a bind helper that registers reactive bindings in the current render context. Per-item bindings (accessor.length === 0) are registered as direct updaters on the scope — called by each() when item changes, bypassing the Phase 2 binding scan entirely. Fast path for each() rows — 1 cloneNode instead of N createElement.

function elTemplate(html: string, patch: (root: Element, bind: TemplateBind) => void): Element

_handleMsg()

Run a handler for a single message: call update(), reconcile blocks with the given method, run Phase 2. Used by compiler-generated **handlers to avoid duplicating boilerplate per message type. @param method 0=reconcile, 1=reconcileItems, 2=reconcileClear, 3=reconcileRemove, -1=skip blocks @public — used by compiler-generated **handlers

function _handleMsg(
  inst: ComponentInstance,
  msg: unknown,
  dirty: number,
  method: number,
): [unknown, unknown[]]

_runPhase2()

Phase 2: compact dead bindings + update live bindings. Shared between genericUpdate and compiler-generated **update. @public — used by compiler-generated **update functions

function _runPhase2(
  state: unknown,
  dirty: number,
  bindings: Binding[],
  bindingsBeforePhase1: number,
  componentName?: string,
): void

Types

Send

export type Send<M> = (msg: M) => void

Props

Maps a value shape to a reactive-props shape: every field becomes an accessor (s: S) => V. Use for Level-1 view function signatures.

type ToolbarData = { tools: Tool[]; theme: 'light' | 'dark' }
export function toolbar<S>(props: Props<ToolbarData, S>, send: Send<Msg>) {
  return [div({ class: props.theme }, [each({ items: props.tools, ... })])]
}
// Caller — TypeScript enforces per-field accessors; passing a raw value errors:
toolbar({ tools: (s: State) => s.tools, theme: (s) => s.theme }, send)
export type Props<T, S> = {
  [K in keyof T]: (s: S) => T[K]
}

BindingKind

export type BindingKind = 'text' | 'prop' | 'attr' | 'class' | 'style'

ItemAccessor

Per-item accessor. Two access forms:

  • item.field — shorthand, returns accessor for item.current[field]
  • item(t => t.expr) — computed expressions In both cases the returned value is a () => V accessor. Invoke it (item.field()) to read the current value imperatively.
export type ItemAccessor<T> = {
  <R>(selector: (t: T) => R): () => R
} & {
  [K in keyof T]-?: () => T[K]
}

ModuleState

Extract the state type from a component module's update function. Works with both property and method syntax.

export type ModuleState<T> = T extends {
  update: (state: infer S, msg: infer _M) => [infer _S2, infer _E]
}
  ? S
  : never

ModuleMsg

Extract the message type from a component module's update function.

export type ModuleMsg<T> = T extends {
  update: (state: infer _S, msg: infer M) => [infer _S2, infer _E]
}
  ? M
  : never

ModulesState

Given a record of component modules, derive the combined child state. Each key maps to its module's state type.

const children = { dialog, sort: sortable } as const
type CS = ModulesState<typeof children>
// → { dialog: DialogState; sort: SortableState }
export type ModulesState<T extends Record<string, unknown>> = {
  [K in keyof T]: ModuleState<T[K]>
}

ModulesMsg

Given a record of component modules, derive the combined child message union. Each module's messages are wrapped in { type: key; msg: SubMsg }.

const children = { dialog, sort: sortable } as const
type CM = ModulesMsg<typeof children>
// → { type: 'dialog'; msg: DialogMsg } | { type: 'sort'; msg: SortableMsg }
export type ModulesMsg<T extends Record<string, unknown>> = {
  [K in keyof T]: { type: K; msg: ModuleMsg<T[K]> }
}[keyof T]

FieldMsg

Type utility for form field messages. Generates a discriminated union where each field gets its own typed variant, avoiding the need to define one message type per field. Usage: type Fields = { name: string; email: string; age: number } type Msg = FieldMsg | { type: 'submit' } // Produces: { type: 'setField'; field: 'name'; value: string } // | { type: 'setField'; field: 'email'; value: string } // | { type: 'setField'; field: 'age'; value: number } // | { type: 'submit' }

export type FieldMsg<Fields extends Record<string, unknown>> = {
  [K in keyof Fields]: { type: 'setField'; field: K; value: Fields[K] }
}[keyof Fields]

Interfaces

ComponentDef

export interface ComponentDef<S, M, E = never, D = void> {
  name: string
  init: (data: D) => [S, E[]]
  update: (state: S, msg: M) => [S, E[]]
  view: (h: View<S, M>) => Node[]
  onEffect?: (ctx: { effect: E; send: Send<M>; signal: AbortSignal }) => void

  /** @internal Compiler-injected — path-keyed reactivity prefix table.
   *  Replaces the legacy `__dirty` bitmask. Each entry is a stable
   *  closure `(s: S) => unknown` hoisted at module scope; the array
   *  position IS the bit position (0..30 → low word, 31..61 → high). */
  __prefixes?: ReadonlyArray<(state: S) => unknown>
  /** @internal Compiler-injected */
  __renderToString?: (state: S) => string
  /** @internal Compiler-injected */
  __msgSchema?: object
  /** @internal Compiler-injected — maps top-level state field → dirty-mask bit(s) */
  __maskLegend?: Record<string, number>
  /** @internal Compiler-injected — source-file location of the component() call */
  __componentMeta?: { file: string; line: number }
  /** @internal Compiler-injected — shape of the State type (for introspection) */
  __stateSchema?: object
  /** @internal Compiler-injected — Effect union schema (for introspection) */
  __effectSchema?: object
  /** @internal Compiler-injected — replaces generic Phase 1 + Phase 2 loop.
   *  Trailing `dirtyHi` carries the high-word mask for 32..61-prefix
   *  components; defaults to 0 for backward compat with stale 5-param bundles. */
  __update?: (
    state: S,
    dirty: number,
    bindings: Binding[],
    blocks: StructuralBlock[],
    bindingsBeforePhase1: number,
    dirtyHi?: number,
  ) => void
  /** @internal Compiler-injected — per-message-type specialized handlers.
   *  Bypass the entire processMessages pipeline for single-message updates. */
  __handlers?: Record<string, (inst: object, msg: unknown) => [S, E[]]>
}

User-authored __dirty is no longer accepted — the compiler emits __prefixes automatically from accessor analysis, and the runtime throws at createComponentInstance if any user code sets def.__dirty. See Migration from v0.0.x for the migration recipe.

LazyDef

Type-erased component definition for use at module boundaries where the loaded component's S, M, E are internal and invisible to the caller. Only D (init data) survives because the caller provides it. ComponentDef<S, M, E, D> is structurally assignable to LazyDef<D> for any S, M, E — view: (h: unknown) => Node[] accepts any View via contravariance, and all other fields widen to unknown return types. Used by lazy() as the loader's return type.

export interface LazyDef<D = void> {
  name: string
  // Method syntax — TypeScript checks methods bivariantly, so
  // ComponentDef<S, M, E, D>'s concrete (state: S, msg: M) => ...
  // assigns here even though S/M ≠ unknown. Property syntax would
  // be contravariant and reject the assignment.
  init(data: D): [unknown, unknown[]]
  update(state: unknown, msg: unknown): [unknown, unknown[]]
  view(h: unknown): Node[]
  onEffect?: unknown
  __prefixes?: unknown
  __renderToString?: unknown
  __msgSchema?: unknown
  __maskLegend?: unknown
  __componentMeta?: unknown
  __stateSchema?: unknown
  __effectSchema?: unknown
  __update?: unknown
  __handlers?: unknown
}

AppHandle

export interface AppHandle {
  dispose(): void
  flush(): void
}

Scope

export interface Scope {
  id: number
  parent: Scope | null
  children: Scope[]
  disposers: Array<() => void>
  bindings: Binding[]
  /** Per-item updaters — called directly by each() when item changes, bypassing Phase 2 */
  itemUpdaters: Array<() => void>
}

Binding

export interface Binding {
  mask: number
  accessor: (state: unknown) => unknown
  lastValue: unknown
  kind: BindingKind
  node: Node
  key?: string
  ownerScope: Scope
  perItem: boolean
  dead: boolean
}

TransitionOptions

export interface TransitionOptions {
  enter?: (nodes: Node[]) => void | Promise<void>
  leave?: (nodes: Node[]) => void | Promise<void>
  onTransition?: (ctx: { entering: Node[]; leaving: Node[]; parent: Node }) => void | Promise<void>
}

BranchOptions

export interface BranchOptions<S, M = unknown> extends TransitionOptions {
  on: (s: S) => string | number | boolean
  cases: Record<string | number, (h: View<S, M>) => Node[]>
}

ShowOptions

export interface ShowOptions<S, M = unknown> extends TransitionOptions {
  when: (s: S) => boolean
  render: (h: View<S, M>) => Node[]
  fallback?: (h: View<S, M>) => Node[]
}

EachOptions

export interface EachOptions<S, T, M = unknown> extends TransitionOptions {
  items: (s: S) => T[]
  key: (item: T) => string | number
  render: (opts: {
    send: Send<M>
    item: ItemAccessor<T>
    /**
     * Plain (non-Proxy) accessor factory. Compiler-output path; avoid in user code
     * (use `item.field` / `item(fn)` — more ergonomic and bypasses Proxy when compiled).
     */
    acc: <R>(selector: (t: T) => R) => () => R
    index: () => number
    /** @internal Compiler-injected — entry reference for row factory */
    entry?: Record<string, unknown>
  }) => Node[]
}

PortalOptions

export interface PortalOptions {
  target: HTMLElement | string
  render: () => Node[]
}

ForeignOptions

export interface ForeignOptions<S, M, T extends Record<string, unknown>, Instance> {
  mount: (ctx: { container: HTMLElement; send: Send<M> }) => Instance
  props: (s: S) => T
  sync:
    | ((ctx: { instance: Instance; props: T; prev: T | undefined }) => void)
    | {
        [K in keyof T]?: (ctx: { instance: Instance; value: T[K]; prev: T[K] | undefined }) => void
      }
  destroy: (instance: Instance) => void
  container?: { tag?: string; attrs?: Record<string, string> }
}

View

Typed view helpers bound to a component's State / Msg. The sole argument to view, so every state-bound primitive infers State from the component definition — no per-call show<State>(...) annotation.

view: ({ send, show, text }) => [
  ...show({ when: s => s.count > 0, render: () => [...] }),
  text(s => String(s.count)),
]

Tip: to view-function over a sub-slice of parent state, import slice as a standalone helper:

import { slice } from '@llui/dom'
const form = slice(h, (s) => s.form) // returns View<FormState, Msg>

The Vite plugin's mask-injection pass recognizes all three call forms equivalently: h.text(...) (member expression), text(...) (destructured alias), and text(...) (bare import from @llui/dom). No per-binding gating is lost when calling through h.

export interface View<S, M> {
  send: Send<M>
  show(opts: ShowOptions<S, M>): Node[]
  branch(opts: BranchOptions<S, M>): Node[]
  each<T>(opts: EachOptions<S, T, M>): Node[]
  text(accessor: ((s: S) => string) | string, mask?: number): Text
  memo<T>(accessor: (s: S) => T): (s: S) => T
  selector<V>(field: (s: S) => V): SelectorInstance<V>
  ctx<T>(c: Context<T>): (s: S) => T
}

MountOptions

export interface MountOptions {
  devTools?: boolean
}

LluiDebugAPI

export interface LluiDebugAPI {
  getState(): unknown
  send(msg: unknown): void
  flush(): void
  getMessageHistory(opts?: { since?: number; limit?: number }): MessageRecord[]
  evalUpdate(msg: unknown): { state: unknown; effects: unknown[] }
  exportTrace(): {
    lluiTrace: 1
    component: string
    generatedBy: string
    timestamp: string
    entries: Array<{ msg: unknown; expectedState: unknown; expectedEffects: unknown[] }>
  }
  clearLog(): void
  validateMessage(msg: unknown): ValidationError[] | null
  getBindings(): BindingDebugInfo[]
  whyDidUpdate(bindingIndex: number): UpdateExplanation
  searchState(query: string): unknown
  /** Returns the compiled Msg schema (discriminant + variant field types). */
  getMessageSchema(): MessageSchemaInfo | null
  /** Returns the bit→field map injected by the compiler. Lets tools decode dirty-mask values. */
  getMaskLegend(): Record<string, number> | null
  /** Given a dirty mask, return the list of top-level fields it represents. */
  decodeMask(mask: number): string[]
  /** Component name + source location (file/line from compiler-injected metadata). */
  getComponentInfo(): ComponentInfo
  /** Returns the compiled State type shape (from TypeScript `type State = { … }`). */
  getStateSchema(): object | null
  /** Returns the compiled Effect schema (from TypeScript `type Effect = { … }` union). */
  getEffectSchema(): object | null
  /** Deep-clone the current state. Pair with restoreState() to checkpoint before risky operations. */
  snapshotState(): unknown
  /** Overwrite the current state with a previously-captured snapshot. Triggers a full re-render. */
  restoreState(snap: unknown): void
  /** Find all bindings whose target node matches or is a child of the selector. */
  getBindingsFor(selector: string): BindingLocation[]
}

Context

export interface Context<T> {
  readonly _id: symbol
  readonly _default: T | undefined
  readonly _name: string | undefined
}

VirtualEachOptions

export interface VirtualEachOptions<S, T, M = unknown> {
  items: (s: S) => T[]
  key: (item: T) => string | number
  /** Fixed pixel height per item. Required — dynamic heights are not supported yet. */
  itemHeight: number
  /** Scrollable container height in pixels. */
  containerHeight: number
  /** Extra rows to render above/below the viewport for smooth scrolling. Default: 3. */
  overscan?: number
  /** Optional class for the scroll container. */
  class?: string
  render: (opts: {
    send: Send<M>
    item: ItemAccessor<T>
    acc: <R>(selector: (t: T) => R) => () => R
    index: () => number
  }) => Node[]
}

LazyOptions

export interface LazyOptions<S, M, D> {
  /** Async loader — typically `() => import('./MyComponent').then(m => m.default)`. */
  loader: () => Promise<LazyDef<D>>
  /** Nodes to render while loading. */
  fallback: (h: View<S, M>) => Node[]
  /** Nodes to render if the loader rejects. */
  error?: (err: Error, h: View<S, M>) => Node[]
  /** Props passed as init data to the loaded component. Evaluated once at resolution. */
  data?: (s: S) => D
}

Constants

a

const a

abbr

const abbr

article

const article

aside

const aside

b

const b

blockquote

const blockquote

br

const br

button

const button

canvas

const canvas

code

const code

dd

const dd

details

const details

dialog

const dialog

div

const div

dl

const dl

dt

const dt

em

const em

fieldset

const fieldset

figcaption

const figcaption

figure

const figure

footer

const footer

form

const form

h1

const h1

h2

const h2

h3

const h3

h4

const h4

h5

const h5

h6

const h6

header

const header

hr

const hr

i

const i

iframe

const iframe

img

const img

input

const input

label

const label

legend

const legend

li

const li

main

const main

mark

const mark

nav

const nav

ol

const ol

optgroup

const optgroup

option

const option

output

const output

p

const p

pre

const pre

progress

const progress

section

const section

select

const select

small

const small

span

const span

strong

const strong

sub

const sub

summary

const summary

sup

const sup

table

const table

tbody

const tbody

td

const td

textarea

const textarea

tfoot

const tfoot

th

const th

thead

const thead

time

const time

tr

const tr

ul

const ul

video

const video

svg

const svg

g

const g

defs

const defs

symbol

const symbol

use

const use

circle

const circle

ellipse

const ellipse

line

const line

path

const path

polygon

const polygon

polyline

const polyline

rect

const rect

tspan

const tspan

textPath

const textPath

clipPath

const clipPath

linearGradient

const linearGradient

radialGradient

const radialGradient

stop

const stop

mask

const mask

pattern

const pattern

marker

const marker

filter

const filter

feBlend

const feBlend

feColorMatrix

const feColorMatrix

feComponentTransfer

const feComponentTransfer

feComposite

const feComposite

feConvolveMatrix

const feConvolveMatrix

feDiffuseLighting

const feDiffuseLighting

feDisplacementMap

const feDisplacementMap

feDropShadow

const feDropShadow

feFlood

const feFlood

feGaussianBlur

const feGaussianBlur

feImage

const feImage

feMerge

const feMerge

feMergeNode

const feMergeNode

feMorphology

const feMorphology

feOffset

const feOffset

feSpecularLighting

const feSpecularLighting

feTile

const feTile

feTurbulence

const feTurbulence

fePointLight

const fePointLight

feSpotLight

const feSpotLight

feDistantLight

const feDistantLight

feFuncR

const feFuncR

feFuncG

const feFuncG

feFuncB

const feFuncB

feFuncA

const feFuncA

image

const image

foreignObject

const foreignObject

animate

const animate

animateMotion

const animateMotion

animateTransform

const animateTransform

set

const set

mpath

const mpath

desc

const desc

title

const title

metadata

const metadata

math

const math

mi

const mi

mn

const mn

mo

const mo

ms

const ms

mtext

const mtext

mrow

const mrow

mfrac

const mfrac

msqrt

const msqrt

mroot

const mroot

msup

const msup

msub

const msub

msubsup

const msubsup

munder

const munder

mover

const mover

munderover

const munderover

mmultiscripts

const mmultiscripts

mprescripts

const mprescripts

mnone

const mnone

mtable

const mtable

mtr

const mtr

mtd

const mtd

mspace

const mspace

mpadded

const mpadded

mphantom

const mphantom

menclose

const menclose

merror

const merror

maction

const maction

semantics

const semantics

annotation

const annotation

annotationXml

const annotationXml