registry:ui · Data Entry
combobox — the searchable select
Searchable select with custom-value input: real-time label filtering, allowCustom for free-text values, popover panel with roving aria-activedescendant (focus stays in input), 'No results' + 'Use xxx' affordance, blur-commit, family label/error contract. name= is intercepted off the native input and submits the committed VALUE (option value or custom string — never the display label) through the faceless form-field bridge. Floating-surface law: ::after shadow layer (semi-transparent black/white) + @starting-style entry; variant='solid' | 'acrylic' | 'auto' (acrylic falls back to solid under prefers-reduced-transparency; the terminal bezel keeps its identity through the jx-surface fill props).
$npx jixoai-ui add combobox
https://ui.jixoai.com/r/combobox.json
overview
Overview
The searchable sibling of select: the trigger IS an <input>, so the popup is a conversation — the filter text is input state and only an explicit commit touches value.
The commit paths are the contract: Enter commits the
highlighted row (or, with nothing highlighted, resolves the raw text — exact option, else
the custom value when allowCustom, else nothing); blur resolves the same way; Escape reverts
to the committed display; Tab keeps the typed text through the
same resolution. The “Use “xxx”” row appears only when nothing matched — while matches
exist they are the answer.
The ARIA contract is the hard one and this family takes the APG shape with zero focus
management: role="combobox" sits on the input, aria-expanded mirrors the panel, aria-controls and aria-owns name the
top-layer listbox (a DOM sibling promoted by popover="auto"), and the
roving highlight rides aria-activedescendant — focus never enters
the panel, so there is no focus restitution to get wrong (measured: after ArrowDown and
commit, document.activeElement is still the input). In multiple mode the
listbox is aria-multiselectable, the trigger wears chips with per-chip
“remove X” buttons, and the committed array crosses to FormData as repeated same-name
entries through the faceless form-field bridge — the name prop is
intercepted off the native input, which never submits its display text.
The paint is the terminal bezel the select family owns, and the theme axis splits it: the
typed atoms (shell border and ground, chips, the active/selected row grounds) freeze at
the :root pole while the raw machines (well shadow, row hover, panel ink, focus ring)
flip under a scoped .dark — one listbox, two time-bases, detailed in the
axis table below. Kinship: select is the composite sibling sharing the
panel law; tags-input shares the chip law; cascader is
the honest-path cousin — the ruled chain-of-<select>s route for
dependent choices that need no typing at all.
usage
Usage
The filter text is input state, never committed state — value only changes through an explicit commit.
const backends: ComboboxOption[] = [
{ value: 'node-pty', label: 'node-pty', description: 'conpty / forkpty addon' },
{ value: 'bun-terminal', label: 'Bun.Terminal', description: 'linux/macos 1.3.13+, windows 1.3.14' },
{ value: '@sigma/pty-ffi', label: '@sigma/pty-ffi', description: 'deno FFI over rust portable-pty' },
];
<!-- type to filter (label, case-insensitive); ↑/↓ + Enter commits; Escape
reverts; Tab keeps. No match + allowCustom → the “Use “xxx”” row -->
<Combobox label="backend" bind:value={backend} options={backends} />
<!-- strict: allowCustom={false} reverts stray text on blur -->
<Combobox label="runtime" allowCustom={false} options={backends} />combobox
The searchable select: the trigger IS the input — typing filters the panel live, ↑/↓ + Enter commits, Escape reverts, and allowCustom offers the “Use “xxx”” row.
- node-pty conpty / forkpty — the battle-tested addon
- Bun.Terminal linux/macos since 1.3.13, windows 1.3.14
- @sigma/pty-ffi deno FFI over rust portable-pty
- termless VT emulator — not a pty host
<Combobox
label="backend"
allowCustom
options={backendOptions}
bind:value
/>combobox
The high-form select — the popup becomes a conversation
The trigger IS an input, typing filters the panel live (label contains, case-insensitive), ↑/↓ ride a roving aria-activedescendant highlight, Enter commits it, Escape reverts, Tab keeps — and when nothing matches, the allowCustom row offers “Use “xxx”” in the primary hue while strict fields revert stray text on blur. The panel is the same popover=auto terminal bezel as Select — light dismiss, Escape, and top layer are the browser's; focus never leaves the text field.
Focus one: the text selects itself and the panel opens on the full list with the
committed row highlighted (the 2px --primary edge);
typing filters live and auto-highlights the first match. Try wasi in the first field — no match, so the
“Use “wasi”” row appears in primary; press Enter to commit it as a custom value.
The strict field (allowCustom={false}) keeps
its committed label instead.
combobox · catalogue
- node-pty conpty / forkpty — the battle-tested addon
- Bun.Terminal linux/macos since 1.3.13, windows 1.3.14
- @sigma/pty-ffi deno FFI over rust portable-pty
- termless VT emulator — not a pty host
node-pty- node-pty conpty / forkpty — the battle-tested addon
- Bun.Terminal linux/macos since 1.3.13, windows 1.3.14
- @sigma/pty-ffi deno FFI over rust portable-pty
—- node-pty conpty / forkpty — the battle-tested addon
- Bun.Terminal linux/macos since 1.3.13, windows 1.3.14
- @sigma/pty-ffi deno FFI over rust portable-pty
—- node-pty conpty / forkpty — the battle-tested addon
- Bun.Terminal linux/macos since 1.3.13, windows 1.3.14
- @sigma/pty-ffi deno FFI over rust portable-pty
- termless VT emulator — not a pty host
backend is required
<script lang="ts">
import Combobox from '@ui/combobox.svelte';
import type { ComboboxOption } from '@ui/combobox.svelte';
const backendOptions: ComboboxOption[] = [
{ value: 'node-pty', label: 'node-pty', description: 'conpty / forkpty — the battle-tested addon' },
{ value: 'bun-terminal', label: 'Bun.Terminal', description: 'linux/macos since 1.3.13, windows 1.3.14' },
{ value: '@sigma/pty-ffi', label: '@sigma/pty-ffi', description: 'deno FFI over rust portable-pty' },
{ value: 'termless', label: 'termless', description: 'VT emulator — not a pty host', disabled: true },
];
let backendRoute = $state<string | undefined>('node-pty');
let backendStrict = $state<string | undefined>(undefined);
let backendCustom = $state<string | undefined>(undefined);
</script>
<Combobox label="backend — type to filter" bind:value={backendRoute} options={backendOptions} />
<!-- strict: allowCustom={false} reverts stray text on blur -->
<Combobox
label="strict — no custom values"
allowCustom={false}
bind:value={backendStrict}
options={backendOptions.slice(0, 3)}
placeholder="Search..."
/>
<!-- no match → the “Use “xxx”” row -->
<Combobox
label="custom — try “wasi”"
bind:value={backendCustom}
options={backendOptions.slice(0, 3)}
placeholder="Search or type..."
/>
<!-- error wiring: aria-invalid + dashed shell -->
<Combobox label="backend" error="backend is required" options={backendOptions} />The component keeps the popover orchestration law of the family: popover="auto" panels anchored with CSS Anchor
Positioning (anchor-size(width), flip fallbacks,
viewport-center when the engine lacks it), focus that never enters the panel — the
roving highlight rides aria-activedescendant + aria-owns off the input itself — and geometry from
logical properties only, so dir="rtl" mirrors the
chevron and the selected-row edge with zero branches.
const backends: ComboboxOption[] = [
{ value: 'node-pty', label: 'node-pty', description: 'conpty / forkpty addon' },
{ value: 'bun-terminal', label: 'Bun.Terminal', description: 'linux/macos 1.3.13+, windows 1.3.14' },
{ value: '@sigma/pty-ffi', label: '@sigma/pty-ffi', description: 'deno FFI over rust portable-pty' },
];
<!-- type to filter (label, case-insensitive); ↑/↓ + Enter commits; Escape
reverts; Tab keeps. No match + allowCustom → the “Use “xxx”” row -->
<Combobox label="backend" bind:value={backend} options={backends} />
<!-- strict: allowCustom={false} reverts stray text on blur -->
<Combobox label="runtime" allowCustom={false} options={backends} />multiple
multiple — chips, check states, and a lossless form bridge
multiple flips the bindable to string[] in SELECTION ORDER: options toggle membership (pick to add, re-pick to remove), the trigger wears chips with per-chip remove ×, panel rows carry aria-multiselectable plus a check glyph, and picking keeps the panel open. Submission rides the form-field bridge's MULTIVALUE seam — the committed array crosses as a values PROPERTY and lands in FormData as repeated same-name entries, so getAll(name) returns every pick byte-for-byte in order; form.reset() restores the mount array and disabled fields submit nothing. showClear adds an × in the trigger lane that empties the selection — the field then submits honestly empty.
combobox · multiple + clear
- node-pty conpty / forkpty — the battle-tested addon
- Bun.Terminal linux/macos since 1.3.13, windows 1.3.14
- @sigma/pty-ffi deno FFI over rust portable-pty
- termless VT emulator — not a pty host
[node-pty] — chips remove ×,
panel check state, re-pick toggles off- node-pty conpty / forkpty — the battle-tested addon
- Bun.Terminal linux/macos since 1.3.13, windows 1.3.14
- @sigma/pty-ffi deno FFI over rust portable-pty
- termless VT emulator — not a pty host
nothing, never "undefined" · value: bun-terminal<script lang="ts">
import Combobox from '@ui/combobox.svelte';
import type { ComboboxOption } from '@ui/combobox.svelte';
const backendOptions: ComboboxOption[] = [
{ value: 'node-pty', label: 'node-pty' },
{ value: 'bun-terminal', label: 'Bun.Terminal' },
{ value: '@sigma/pty-ffi', label: '@sigma/pty-ffi' },
];
let pickedStacks = $state<string[]>(['node-pty']);
let clearedRoute = $state<string | undefined>('bun-terminal');
</script>
<!-- multiple: bind an ARRAY in selection order; getAll(name) returns
the picks byte-for-byte in order -->
<Combobox
label="stacks (multiple)"
multiple
name="stacks"
bind:value={pickedStacks}
options={backendOptions}
placeholder="pick several…"
/>
<!-- showClear: × in the trigger lane; clearing submits honestly empty -->
<Combobox
label="backend (showClear)"
showClear
name="backend-clear"
bind:value={clearedRoute}
options={backendOptions}
/>The transport is the DECIDED one (design.md): no hidden inputs, no joined-string
channel — newline-bearing, quote-bearing and arbitrary Unicode values survive
byte-for-byte because each value is a separate FormData entry. The breaking change is
deliberate and documented: in multiple mode bind:value is string[] — there is no compatibility shim. Blur no
longer commits raw text in multiple mode; chips join through explicit Enter / row click
only.
<!-- multiple: bind an ARRAY in selection order; the committed
array submits as repeated same-name FormData entries through the
form-field bridge's MULTIVALUE seam — getAll(name) returns the
picks byte-for-byte in order (BREAKING: value becomes string[]) -->
<Combobox label="stacks" multiple bind:value={stacks} options={backends} />
<!-- showClear: × in the trigger lane when something is committed;
clearing submits honestly empty (no entry, never "undefined") -->
<Combobox label="backend" showClear bind:value={backend} options={backends} />
const stacks = $state<string[]>([]);geometry
RTL — geometry from logical properties
Nothing in the component branches on direction: the chevron sits in the flex flow and the selected-row edge is border-inline-start. The writing mode does the rest.
combobox · rtl
- node-pty conpty / forkpty — the battle-tested addon
- Bun.Terminal linux/macos since 1.3.13, windows 1.3.14
- @sigma/pty-ffi deno FFI over rust portable-pty
- termless VT emulator — not a pty host
<script lang="ts">
import Combobox from '@ui/combobox.svelte';
import type { ComboboxOption } from '@ui/combobox.svelte';
const backendOptions: ComboboxOption[] = [
{ value: 'node-pty', label: 'node-pty' },
{ value: 'bun-terminal', label: 'Bun.Terminal' },
];
let backendRtl = $state<string | undefined>('bun-terminal');
</script>
<!-- logical properties only: dir="rtl" mirrors the chevron and the
selected-row edge with zero branches -->
<div dir="rtl">
<Combobox label="backend (rtl)" bind:value={backendRtl} options={backendOptions} />
</div>The chevron sits in the flex flow, the selected-row edge is border-inline-start, and the panel anchors with CSS
Anchor Positioning whose offsets are logical too. The writing mode does the rest.
types
Combobox variants
The commit rules are the variants: free custom values by default, strict reverting on blur, plus the error shell.
allowCustom (default)
No match → the “Use “xxx”” row in primary; Enter/Tab commits the typed text as the value.
strict — allowCustom={false}
Stray text reverts on blur; only a listed option (or empty) can be committed.
disabled option rows
Options may disable themselves — skipped by keyboard navigation and click.
error wiring
error → aria-invalid + aria-describedby + the dashed shell.
a11y
Accessibility
Focus never enters the panel — the roving highlight rides aria-activedescendant off the input itself.
Keyboard
| Key | Action |
|---|---|
| ↑ / ↓ | Move the roving highlight through the filtered rows (opening the panel first if closed) |
| Enter | Commit the highlighted row (or the raw text with allowCustom) |
| Escape | Revert to the committed display and close the panel |
| Tab | Keep the typed text: resolve to an option, custom value, or revert |
| any character | Open the panel and filter live — the first match auto-highlights |
ARIA
| Attribute | Value | Description |
|---|---|---|
| role | combobox | On the trigger input, with aria-haspopup="listbox". |
| aria-activedescendant | {id}-opt-n | The keyboard/aria cursor; focus stays in the input the whole time. |
| aria-controls / aria-owns | {id}-listbox | The top-layer promoted listbox is a DOM sibling of the input. |
| aria-expanded | true/false | On the input; mirrors panel state. |
| aria-multiselectable | 'true' | On the listbox in multiple mode; picked rows carry aria-selected plus the check glyph. |
| aria-label | "remove X" / "clear selection" | On the chip remove × buttons and the showClear ×. |
| aria-invalid / aria-describedby | true / {id}-error | Error wiring — dashed shell plus the validation message. |
theming
Density and tokens
Density is live through four measured channels with one floor asymmetry; the glyph slots, the anchor and the motion kernel are the family's own seams.
- node-pty conpty / forkpty — the battle-tested addon
- Bun.Terminal linux/macos since 1.3.13, windows 1.3.14
- @sigma/pty-ffi deno FFI over rust portable-pty
- termless VT emulator — not a pty host
- node-pty conpty / forkpty — the battle-tested addon
- Bun.Terminal linux/macos since 1.3.13, windows 1.3.14
- @sigma/pty-ffi deno FFI over rust portable-pty
- termless VT emulator — not a pty host
- node-pty conpty / forkpty — the battle-tested addon
- Bun.Terminal linux/macos since 1.3.13, windows 1.3.14
- @sigma/pty-ffi deno FFI over rust portable-pty
- termless VT emulator — not a pty host
- node-pty conpty / forkpty — the battle-tested addon
- Bun.Terminal linux/macos since 1.3.13, windows 1.3.14
- @sigma/pty-ffi deno FFI over rust portable-pty
- termless VT emulator — not a pty host
| Token | Default | Source |
|---|---|---|
| --jx-text | 11 / 12 / 13 / 15px (measured) | density |
| --jx-text-secondary | 10 / 11 / 12 / 14px (measured) | density |
| --jx-stack | 4 / 4 / 8 / 8px (measured) | density |
| --jx-hit | lane 26 / 30 / 38 / 46px; shell floor FIXED 40px (measured) | density |
| --jx-icon-chevron / -clear / -check | inline SVG fallbacks | component |
| --jx-cbx-{id} | anchor-name | component |
| --jx-p | 0 → 1 timeline | component |
| colorScheme | light ↔ dark on prefers-color-scheme | structural |
axes
The eight axes
The eight-axis surface (explicit-props): size · shape · radius · density · color · theme · elevation · motion — each axis takes named steps, auto (inherit the ambient context; stamps nothing), an exact number (px · coefficient · dp · hue per axis), or query() for responsive values. MEASURED, not claimed: density is the family's only live ladder (four channels, one floor asymmetry); size is the §11 echo with nothing following; the theme split is the page's same-token demonstrator; shape/radius/color/elevation are supply-only.
| Property | Type | Default | Description |
|---|---|---|---|
| size | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' | THE §11 ECHO, NOTHING FOLLOWS — the stamp lands verbatim (measured root inline: --jx-size-effective: 14px; font-size: var(--jx-size-effective, 1rem); computed 14px / 18px at the two demo lanes), and no family paint is em-of-parent: the input's voice is the lane's var(--jx-text) (density-governed, it overrides inheritance), the shell floor is 2.5rem rem-of-page, the paddings ride px space channels, the option voices ride token steps. A stamp with nothing to scale. Number unit: px. |
| shape | 'round' | 'scoop' | 'bevel' | 'notch' | 'square' | 'squircle' | 'auto' | 'auto' | SUPPLY-ONLY — stamps the carriers; zero readers in the family (grep receipt), and the shell corner is the input-family box law itself: borderRadius 0, the squared edge. Number unit: none. |
| radius | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' | SUPPLY-ONLY — stamps --jx-radius-effective; zero readers (grep receipt). The shell, the chips and the panel are square by the family box law — the concentric broadcast finds nothing to round. Number unit: px. |
| density | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' | LIVE THROUGH FOUR CHANNELS + THE FLOOR ASYMMETRY (all measured across xs/sm/default/lg): the label and error voices step --jx-text-secondary 10/11/12/14px; the input's voice steps --jx-text 11/12/13/15px; the field gap steps --jx-stack 4/4/8/8px; the lane's min-height steps --jx-hit−2px 26/30/38/46px. The ASYMMETRY: the shell's own floor is FIXED 2.5rem (40px) at every rung — at xs/sm the lane shrinks inside the floor (dead), at lg the 46px lane outgrows it (live), so the control grows with lg and refuses to shrink below 40. The chips stay 1.5rem fixed. The rung rides the ANCESTOR scope: under DensityDemo's [data-density] boxes the family's own attr stays absent (measured null — ambient carries no explicit lane); pass density="small" and the alias resolves small→sm · medium→default · large→lg onto the field root. Number unit: coefficient. |
| color | 'primary' | 'secondary' | 'error' | 'warn' | 'success' | 'info' | 'auto' | number | string | 'auto' | SUPPLY-ONLY — stamps --jx-color-effective; zero readers in the family (grep receipt). The ink is the theme-token set (the typed --jx-foreground/--jx-muted-*) plus the terminal machines; the two semantic accents (the “Use “xxx”” row, the selected edge + check glyph) read --primary/--jx-primary directly, not the lane. Number unit: hue degrees. |
| theme | 'light' | 'dark' | 'system' | 'auto' | 'auto' | THE BRIDGE, AND THE PAGE'S SPLIT DEMONSTRATOR — the .dark class lands on the field root and what answers depends on the EMISSION FORM. TYPED POLE (frozen at :root): the shell's border and ground, the chips, and the ACTIVE + SELECTED row grounds — measured inside a .dark scope the border stays the light oklch(0 0 0) and --jx-terminal-hover still mixes its light pole. RAW MACHINES (flip): the well shadow (measured flipping to the white-inset dark well), the option ink and HOVER ground (raw --terminal-hover — the SAME sheet token whose typed twin stayed home), the panel ink, the focus ring, the check glyph. Hover a row in a dark scope and the machine lands the dark ground; arrow onto the same row and the frozen pole paints it light — one listbox, two time-bases. A third time-base rides the shell's colorScheme property, flipping on prefers-color-scheme (the media, not the class). system/auto ride tree inheritance. No number lane. |
| elevation | 'level-1' | 'level0' | 'level1' | 'level2' | 'level3' | 'level4' | 'level5' | 'auto' | number | 'auto' | SUPPLY-ONLY — zero carrier reads (grep receipt). The shadow the shell carries is the control-shell law’s own F-1 well sweep (var(--shadow-well) resting, -hover deepening — intensity, not tier); an elevation lane adds nothing to it. Number unit: dp. |
| motion | 'reduced' | 'subtle' | 'normal' | 'expressive' | 'auto' | number | 'auto' | AXIS SUPPLY-ONLY, KERNELS LIVE — the lane carrier is unread (grep receipt), but the family’s motion rides the sheet kernels directly: transitions on var(--motion-100)/var(--motion-150) ease-out (the well sweep, the chevron’s 180° flip, the row ground/ink, the × buttons), the prefers-reduced-motion kill over all of them, and the panel’s WAAPI surface kernel (the measured --jx-p: 0 → 1 timeline; every visible formula hangs off it in jixoai.css). Number unit: coefficient. |
Receipts: the density ladder, the size echo, the frozen-pole contrast and the kernel timeline were measured on this page's served DOM (probe, task 24); the supply-only rows carry grep receipts over src/lib/ui/combobox/. The universal demo folds in below, with a query() seat: the density lane flips to the large rung at the lg viewport key (≥64rem — the lg key is 64rem VIEWPORT_SCALE, not the old 40rem container tier).
Combobox · universal props
- node-pty conpty / forkpty — the battle-tested addon
- Bun.Terminal linux/macos since 1.3.13, windows 1.3.14
- @sigma/pty-ffi deno FFI over rust portable-pty
- termless VT emulator — not a pty host
- node-pty conpty / forkpty — the battle-tested addon
- Bun.Terminal linux/macos since 1.3.13, windows 1.3.14
- @sigma/pty-ffi deno FFI over rust portable-pty
- termless VT emulator — not a pty host
- node-pty conpty / forkpty — the battle-tested addon
- Bun.Terminal linux/macos since 1.3.13, windows 1.3.14
- @sigma/pty-ffi deno FFI over rust portable-pty
- termless VT emulator — not a pty host
<Combobox {options} label="px number" size={14} density="small" />
<Combobox {options} label="named steps" size="large" radius="medium" />
<Combobox {options} label="query()" density={query<{ lg: DensityLane }, DensityLane>({ lg: 'large' })} />api
API
22 meta rows − 8 ambient axes = 14 family rows; the synthesized rest row hides behind the curation — the spread is real (HTMLInputAttributes minus value/size/color ride the rest spread to the native input) — 21 served. value/multiple's generic degradation is corrected to the instantiated contract in the curation; the drift-lock test pins it.
Properties
| Property | Type | Default | Description |
|---|---|---|---|
| options * | ComboboxOption[] | — | The full option list (order = panel order): { value, label, description?, disabled? }. |
value bind | string | string[] | — | Committed value (bind:value) — a listed option’s value or a custom string; in multiple mode the selected string[] in SELECTION ORDER (breaking: no compat shim). |
| multiple | boolean | false | Multi-select: options toggle membership, trigger chips + panel check states, aria-multiselectable, and the bridge commits repeated same-name FormData entries via the MULTIVALUE seam. |
| placeholder | string | 'Search or type...' | Input placeholder while nothing is committed. |
| label | string | — | Renders label[for] above the control. |
| name | string | — | Form field name — intercepted off the input; the bridge submits the VALUE, never the display text. |
| error | string | — | Adds aria-invalid + aria-describedby + the dashed border. |
| id | string | auto | Wired into label[for] / error[id]; auto-generated when omitted. |
| allowCustom | boolean | true | Accept typed text that matches no option as the committed value (multiple: it joins the selection as a chip). |
| showClear | boolean | false | × in the trigger lane when something is committed; clearing submits honestly empty. |
| disabled | boolean | false | Disable the input, the chips and the chevron together. |
| variant | 'solid' | 'acrylic' | 'auto' | 'auto' · Own default, not ambient | Floating-surface paint of the panel. |
| class | string | '' | Forwarded to the shell. |
Universal props
| Property | Type | Default | Description |
|---|---|---|---|
| size | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | the base scale — root font-size; parts size in em |
| shape | 'round' | 'scoop' | 'bevel' | 'notch' | 'square' | 'squircle' | 'auto' | 'auto' · ambient scope | corner geometry (CSS corner-shape; §14 degrade table) |
| radius | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | corner size; auto = the concentric broadcast (§3) |
| density | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | spacing/leading scale over the kernel channels (§4) |
| color | 'primary' | 'secondary' | 'error' | 'warn' | 'success' | 'info' | 'auto' | number | string | 'auto' · ambient scope | the hue axis of the fixed oklch primary system (§5) |
| theme | 'light' | 'dark' | 'system' | 'auto' | 'auto' · ambient scope | light/dark profile; system = the JS-mutable global (§6) |
| elevation | 'level-1' | 'level0' | 'level1' | 'level2' | 'level3' | 'level4' | 'level5' | 'auto' | number | 'auto' · ambient scope | official M3 levels over the surface ladder (§7) |
| motion | 'reduced' | 'subtle' | 'normal' | 'expressive' | 'auto' | number | 'auto' · ambient scope | intensity across the motion kernels (§8) |