Theme
Customize the design system

Every control starts at the preset. Change one and every preview on the site follows, on this page and the next, until you reset it.

Contrast

Text against what it sits on. Below 4.5:1 fails WCAG AA for body text — the check a reviewer runs on any surface that takes a tone.

Waiting for the first preview to report its palette.

Corners

Sets all three radii at once. The individual values are under Geometry.

Brand 5

The accent seam an app actually brands itself through. Change these and you have made the decision a real `DesignTokens.graphite(...)` call makes.

Geometry 15

Square or rounded, and how heavy every line is. The radius row above the controls sets all three corners at once, because "what does square feel like" is one decision rather than three numbers.

Surfaces 17

Everything the app is painted ON — the ground behind the content (the frame paints it too), the cards over it, the well inset in a card, and the washes that show state.

Bands 10

The rail's head and the view bar beside it (one band across the top at 960 and wider), a pushed screen's bar, the compact bar and the compact nav bar — the band's fill, its rule, its hover and active faces, the resting field box on it, and the ink and accent control that sit on it. The rail's tree and its footer stay on the ground.

Text and controls 11

The ink ramp, the control outlines and the field's focus halo. Every pair the panel can break is watched by a contrast badge above.

Status 7

The three voices that mean something — danger, caution and ok — each as a foreground and a surface wash, danger with a border as well.

Data 5

The default pin head, its glyph and its halo, and the group bands. A pin's category color comes from the palette, named in the theme seed.

Map 11

The basemap's roles — land, water, parks, roads and their casings, buildings, labels — and the pending pin's gray. A raster basemap keeps its own imagery; these paint the map's plate and any kit-drawn map.

Scrollbars 6

Every scroller's bar: the thumb at rest and while hovered or dragged, the strip it rides, and the desk bar's thickness, side gap and corners. The desk half shows only while a fine pointer is attached; at touch the platform's own overlay keeps its size.

Advanced 6

Real controls that are not decisions — motion durations, the focus ring offset, the touch floor. Opens collapsed on purpose.

Take it with you

The Dart that reproduces what you are looking at. It is a real constructor call, because the configurator edits a real preset — what you copy is what an app can paste.

Components

Selection

The controls that answer "which of these" — one of several, on or off, and the chips that show what a reader already picked.

Spec of record: design-system/components/forms/SegmentGroup.d.ts

1
Knobs

Live

Real widgets, built from the real kit. Turn a knob and it rebuilds; the theme bar above applies the design system's own tokens — including anything you changed under Customize — and the device selector uses the same sizes the golden baselines gate on, so what you see here is what CI holds the widget to.

Press Focus on any preview to take it to the full window — at a desktop width that renders most devices at 1:1, which is when the widget's own controls become clickable. The bar always states the device and the scale, so a shrunk preview says it is shrunk. Esc leaves.

family_selection
Knobs
Theme

Every specimen at once, in one state. Per-specimen controls would rebuild the wall a board exists to remove.

Dart for these values
family_selection()

What it is made of

The widgets this screen assembles, with their full API below. They used to live on a second entry of their own; one thing you choose gets one page.

AppSegmentGroup widget

The design-system SegmentGroup — the closed-enum editor at 2–5 options: ONE row of equal-width segments sharing one hairline outline, filling the field width like every box editor. 6+ options is a Select; an open enum is a Combobox; a boolean stays a Checkbox row. Selection is the applied-toggle…

Full dartdoc

The design-system SegmentGroup — the closed-enum editor at 2–5 options: ONE row of equal-width segments sharing one hairline outline, filling the field width like every box editor. 6+ options is a Select; an open enum is a Combobox; a boolean stays a Checkbox row. Selection is the applied-toggle grammar: accent text (body weight-em) + accent border + DesignTokens.accentTint fill on the selected segment. Deliberately NO check glyph — the ink/border/tint shift is the one encoding, and a ✓ beside it would encode twice. **Fit rule** (deterministic, no config): labels render on one line and are never ellipsized. When any label cannot fit its equal share at the ambient text scale — measured at the selected weight, so selecting cannot change fit — or the share drops under the spec's 64px minimum, the control morphs to the **wrap posture**: the same segments as standalone hairline-outlined toggle tiles wrapping onto further rows (same paint, same semantics, same order). **Keyboard** (Radio semantics — this IS the exclusive choice, re-anatomized): arrow keys move the selection to the next/previous non-disabled option AND select it, wrapping past the ends; focus follows the new selection (roving). Enter/Space select the focused segment. Segments never un-check — tapping the selected one is a no-op. ```dart AppSegmentGroup<String>( label: 'Priority', value: priority, onChanged: setPriority, options: const [ (value: 'routine', label: 'Routine', disabled: false), (value: 'elevated', label: 'Elevated', disabled: false), (value: 'critical', label: 'Critical', disabled: false), ], ) ```

AppSegmentGroupdefault constructor

Knob Type Default What it does
disabled bool false Disables every segment regardless of onChanged — the state contract's disabled paint plus the field-family lock mark on the micro-label.
error String? null Group-level inline error. When non-null it renders as the danger error line below the row — the same anatomy and typography as `AppSelect`'s error line. Default null — no error anatomy.
isRequired bool false Renders the required marker — the trailing accent `*` on the micro-label (the field-family marker). Visual only; no-op when label is null.
label String? null Optional micro-label rendered above the row (DesignTokens.label).
onChanged ValueChanged<T>? null Receives the tapped option's value. Null (or disabled) disables every segment in the group.
options * List<AppSegmentOption<T>> — The exclusive options this group offers, in display order. 2–5 — more is a Select. Each entry's `label` renders inside its segment; `disabled` mutes just that segment even when the group is enabled.
value T? null The group's current selection, or `null` if none is selected yet.

AppRadio widget

The design-system Radio — a themed, token-driven pressable claiming the M3 radio archetype. A single exclusive-choice option: value is this option's identity, groupValue is the group's current selection, and this radio renders selected when they're equal.

Full dartdoc

The design-system Radio — a themed, token-driven pressable claiming the M3 radio archetype. A single exclusive-choice option: value is this option's identity, groupValue is the group's current selection, and this radio renders selected when they're equal. Circular indicator, 16px, centered in a hit area at least DesignTokens.touch (44 logical px) square. Two forms, chosen by whether label is supplied: **bare** (just the circle — pass title for the tooltip and accessible name) and **label** (circle + DesignTokens.body text in a row — the common form; the whole row is one tap target and one Semantics node). Paint: selected renders an DesignTokens.accent ring with an DesignTokens.accent center dot — **no filled background, no row tint** (Radio.d.ts: the circle is anatomy, stays round in every theme; a filled radio reads as a checkbox). Unselected renders a DesignTokens.chkBorder ring on DesignTokens.paper, no dot. State contract (all honored): enabled · hover (DesignTokens.ink4 wash across the full hit area, pointer enhancement only) · focus-visible (1px solid accent, zero offset) · pressed (no separate pressed paint beyond the state flip — the circle repaints on release) · disabled (onChanged null → DesignTokens.ink40 ring/dot, not tappable). No ripple. Radios never un-check themselves — tapping an already-selected radio is a no-op; selection only ever moves to a different value via a sibling radio (or programmatically via AppRadioGroup). ```dart AppRadio<String>( value: 'routine', groupValue: priority, label: 'Routine', onChanged: setPriority, ) ```

AppRadiodefault constructor

Knob Type Default What it does
focusNode FocusNode? null Optional external focus node — AppRadioGroup supplies one per option so arrow-key selection can rove focus onto the newly selected radio. Null keeps the internal node (current behavior).
groupValue * T? — The group's current selection, or `null` if none is selected yet.
label String? null Optional row label, styled DesignTokens.body. When present the circle and label render as one row and one tap target. When absent, only the bare circle renders — pass title for its accessible name.
onChanged ValueChanged<T>? null Receives value when this radio is activated. Radios never un-check themselves — not invoked when already selected. Null disables the radio (not tappable, muted paint).
title String? null Tooltip and accessibility label. Required in practice for the bare form (the circle alone has no visible text); ignored as the accessible name when label is set (the label speaks for the row), but still feeds the Tooltip.
value * T — This option's identity.

AppRadioGroup widget

The design-system RadioGroup — a vertical, single-select group of AppRadios claiming the M3 radio-group archetype. For **2–5 always-visible exclusive options** (Radio.prompt.md); more options, or options arriving from data, is a Select instead.

Full dartdoc

The design-system RadioGroup — a vertical, single-select group of AppRadios claiming the M3 radio-group archetype. For **2–5 always-visible exclusive options** (Radio.prompt.md); more options, or options arriving from data, is a Select instead. Renders one AppRadio per entry in options, each at least DesignTokens.touch tall, selecting value and firing onChanged with the tapped option's value. disabled mutes the whole group (each radio renders disabled, `onChanged` un-wired) without the caller threading it through every option. **Keyboard**: with focus anywhere inside the group, arrow keys move the selection to the next/previous non-disabled option AND select it (Radio.d.ts: "Arrow keys move AND select; roving tabindex") — focus follows the new selection. Down/Right advance, Up/Left go back, both wrapping past the ends. Enter/Space select the focused radio. ```dart AppRadioGroup<String>( label: 'Priority', value: priority, onChanged: setPriority, options: const [ (value: 'routine', label: 'Routine'), (value: 'elevated', label: 'Elevated'), (value: 'critical', label: 'Critical — dispatch review'), ], ) ```

AppRadioGroupdefault constructor

Knob Type Default What it does
axis Axis Axis.vertical Layout axis for the options. Axis.vertical (default) stacks the options in a column — current behavior. Axis.horizontal lays them in a wrapping row (the spec's row posture), options separated by the group's row gap.
disabled bool false Disables every option in the group regardless of onChanged.
error String? null Group-level inline error (Radio.d.ts `error`). When non-null it renders as the danger error line below the options — the same anatomy and typography as `AppSelect`'s error line. Default null — no error anatomy (current behavior).
isRequired bool false Renders the required marker — the trailing accent `*` on the micro-label (design-system pin `5908e48`). Visual only; no-op when label is null.
label String? null Optional micro-label rendered above the group (DesignTokens.label).
onChanged ValueChanged<T>? null Receives the tapped option's value. Null (or disabled) disables every option in the group.
options * List<AppRadioOption<T>> — The exclusive options this group offers, in display order. Each entry's `label` renders next to its radio; `disabled` mutes just that option even when the group itself is enabled.
value T? null The group's current selection, or `null` if none is selected yet.

AppCheckbox widget

The design-system Checkbox — a themed, token-driven tri-state pressable claiming the M3 checkbox archetype. A 16px box centered in a hit area at least DesignTokens.touch (44 logical px) square.

Full dartdoc

The design-system Checkbox — a themed, token-driven tri-state pressable claiming the M3 checkbox archetype. A 16px box centered in a hit area at least DesignTokens.touch (44 logical px) square. Two forms, chosen by whether label is supplied: **bare** (just the box — table cells; pass title for the tooltip and accessible name) and **label** (box + DesignTokens.body text in a row — settings/filter rows; the whole row is one tap target and one Semantics node). Tri-state: value is `true` (checked), `false` (unchecked), or `null` (indeterminate — the select-all "some but not all" state). Per M3 select-all behavior, tapping while indeterminate resolves to checked (`onChanged(true)`), never to unchecked. Paint: checked fills DesignTokens.accent with a check glyph in DesignTokens.onAccent; indeterminate fills DesignTokens.accent with a dash glyph in DesignTokens.onAccent; unchecked is DesignTokens.paper with a DesignTokens.chkBorder hairline border (the 3:1 boundary). Radius DesignTokens.rXs, the chip/checkbox radius. State contract (all honored): enabled · hover (DesignTokens.ink4 wash across the full hit area, pointer enhancement only) · focus-visible (1px solid accent, zero offset) · pressed (no separate pressed paint beyond the state flip — the box repaints on release) · disabled (onChanged null → DesignTokens.ink40 foreground/border, not tappable). No ripple. ```dart AppCheckbox(value: row.selected, title: 'Select row', onChanged: (v) => select(row.id, v)) AppCheckbox( value: allSelected ? true : (someSelected ? null : false), title: 'Select all', onChanged: toggleAll, ) AppCheckbox(value: prefs.archived, label: 'Include archived', onChanged: setArchived) ```

AppCheckboxdefault constructor

Knob Type Default What it does
label String? null Optional row label, styled DesignTokens.body. When present the box and label render as one row and one tap target (settings/filter rows). When absent, only the bare box renders (table cells) — pass title for its accessible name.
onChanged ValueChanged<bool>? null Receives the next boolean state. Indeterminate resolves to `true`. Null disables the checkbox (not tappable, muted paint).
title String? null Tooltip and accessibility label. Required in practice for the bare form (the box alone has no visible text); ignored as the accessible name when label is set (the label speaks for the row), but still feeds the Tooltip.
value * bool? — Tri-state value — `true` checked, `false` unchecked, `null` indeterminate.

AppSwitch widget

The design-system Switch — a themed, token-driven pressable claiming the M3 switch archetype. **Instant-effect toggles only** (settings, view options) — the value takes effect the moment it's tapped. Deferred form state (submitted later via a save action) stays `AppCheckbox`.

Full dartdoc

The design-system Switch — a themed, token-driven pressable claiming the M3 switch archetype. **Instant-effect toggles only** (settings, view options) — the value takes effect the moment it's tapped. Deferred form state (submitted later via a save action) stays `AppCheckbox`. A 34x20 track with a 12px thumb, centered in a hit area at least DesignTokens.touch (44 logical px) square. Two forms, chosen by whether label is supplied: **bare** (just the track — pass title for the tooltip and accessible name) and **label** (track + DesignTokens.body text in a row — settings rows; the whole row is one tap target and one Semantics node). Paint: off renders a DesignTokens.paper track with a DesignTokens.hairS hairline border and a DesignTokens.ink60 thumb; on renders a DesignTokens.accent track (border matches) with a DesignTokens.onAccent thumb. No icons inside the thumb. Track and thumb share DesignTokens.rS — square in house themes; a rounder theme pills both shapes with zero widget edits. State contract (all honored): enabled · hover (DesignTokens.ink4 wash across the full hit area, pointer enhancement only) · focus-visible (1px solid accent, zero offset) · pressed (no separate pressed paint beyond the state flip — the track repaints on release) · disabled (onChanged null → DesignTokens.ink40 track/border, DesignTokens.paper thumb when off or DesignTokens.ink40 track with a DesignTokens.paper thumb when on; not tappable). No ripple. ```dart AppSwitch(value: sync, label: 'Auto-sync on cellular', onChanged: setSync) AppSwitch(value: dense, title: 'Compact rows', onChanged: setDense) ```

AppSwitchdefault constructor

Knob Type Default What it does
label String? null Optional row label, styled DesignTokens.body. When present the track and label render as one row and one tap target (settings rows). When absent, only the bare track renders — pass title for its accessible name.
onChanged ValueChanged<bool>? null Receives the next boolean state. Null disables the switch (not tappable, muted paint).
title String? null Tooltip and accessibility label. Required in practice for the bare form (the track alone has no visible text); ignored as the accessible name when label is set (the label speaks for the row), but still feeds the Tooltip.
value * bool — Whether the switch is on.

AppChip widget

The design-system Chip — a themed, token-driven pressable claiming the M3 input-chip archetype. **Removable selections only** — applied filters, picked values. A chip with no onRemove is almost always the wrong widget; per the constitution, a status or neutral enum value is a word in the row, never…

Full dartdoc

The design-system Chip — a themed, token-driven pressable claiming the M3 input-chip archetype. **Removable selections only** — applied filters, picked values. A chip with no onRemove is almost always the wrong widget; per the constitution, a status or neutral enum value is a word in the row, never a chip (encode once). When onRemove is null the "x" glyph does not render at all — the chip degrades to a plain read-only label, never a disabled-but-visible remove control. THE CONTAINED CHIP (`Chip.d.ts`, 2026-09-29, #893 — the owner's answer to #869): every chip is set in the UI FONT, never the data font — the data role is for IDs, counts and numerals, and a chip is words. It is 28 tall at DesignTokens.rXs, 12.5/500, padded 10 at the inline start and 3 before the "x" (10 when there is none). A chip reads as KEY and VALUE when it has a name: the name first at the regular weight and 75% opacity, then label at the chip's weight, 6 apart — no middle dot is drawn between them (the weight and the opacity are the separator). The chip's accessible name says both halves. accent marks the applied-filter state: the DesignTokens.accentTint fill with DesignTokens.accent text and NO outline. The default (non-accent) form is DesignTokens.paper with the DesignTokens.cardEdge outline and DesignTokens.ink60 text. State contract (all honored): enabled · hover (the "x" strengthens to DesignTokens.ink, pointer enhancement only) · focus-visible on the "x" (1px solid accent, zero offset — the "x" is a real focusable/tappable target, independent of the chip body) · pressed (the "x"'s hit area, no separate chip-body press) · accent (selected styling, see above). No disabled state: a chip that cannot be removed simply omits onRemove rather than rendering a muted, non-functional "x". ```dart AppChip(label: 'Crew B-7', onRemove: () => drop('crew')) AppChip( name: 'Status', label: 'Blocked, In progress', accent: true, onRemove: () => drop('status'), ) ```

AppChipdefault constructor

Knob Type Default What it does
accent bool false Applied-filter styling: DesignTokens.accent text over the DesignTokens.accentTint fill, no outline. Defaults to the plain form.
label * String — The chip's text in the UI font — the VALUE half of a key/value chip ("Blocked, In progress"), or the whole chip ("Crew B-7", a picked value). One line, never wrapped — a long label ellipsizes.
mark AppChipMark? null THE VALUE'S MARK (`Chip.d.ts` `mark`, 2026-10-01, #977): a facet chip for a value of a column with FORMAT RULES carries the mark that value wears in the cell — the glyph in its color's ink, or with no glyph the 8px square in its fill inside the pin rim — drawn FIRST, 6 before the name, so a colored…
name String? null The KEY half of a key/value chip ("Status"), drawn before label at the regular weight and 75% opacity (`Chip.d.ts` THE CONTAINED CHIP). Null draws the label alone.
onRemove VoidCallback? null Fires when the "x" is tapped. Null hides the "x" entirely (a read-only chip) — never rendered muted/disabled.
title String? null Tooltip and accessibility hint for the chip's label region. The "x" carries its own independent `Remove <name> <label>` accessible name regardless of title.

Types its knobs take

These are not things you choose — they are the shapes the knobs above expect. They live under this entry rather than in the navigation for exactly that reason.

AppChipMark support

A chip's VALUE MARK (`Chip.d.ts` `mark`: `{color?, glyph?}`) — the color and glyph a formatted value wears, without the rule's paint or text treatments (a chip keeps its own).

Field Type Default What it does
color KitColor? null The mark's color — the square's fill, or the glyph's ink.
glyph KitIconRef? null The mark's glyph, or null for the color square.

Where it lives

Class File
AppSegmentGroup packages/app_widgets/lib/src/kit/forms/segment_group.dart
AppRadio packages/app_widgets/lib/src/kit/forms/radio.dart
AppRadioGroup packages/app_widgets/lib/src/kit/forms/radio.dart
AppCheckbox packages/app_widgets/lib/src/kit/forms/checkbox.dart
AppSwitch packages/app_widgets/lib/src/kit/forms/switch.dart
AppChip packages/app_widgets/lib/src/kit/forms/chip.dart