Segment Group
A segmented single-choice control on Ark UI's SegmentGroup machine — a compact, always-visible row of mutually exclusive segments with a sliding pill, built-in label / description / error, and native form submission.
SegmentGroup is a compact, always-visible single-choice control — a row (or column) of mutually exclusive segments with a pill that slides to the selected item. Reach for it over a Select when the option set is small (2–5 items) and you want every choice visible at a glance: view toggles, filters, billing cadence, alignment. It implements the kit's value-based form-control contract, so name submits and onChange hands you the value directly. Label, description, and error are built in via Field.
import { SegmentGroup } from '@42/ui-react/segment-group';
<SegmentGroup
items={['List', 'Grid', 'Board']}
defaultValue="List"
/>Playground
Component
Dir
Presets
Props
Code
<Component orientation="horizontal" variant="default" size="md"/>Items
items accepts bare strings (where the value is the label) or
{ value, label, disabled? } objects — mixed freely.
// 1 — strings (value === label)
<SegmentGroup items={['Common Core', 'Piscine']} />
// 2 — objects, with optional per-item disabled
<SegmentGroup
items={[
{ value: 'left', label: 'Left' },
{ value: 'center', label: 'Center' },
{ value: 'justify', label: 'Justify', disabled: true },
]}
/>Variants
Five treatments, mirroring Button. The variant restyles all three layers — the tray, the sliding pill, and the checked label — off the same --c-* color slots, so they all swap palettes uniformly. filled is a solid pill, light a soft tint, outline a ringed pill over the track, subtle is chromeless. default is intentionally palette-independent (a neutral pill); the group color only tints the other four — though a per-item color still tints default.
Code
<SegmentGroup variant="filled" items={['List', 'Grid', 'Board']} defaultValue="List" /><SegmentGroup variant="light" items={['List', 'Grid', 'Board']} defaultValue="List" /><SegmentGroup variant="outline" items={['List', 'Grid', 'Board']} defaultValue="List" /><SegmentGroup variant="subtle" items={['List', 'Grid', 'Board']} defaultValue="List" /><SegmentGroup variant="default" items={['List', 'Grid', 'Board']} defaultValue="List" />Sizes
Five sizes — xs through xl — matching the rest of the family.
Code
<SegmentGroup size="xs" items={['List', 'Grid', 'Board']} defaultValue="List" /><SegmentGroup size="sm" items={['List', 'Grid', 'Board']} defaultValue="List" /><SegmentGroup size="md" items={['List', 'Grid', 'Board']} defaultValue="List" /><SegmentGroup size="lg" items={['List', 'Grid', 'Board']} defaultValue="List" /><SegmentGroup size="xl" items={['List', 'Grid', 'Board']} defaultValue="List" />Colors
The resting segments are palette-independent; only the sliding pill (and the checked label's text) adopt color. Set data-color once on the root and every kit palette tints for free.
Code
<SegmentGroup color="brand" items={['List', 'Grid', 'Board']} defaultValue="List" /><SegmentGroup color="green" items={['List', 'Grid', 'Board']} defaultValue="List" /><SegmentGroup color="blue" items={['List', 'Grid', 'Board']} defaultValue="List" /><SegmentGroup color="purple" items={['List', 'Grid', 'Board']} defaultValue="List" />Per-item color
Give an item its own color and the pill adopts it whenever that item is selected — handy for status pickers where each option owns a hue. This overrides the group color, and unlike the group color it tints even the neutral default variant (the pill turns into a solid fill in that color). Select each segment below to see the pill shift.
Code
<SegmentGroupdefaultValue="active"items={[ { value: 'todo', label: 'Queued' }, { value: 'active', label: 'In progress', color: 'blue' }, { value: 'done', label: 'Validated', color: 'green' },]}/>Custom rendering
By default each segment renders its label. Pass renderItem to fill the label content yourself — it receives the (value-narrowed) item, including its typed payload, and the live { checked, disabled } state. The segment keeps its sizing, sliding pill, and selection wiring; you fill the inside. Define the function in a "use client" module so it survives the server → client boundary.
'use client';
// item.payload is inferred from `items` — here { count: number } | undefined
const renderTab = (item, { checked }) => (
<span className="inline-flex items-center gap-1.5">
{item.label}
{item.payload && (
<span className={checked ? 'bg-(--c-on-solid)/20 rounded-full px-1.5' : 'rounded-full bg-gray-light-200 px-1.5'}>
{item.payload.count}
</span>
)}
</span>
);
<SegmentGroup items={inbox} renderItem={renderTab} />Typed payload
SegmentGroup is generic over items (via a const type parameter), exactly like Select: value / onChange narrow to the literal value union, and an item's payload is inferred so item.payload is fully typed in renderItem (and undefined for bare-string or payload-less items). The machine ignores payload — only your renderer sees it.
'use client';
const evals = [
{ value: 'all', label: 'All', payload: { count: 128 } },
{ value: 'booked', label: 'Booked', payload: { count: 9 } },
] as const;
// value is 'all' | 'booked'; item.payload is { count: number } | undefined
<SegmentGroup
items={evals}
defaultValue="all"
onChange={(value) => console.log(value)}
renderItem={(item) => <>{item.label} ({item.payload?.count})</>}
/>Orientation
Set orientation="vertical" to stack the segments in a column instead of a row.
Label, description & error
SegmentGroup wraps itself in Field, so label / description / error / required work out of the box and wire the aria-* relationships. Passing error flips the control to the invalid (red) state.
Code
<SegmentGrouplabel="View"description="How the projects are laid out."items={['List', 'Grid', 'Board']}defaultValue="Grid"/><SegmentGrouplabel="View"error="Pick a layout"items={['List', 'Grid', 'Board']}/>Controlled
Drive the value from state. onChange hands you the selected value directly.
'use client';
import { useState } from 'react';
import { SegmentGroup } from '@42/ui-react/segment-group';
export function ViewToggle() {
const [view, setView] = useState('list');
return (
<SegmentGroup
items={['List', 'Grid', 'Board']}
value={view}
onChange={setView}
/>
);
}Forms
SegmentGroup renders a hidden radio input per segment, all sharing the field name, so the selected value rides a native <form> submission and FormData — no controlled state required.
<form action={save}>
<SegmentGroup name="view" items={['List', 'Grid', 'Board']} />
</form>API
Prop
Type
Accessibility
- Built on Ark UI's SegmentGroup (radio-group) machine:
←/→(or↑/↓when vertical) move and select,Tabenters / leaves the group, and the selection follows focus. - Each segment is backed by a hidden native radio input with the shared
name, exposingrole="radiogroup"/radioandaria-checked. invalid(orerror) flips the group to the red state; label / description / error associations are wired via the built-inField.- RTL is honored via the document
dir— the segments and the sliding pill mirror automatically.
Switch
A single boolean toggle on Ark UI's Switch machine — sliding track + thumb, inline label, built-in description / error, native form submission, and full keyboard / ARIA.
Choice Card Group
A data-driven set of selectable cards — radio (single) or checkbox (multi) — each a bordered card with a title, helper line, and an optional leading icon, with built-in label / description / error and native form submission.