{"slug":"segmented-control","name":"SegmentedControl","packageName":"@liquidify/react","category":"controls","maturity":"experimental","summary":"A horizontal track of 2–N mutually-exclusive segments where exactly one is selected — the canonical glass segmented picker.","importPath":"@liquidify/react/segmented-control","sourcePath":"packages/@liquidify/react/src/components/segmented-control/segmented-control.tsx","specPath":"packages/@liquidify/react/src/components/segmented-control/spec.md","docsPath":"../liquidify-page/content/component-source/segmented-control.md","documentationStatus":"draft","storyIds":["controls-segmented-control--default"],"additionalExports":["SegmentedControlItemProps","SegmentedControlSize","SizedControlProps"],"anatomy":[],"props":[{"name":"children","type":"ReactNode","required":true,"description":"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.","declaredIn":"packages/@liquidify/react/src/components/segmented-control/segmented-control.tsx"},{"name":"className","type":"string | undefined","required":false,"description":"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.","declaredIn":"packages/@liquidify/react/src/liquid-component-props.ts"},{"name":"defaultValue","type":"string | undefined","required":false,"description":"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`.","defaultValue":"the first child's `value`","declaredIn":"packages/@liquidify/react/src/components/segmented-control/segmented-control.tsx"},{"name":"disabled","type":"boolean | undefined","required":false,"description":"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.","declaredIn":"packages/@liquidify/react/src/components/segmented-control/segmented-control.tsx"},{"name":"distribution","type":"\"fill\" | \"fit\" | undefined","required":false,"description":"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`.","defaultValue":"`\"fill\"`","declaredIn":"packages/@liquidify/react/src/components/segmented-control/segmented-control.tsx"},{"name":"labelProps","type":"LabelProps | undefined","required":false,"description":"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.","declaredIn":"packages/@liquidify/react/src/components/segmented-control/segmented-control.tsx"},{"name":"onChange","type":"((next: string) => void) | undefined","required":false,"description":"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.","declaredIn":"packages/@liquidify/react/src/components/segmented-control/segmented-control.tsx"},{"name":"ref","type":"Ref<HTMLDivElement> | undefined","required":false,"description":"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`.","declaredIn":"packages/@liquidify/react/src/liquid-component-props.ts"},{"name":"size","type":"SegmentedControlSize | undefined","required":false,"description":"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.","defaultValue":"`\"regular\"`","declaredIn":"packages/@liquidify/react/src/components/segmented-control/segmented-control.tsx"},{"name":"value","type":"string | undefined","required":false,"description":"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`.","declaredIn":"packages/@liquidify/react/src/components/segmented-control/segmented-control.tsx"}],"swiftUIMap":[{"swiftUI":"`value`","react":"`Picker(selection:)` get","notes":"— | controlled selection | string identity","relationship":"direct"},{"swiftUI":"`defaultValue`","react":"`@State` initial selection","notes":"first `.Item`'s `value` | uncontrolled seed | web-only idiom (ADR-0015)","relationship":"web-only"},{"swiftUI":"`onChange`","react":"`Picker(selection:)` set","notes":"— | change callback (both modes) | `(next: string) => void`","relationship":"direct"},{"swiftUI":"`children` (`.Item`)","react":"`content:` `ForEach { … .tag(_:) }`","notes":"— | — | children role: `items`","relationship":"direct"},{"swiftUI":"`.Item.value`","react":"`.tag(_:)`","notes":"— | identity | required, unique","relationship":"direct"},{"swiftUI":"`.Item.disabled`","react":"`.disabled(_:)` on the tagged view","notes":"`false` | — | per-segment skip","relationship":"direct"},{"swiftUI":"`.Item.children`","react":"the `Text`/`Image` label view","notes":"— | — | children role: `label`","relationship":"direct"},{"swiftUI":"`disabled`","react":"`.disabled(_:)` on the `Picker`","notes":"`false` | — | Shape W (§3): `aria-disabled` + `data-disabled`","relationship":"direct"},{"swiftUI":"`size`","react":"`.controlSize(_:)` (`ControlSize`)","notes":"`regular` | — | full 5-step (toolbar-density, §7)","relationship":"direct"},{"swiftUI":"`distribution`","react":"AppKit `NSSegmentedControl.Distribution` (`.fill`/`.fit`)","notes":"`fill` | — | no SwiftUI analogue; Apple-API pedigree (§5)","relationship":"web-only"},{"swiftUI":"`labelProps`","react":"label-view modifiers (`.font`/`.fontWeight`/`.foregroundStyle`)","notes":"— | — | ADR-0028 seam; text segments only","relationship":"direct"},{"swiftUI":"`className`","react":"—","notes":"— | — | web-only glass surface override","relationship":"web-only"},{"swiftUI":"`ref`","react":"—","notes":"— | — | web-only; `Ref<HTMLDivElement>` (radiogroup)","relationship":"web-only"},{"swiftUI":"`aria-label` / `aria-labelledby`","react":"`.accessibilityLabel(_:)` / label view","notes":"— | — | required accessible name","relationship":"direct"}],"tokens":["--lq-color-accent","--lq-color-bg-primary","--lq-color-label-primary","--lq-color-label-secondary","--lq-glass-","--lq-glass-fill-0","--lq-hit-target-touch","--lq-lens-progress","--lq-lens-stretch","--lq-lens-x","--lq-radius-","--lq-radius-pill","--lq-segmented-control-","--lq-segmented-control-bloom-w","--lq-segmented-control-col-w","--lq-segmented-control-indicator-alpha","--lq-segmented-control-inset","--lq-segmented-control-label-size","--lq-segmented-control-lens-","--lq-segmented-control-pad","--lq-segmented-control-rest-","--lq-segmented-control-track-h","--lq-shadow-ambient-1","--lq-space-","--lq-toggle-"],"keyboard":[{"key":"Keyboard","action":"Keyboard (ARIA-APG radiogroup, ADR-0017): ←/↑ previous, →/↓ next — each move changes selection and animates the glide (APG radio: arrows move and select); Home → first, End → last. Disabled segments are skipped. Recommend NO wrap — clamp at the ends. Each keypress preventDefaults, commits the value, and lens.moveTo(index segWidth) (spring, no follow)."}],"variants":[],"variantPages":[]}