Component catalog
Browse the public controls by interaction purpose. Every result links to generated API facts, accessibility behavior, source, and exhaustive stories.
Browse the public controls by interaction purpose. Every result links to generated API facts, accessibility behavior, source, and exhaustive stories.
A horizontal track of 2–N mutually-exclusive segments where exactly one is selected — the canonical glass segmented picker.
import { SegmentedControl } from "@liquidify/react/segmented-control"| Prop | Type | Default | Description |
|---|---|---|---|
| children required | ReactNode | — | The segments — 2..N {@link SegmentedControl.Item} children, rendered equal-width (children role: `items`). Only `SegmentedControl.Item` children are read; any non-`Item` node (a `Fragment`, a wrapper, stray text) is a **dev-mode `console.error`** and is filtered in production. Each `Item`'s `value` is the collection identity (resolved through `shared/collection`) — duplicate `value`s are a dev-mode error (first wins in production); duplicate visible **labels** are always supported. |
| className | string | undefined | — | Optional additional CSS class merged into the glass surface class. The supported per-instance customisation seam: override any `--lq-*` custom property from this class's CSS — the token cascade the engine reads (ADR-0004). No raw inline style is involved, so this stays ADR-0003 clean. |
| defaultValue | string | undefined | the first child's `value` | Uncontrolled seed for the internal selection (read once at mount; ignored once {@link SegmentedControlProps.value} is supplied). A seed that matches no `.Item` resolves to no selection, like a missing controlled `value`. |
| disabled | boolean | undefined | — | When `true`, the whole control is dimmed, removed from the tab order (every segment `tabIndex={-1}`), and all interaction is suppressed. Since the root is a `<div role="radiogroup">` (no native `disabled`), this sets `aria-disabled` plus a `data-disabled` styling hook. |
| distribution | "fill" | "fit" | undefined | fill | Width distribution — the `NSSegmentedControl.Distribution` analogue. `"fill"` (the default) makes the control **fill its container** (`display: grid`); `"fit"` makes it **hug its content** (`display: inline-grid`). Either way the segments stay equal-width; only container-fill vs content-hug differs. Resolved via the `data-distribution` attribute selector in `styles.css`. |
| labelProps | LabelProps | undefined | — | Per-instance typography override for the segment labels, forwarded to the internal {@link Text} (ADR-0028). Token-backed axes only; the HIG default is `variant="body"` with `color="inherit"`, so each label adopts the segment's own selected/unselected colour (system-gray-1 unselected, primary selected). Applies only to string/number segment labels — an `<Icon>` segment passes through unwrapped. |
| onChange | ((next: string) => void) | undefined | — | Fires with the next selected item's `value` whenever the selection moves — pointer tap, drag-release, or a keyboard arrow / Home / End. Runs through the shared {@link useControllableState} triad, so it fires **at most once per accepted change** in both controlled and uncontrolled modes: a no-op re-selection, a disabled interaction, and a parent-driven controlled `value` change all emit nothing. |
| ref | Ref<HTMLDivElement> | undefined | — | Forwarded ref to the rendered element — the component's native root (or the merged child element on a component that opts into {@link SlottableProps}). Generic over `E` (charter `docs/api-conventions.md` §4 rule 1, default `HTMLElement`) so a component with a polymorphic root (Link's navigation / action mode union, Text's `as`) can instantiate the concrete element per variant instead of leaving every consumer with a widened `Ref<HTMLElement>`. A component that does not narrow simply inherits the `HTMLElement` default — source-compatible with every pre-existing non-generic `extends ControlProps`. |
| size | SegmentedControlSize | undefined | regular | Size of the control — the full canonical {@link SegmentedControlSize} scale (`mini · small · regular · large · xlarge`). SegmentedControl is the exception that ships all five sizes (owner change). Resolved to geometry via the `data-size` attribute selector in `styles.css`; across the five sizes only the box scales — the label font-size stays constant. |
| value | string | undefined | — | Controlled selected value. Passing this switches SegmentedControl to controlled mode: the rendered selection always reflects this value and the consumer owns it via {@link SegmentedControlProps.onChange}. A `value` that matches no `.Item` resolves to **no selection** (no segment `aria-checked`, the pill hidden at index 0, roving `tabIndex` on the first enabled segment) — it never silently selects index 0. Switching between controlled and uncontrolled after mount is a dev-mode `console.error`. |
SegmentedControl supports the documented keyboard interactions below.
| Name | Value |
|---|---|
| --lq-color-accent | #007aff |
| --lq-color-bg-primary | #ffffff |
| --lq-color-label-primary | Component-defined value |
| --lq-color-label-secondary | Component-defined value |
| --lq-glass- | 440.71.15var(--lq-segmented-control-indicator-blur)calc(var(--lq-glass-material-blur) * 8)var(--lq-shadow-presentational) |
| --lq-glass-fill-0 | Resolved by the active Liquidify theme |
| --lq-hit-target-touch | 2.75rem |
| --lq-lens-progress | Resolved by the active Liquidify theme |
| --lq-lens-stretch | Resolved by the active Liquidify theme |
| --lq-lens-x | Resolved by the active Liquidify theme |
| --lq-radius- | 0.25rem0.375rem0.5rem0.625rem0.875rem1.125rem1.375rem9999rem |
| --lq-radius-pill | 9999rem |
| --lq-segmented-control- | var(--lq-space-40)var(--lq-hit-target-min)var(--lq-space-32)var(--lq-space-48)var(--lq-space-56)var(--lq-space-4)var(--lq-font-size-body)calc(var(--lq-glass-material-blur) * 8)calc(var(--lq-space-24) - var(--lq-space-2))var(--lq-radius-pill)var(--lq-shadow-glass-edge), var(--lq-shadow-interactive-active)0.4 |
| --lq-segmented-control-bloom-w | calc( (var(--lq-segmented-control-lens-w, var(--lq-space-48)) - var(--lq-segmented-control-rest-w, var(--lq-space-32))) * var(--lq-lens-progress, 0) ) |
| --lq-segmented-control-col-w | calc(100% / 2 - 2 * var(--lq-segmented-control-pad, var(--lq-space-2)))calc(100% / 3 - 2 * var(--lq-segmented-control-pad, var(--lq-space-2)))calc(100% / 4 - 2 * var(--lq-segmented-control-pad, var(--lq-space-2)))calc(100% / 5 - 2 * var(--lq-segmented-control-pad, var(--lq-space-2)))calc(100% / 6 - 2 * var(--lq-segmented-control-pad, var(--lq-space-2)))calc(100% / 7 - 2 * var(--lq-segmented-control-pad, var(--lq-space-2)))calc(100% / 8 - 2 * var(--lq-segmented-control-pad, var(--lq-space-2))) |
| --lq-segmented-control-indicator-alpha | Component-defined value |
| --lq-segmented-control-inset | Component-defined value |
| --lq-segmented-control-label-size | var(--lq-font-size-body) |
| --lq-segmented-control-lens- | calc(var(--lq-segmented-control-rest-h, var(--lq-space-24)) * 1.5)calc( var(--lq-segmented-control-rest-w, var(--lq-space-32)) + (var(--lq-segmented-control-lens-h, var(--lq-space-40)) - var(--lq-segmented-control-rest-h, var(--lq-space-24))) ) |
| --lq-segmented-control-pad | var(--lq-space-4) |
| --lq-segmented-control-rest- | calc( var(--lq-segmented-control-track-h, var(--lq-space-32)) - 2 * var(--lq-segmented-control-pad, var(--lq-space-2)) )calc(var(--lq-segmented-control-rest-h, var(--lq-space-24)) * 1.4) |
| --lq-segmented-control-track-h | var(--lq-space-40)var(--lq-hit-target-min)var(--lq-space-32)var(--lq-space-48)var(--lq-space-56) |
| --lq-shadow-ambient-1 | 0 0.125rem 0.375rem rgba(0, 0, 0, 0.10) |
| --lq-space- | 0.0625rem0.125rem0.25rem0.5rem0.75rem1rem1.25rem1.5rem2rem2.5rem3rem3.5rem |
| --lq-toggle- | var(--lq-space-2)var(--lq-toggle-pad, var(--lq-space-2))0.55calc(var(--lq-space-64) + var(--lq-space-8))calc(var(--lq-space-40) + var(--lq-space-4))var(--lq-space-32)calc(var(--lq-space-16) + var(--lq-space-4))calc(var(--lq-toggle-track-h, var(--lq-space-32)) - 2 * var(--lq-toggle-pad, var(--lq-space-2)))var(--lq-space-24)var(--lq-thumb-lens-h)var(--lq-thumb-lens-w)calc( var(--lq-toggle-track-w, var(--lq-space-56)) - 2 * var(--lq-toggle-hpad, var(--lq-space-2)) - var(--lq-toggle-thumb-w, var(--lq-space-32)) ) |