SegmentedControl
A compact mutually-exclusive option switch — radiogroup semantics with roving tabindex — for view modes and filter scopes inside a surface.
Import
import { SegmentedControl } from "@roger-emerson/ploxum-primitives";Usage
const [scope, setScope] = useState<"all" | "p1" | "p2">("all");
<SegmentedControl
ariaLabel="Alert severity scope"
onValueChange={setScope}
options={[
{ value: "all", label: "All" },
{ value: "p1", label: "P1" },
{ value: "p2", label: "P2" },
]}
size="sm"
value={scope}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
options | SegmentedControlOption<V>[] | — | Segment definitions, in display order. Required. |
value | V extends string | — | The active option's value. Controlled — required. |
onValueChange | (value: V) => void | — | Fires on click and on Arrow / Home / End keyboard navigation. Required. |
ariaLabel | string | — | ARIA label for the radiogroup. Required. |
size | "sm" | "md" | "md" | sm = 28px segments, md = 36px. |
className | string | — | Merged onto the root. |
ref | Ref<HTMLDivElement> | — | Ref to the root. |
Also accepts all HTMLAttributes<HTMLDivElement>.
interface SegmentedControlOption<V extends string = string> {
value: V;
label: string;
icon?: ReactNode; // leading, aria-hidden
}The generic V flows from options through value and onValueChange, so string-union state types check end-to-end.
Design rules
- It's a radiogroup with roving tabindex — ArrowLeft/ArrowRight wrap, Home/End jump, and only the active segment is tabbable. Don't wrap segments in extra focusable elements.
- The active state reads by border (
amber-tint) + elevated surface, not hue alone. - Use it for 2–5 mutually exclusive view options within a surface. When segments swap entire page regions, use
TabBarinstead. - It's controlled-only — there is no
defaultValue. Keep the state in the owning view (or the demo's zustand store).
