Documents v0.9.0 · previews run the real runtime · experimental features are marked exp

Live form preview

A profile form (display name, role, accent, email notifications) and a preview card that follows every change immediately. Native reset restores everything. There is no backend: the Save button is disabled, so neither the button nor Enter submits, and nothing is stored (the test checks storage and cookies).

Live previewreal runtime
View source

How it works

  • bind:lines 8, 9, 10, 11Connects a native form control two-way with the nearest state of that name.
  • :when()line 22Matches elements for which the condition is true, with states resolved from that element.
  • attr:line 29Derives one allowlisted HTML attribute of the matched element, such as disabled or aria-expanded, from a reactive value.
  • text:lines 21, 22, 23, 25…Writes one reactive value into the element’s own DOM text: a real text node, never HTML.

Two-way binding of native controls (text, <select>, radio group, checkbox) to state, and a styled preview, without script.

Left to native HTML and CSS

  • Without Reactive CSS: the radio group, checkbox and select can drive the preview natively with :has(input[value=violet]:checked) and :has(option:checked).
  • What the library adds: the typed name. CSS cannot read a text field’s current value: attr(value) reads the initial attribute, not what the user types.
  • Also native: form reset, keyboard behavior, maxlength, and implicit submission blocked by the disabled submit button.

What would normally need JavaScript

Copying the typed name into the preview (one input listener). Everything else in this example has a native CSS alternative.

Limitations

  • Real DOM text since 0.5.1: the preview is selectable and copyable. It is still not part of the form data; do not present it as saved data.
  • Defaults written three times: the control’s HTML default (value, selected, checked), the state() initial value, and the preview’s initial text. They must match:
    • bind: starts from the state;
    • form reset uses the HTML defaults;
    • the preview text is shown before the script runs.
  • One rule per accent. Each accent color still needs its own rule; since 0.5.2 it is a native attribute selector on data-accent instead of :when().
  • Data attributes are a copy, not storage (0.5.3). The card’s data-* attributes follow the states and are restored when the card stops matching. Nothing is saved: reload restores the HTML defaults, and nothing goes to localStorage or cookies (tested).
  • A WebKit quirk. Playwright’s WebKit build logs a CSP message for its own internal <select> stylesheet under style-src 'self'. It happens with a bare <select> and no script, so it is not caused by the library.

Accessibility

  • Every control is native, with a visible label; the radio group has a fieldset and legend.
  • Keyboard: typing, arrow keys in the radio group, Space for the checkbox, select with the keyboard (tested).
  • The preview is a labelled region. It has no live announcements: screen-reader users already hear their own input.

Implementation

2 files · 30 lines
style.cssreactive
/* ---- state ------------------------------------------------------------------------------   Initial values repeat the HTML defaults: bind: starts from the state, and form reset returns   every control and its state to the HTML default. */.profile { --name: state("Ada"); --role: state("Developer"); --notify: state(true); }/* 0.5.2: the accent is also mirrored to the form's data-accent attribute, so plain attribute   selectors style the preview (and other scripts or tests can read it). */.profile { --accent: state("teal", bind(data-accent)); }.profile input[name=display_name] { bind: --name; }.profile select[name=role] { bind: --role; }.profile input[name=accent] { bind: --accent; }.profile input[name=notifications] { bind: --notify; }/* ---- preview: real DOM text (text:, 0.5.1) ----------------------------------------------------   The HTML holds the initial preview, shown before the script runs. */.preview { --c: #0f766e; }.profile[data-accent="violet"] .preview { --c: #6d28d9; }.profile[data-accent="amber"] .preview { --c: #b45309; }.card { background: #fff; border-radius: 14px; padding: 20px; border-top: 6px solid var(--c); box-shadow: 0 1px 3px rgb(0 0 0 / 0.1); }.card p { margin: 4px 0; }.avatar { width: 56px; height: 56px; border-radius: 50%; background: var(--c); }.card .name { font-size: 22px; font-weight: 700; text: var(--name); }.profile:when(--name == "") .card .name { color: #888; text: "(no name yet)"; }.card .role { text: var(--role); }.card .accent { color: var(--c); }.card .accent-value { text: var(--accent); }.card .notify-value { text: when(--notify, "on", "off"); }/* attr: (0.5.3): the preview card carries the form's values as data attributes, derived from the   form's states (the card owns none). An empty name gives data-name="" (present, empty). */.card { attr: data-name var(--name); attr: data-role var(--role); attr: data-notify when(--notify, "on", "off"); }.card[data-notify="off"] .notify-value { color: #888; }
state()Function · hover any red tokenMakes each matching element the owner of an independent state with an initial value.Reference →

Source: the library's examples/live-form-preview/. style.css from its first section comment; the layout above it runs but is not shown. Written in the 0.8 syntax (on:, bind:, text:, attr:), which 0.9.0 still runs; see Migrating from 0.8 for the 0.9 forms.

Related

Accessible tabs

Two independent tab sets. Each tab reports a real aria-selected, inactive panels are hidden, one tab is disabled in the HTML, and the "Billing" tab is disabled until a checkbox in the first panel is checked.

0.8 syntax:nth-var()prevent()key()focus:

Accessible toggle button

A pressed-state toggle button and a disclosure button. Their aria-pressed and aria-expanded attributes are real DOM attributes, kept in sync with Reactive CSS state.

0.8 syntaxstate()bind()on:click()

Settings panel

A notifications section whose channel options are disabled while notifications are off, a theme and text-size preview, an "Advanced settings" disclosure, and a native reset. Nothing is saved.

0.8 syntaxbind:attr:toggle()set()