Segmented Button

A connected group of toggle buttons for mutually exclusive (or multi-select) choices. Used for view modes, filter states, alignment, and time-range selectors.

Preview

Icon only

Omit label and provide only icon for compact toolbars. Always add a parent aria-label or title on each segment when labels are hidden.

Multi-select

Set multiple to allow any combination of segments to be active simultaneously. value becomes a string[] and each segment uses role="checkbox" instead of radio.

Sizes

Three sizes share the same scale as Button and Input.

Implementation

The selected segment is styled via a direct class comparison — no hidden input, no CSS peer trick. The active pill uses a slightly elevated surface with an inset border and a shallow box-shadow to appear "pressed in" to the track.

SegmentedButton.tsx
tsx
import { SegmentedButton } from "@/components/ui/SegmentedButton";
import { LayoutGrid, List, Map } from "lucide-react";

// Single selection (default)
const [view, setView] = useState("grid");

<SegmentedButton
  value={view}
  onChange={(v) => setView(v)}
  options={[
    { value: "grid", label: "Grid", icon: <LayoutGrid className="w-4 h-4" /> },
    { value: "list", label: "List", icon: <List className="w-4 h-4" /> },
    { value: "map",  label: "Map",  icon: <Map className="w-4 h-4" /> },
  ]}
/>

// Multiple selection
const [formats, setFormats] = useState(["bold"]);

<SegmentedButton
  multiple
  value={formats}
  onChange={(v) => setFormats(v)}
  options={[
    { value: "bold",      icon: <Bold className="w-4 h-4" /> },
    { value: "italic",    icon: <Italic className="w-4 h-4" /> },
    { value: "underline", icon: <Underline className="w-4 h-4" /> },
  ]}
/>

// Full width
<SegmentedButton fullWidth value={period} onChange={setPeriod}
  options={[
    { value: "day",   label: "Day" },
    { value: "week",  label: "Week" },
    { value: "month", label: "Month" },
    { value: "year",  label: "Year" },
  ]}
/>

SegmentedButton props

PropTypeDefaultDescription
optionsSegmentedButtonOption[]The segments to render. Each has value, optional label, optional icon.
valueT | T[]Controlled selected value. Array when multiple=true.
onChange(value: T | T[]) => voidCalled with the new value when a segment is clicked.
multiplebooleanfalseAllows multiple segments to be active simultaneously.
size"sm" | "md" | "lg""md"Segment height and font size.
fullWidthbooleanfalseStretches the group to fill its container; each segment gets equal width.

SegmentedButtonOption

PropTypeDefaultDescription
valuestringUnique identifier for this segment.
labelstringText label. Can be omitted for icon-only segments.
iconReactNodeIcon rendered before the label.
disabledbooleanPrevents selection of this segment.

Mobile

Segmented buttons with many segments overflow on narrow screens. Cap segments at 4 on mobile, or switch to a Select or bottom sheet for larger option sets.

9:41

Locations

Sitka HQ
Remote — SF
Remote — NYC
Remote — London
ScenarioGuidance
Segment countLimit to 3–4 segments on mobile. Five or more segments will either overflow or produce segments too narrow to tap reliably (below 44px each).
Scrollable variantIf more options are needed, make the segmented button horizontally scrollable with overflow-x: auto. The selected segment should always be visible on initial render — scroll it into view.
Touch target heightUse h-10 (40px) or taller on mobile. The default h-8 (32px) is below the 44px target — add extra padding or switch sizes when space allows.
Icon-only segmentsIcon-only segments save space but require a visible label elsewhere on screen to explain what mode is selected. On mobile, prefer icon + short label.
Alternative on mobileFor 5+ options, replace the segmented button with a native select or a custom bottom sheet picker. These are more thumb-friendly and scale to any option count.

Accessibility

  • The group has role="radiogroup" (single) or role="group" (multiple). Each segment has role="radio" or role="checkbox" with aria-checked.
  • Add an aria-label to the SegmentedButton when the options' meaning isn't clear from context.
  • Keyboard: Tab focuses the group; Arrow keys move between segments in single-select mode.
  • Icon-only segments must have a title attribute or aria-label for screen readers.
  • Never rely solely on selected state to communicate the current view — pair with a visible heading or title.