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

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.

Live previewreal runtime
View source

How it works

  • bind:lines 4, 5, 6Connects a native form control two-way with the nearest state of that name.
  • attr:lines 11, 16, 17, 22Derives one allowlisted HTML attribute of the matched element, such as disabled or aria-expanded, from a reactive value.
  • toggle()line 16Flips a boolean state. In an :on() rule it is the mutation --open: toggle().
  • set()line 18Sets a state to a value. In an :on() rule it is a mutation of the state on the left, --x: set(5), with typed expressions.

One form-owned state disabling a whole group of controls natively (disabled on a <fieldset>), a disclosure with real aria-expanded and hidden, and a preview styled through a derived data-theme attribute.

Left to native HTML and CSS

  • <fieldset disabled>: disables every control inside, removes them from the tab order and from form data, and matches :disabled.
  • Everything else native: hidden, [data-theme] selectors, the range input, the select, and form reset.

What would normally need JavaScript

Toggling disabled on the fieldset when the master checkbox changes, aria-expanded and hidden for the disclosure, and copying the theme to the preview. Without script, CSS can only look disabled (:has(:not(:checked)) + opacity): the controls would still be focusable and submitted.

Limitations

  • Defaults written twice: state(true) and checked, state(light) and selected, state(16) and value="16".
  • Reset and the disclosure. --more is not form data, so form reset does not restore it. The reset button’s own on: closes the disclosure. A disclosure outside the form would need its own reset.
  • Progressive enhancement. Without the script, the advanced region is visible and the channels stay enabled (nothing is hidden or disabled by markup).
  • Nothing persists. Reload restores the HTML defaults; there is no persist().

Accessibility

  • Native controls, fieldsets and legends, visible focus.
  • The disabled fieldset is announced as unavailable, and its controls are skipped by Tab (tested).
  • The disclosure button has real aria-expanded and aria-controls, and the region is hidden while closed (tested). This is the disclosure pattern, not a full settings-dialog pattern.
  • attr: writes attributes; it does not make a component accessible by itself. Not tested with screen readers.

Implementation

2 files · 26 lines
style.cssreactive
/* ---- state: owned by the form. bind: keeps controls and states in sync, including form reset.   Initial values repeat the HTML defaults (checked, selected, value). */.settings { --notify: state(true); --theme: state(light); --size: state(16); --more: state(false); }.settings .master input { bind: --notify; }.settings select[name=theme] { bind: --theme; }.settings input[name=size] { bind: --size; }/* ---- derived attributes (attr:, 0.5.3) ---------------------------------------------------------   The channels fieldset is natively disabled while notifications are off: every control inside   is disabled, skipped by Tab and left out of form data, without touching each control. */.settings .channels { attr: disabled when(--notify, false, true); }.settings .channels:disabled { color: #888; }/* A disclosure: the button reports aria-expanded, the region is hidden while closed. The button   is not a form control, so the form's reset button closes it explicitly. */.settings .more { on: click(toggle(--more)); attr: aria-expanded var(--more); }.settings .advanced { attr: hidden when(--more, false, true); padding: 0 4px; }.settings button[type=reset] { on: click(set(--more, false)); }/* The preview carries the theme as data-theme (plain attribute selectors style it) and the size   as a number in CSS. text: writes the size label. */.settings .preview { attr: data-theme var(--theme); font-size: calc(var(--size) * 1px); border-radius: 10px; padding: 12px 16px; border: 1px solid #ddd; }.settings .preview p { margin: 0; }.settings .preview[data-theme="dark"] { background: #1f2937; color: #f9fafb; }.settings .preview[data-theme="contrast"] { background: #000; color: #ff0; border-color: #ff0; }.settings .size { text: var(--size); font-variant-numeric: tabular-nums; }
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/settings-panel/. 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

Notification center

A collapsible list of three sample notifications. Each can be marked read (a toggle with aria-pressed) or dismissed. "Undo dismiss" restores the last dismissed one, "Dismiss all" clears the list, and the unread count updates as real text.

0.8 syntaxtoggle()set():when()attr:

Form validation

A sign-up form using native constraint validation: required fields, type="email", minlength. A company field appears and becomes required only for business accounts. Submit is disabled until the terms are accepted, and error messages appear after the first submit attempt. Nothing is sent.

0.8 syntaxbind:attr:set():when()

Keyboard playground

A focusable pad moved with the arrow keys (Escape centers it), focus tracking with focusin/focusout, nested regions that show how one key press reaches several handlers, and a Reset button that is disabled while there is nothing to reset.

0.8 syntaxfocusin()focusout()prevent()attr: