import {
SegmentedControl,
SegmentedControlIndicator,
SegmentedControlItem,
SegmentedControlItemInput,
} from '@vega-ui/react';
<SegmentedControl size='sm' name='preview-view' defaultValue='week' aria-label='Calendar view'>
{['Day', 'Week', 'Month'].map((label) => (
<SegmentedControlItem key={label} value={label.toLowerCase()}>
<SegmentedControlItemInput />
{label}- Browser support
- Widely available
- Chrome 111
- Edge 111
- Firefox 121
- Safari 15.4
- Safari on iOS 15.4
- Bundle size
- Anchor positioning — has a fallback in the code, not counted in the minimum
- :has()
- Oklab and OkLCh — comes from the design tokens
- clip-path
- Logical properties
- :focus-visible
- :where()
Summary
SegmentedControl is a compound component for short, always-visible exclusive option sets, with item-level native radio inputs and an animated selected-state indicator. It is the visibility-first exclusive-choice control when options should stay comparable at a glance.
Imports
import {
SegmentedControl,
SegmentedControlItem,
SegmentedControlIndicator,
SegmentedControlItemInput,
type SegmentedControlProps,
type SegmentedControlSize,
type SegmentedControlVariant,
} from '@vega-ui/react';Exported Types
SegmentedControlPropsSegmentedControlItemPropsSegmentedControlIndicatorPropsSegmentedControlItemInputPropsSegmentedControlSizeSegmentedControlVariant
Minimal Composition
import {
SegmentedControl,
SegmentedControlIndicator,
SegmentedControlItem,
SegmentedControlItemInput,
} from '@vega-ui/react';
export default function MinimalComposition() {
return (
<SegmentedControl name='billing-period' defaultValue='monthly' role='radiogroup' aria-label='Billing period'>
<SegmentedControlItem value='monthly'>
<SegmentedControlItemInput />Required Parts
SegmentedControl: root selection and indicator ownerSegmentedControlItem: visible selectable segmentSegmentedControlItemInput: native radio inside every item — focus, arrow keys, selection and form valueSegmentedControlIndicator: selected-state surface
Optional Parts
- None
Composition Order
SegmentedControlSegmentedControlItemSegmentedControlItemInput- visible item content
SegmentedControlIndicator
Variants
- Sizes:
xs,sm,md,lg,xl - Variants:
primary,secondary - States: selected, unselected, disabled
Edge Cases
- Hidden inputs matter for native form and radio-group semantics.
- Long or highly uneven labels can break visual balance.
- Use
Selectwhen the option set is no longer small and always-visible. - If each option needs title, description, or card-like content,
SegmentedControlis usually too dense for the job.
Common Mistakes
- Using segmented control for large option sets.
- Leaving out
SegmentedControlItemInput, which is the actual radio control. - Letting labels vary too much in length and breaking segment balance.
Examples
Basic
Basic: view switcher
Each segment is a SegmentedControlItem with a hidden SegmentedControlItemInput radio, all sharing the root name. The root does not set a role on its own, so pass role='radiogroup' and an aria-label to name the group.
import {
SegmentedControl,
SegmentedControlIndicator,
SegmentedControlItem,
SegmentedControlItemInput,
} from '@vega-ui/react';
export default function ViewRangeSelector() {
return (
<SegmentedControl name='calendar-view' defaultValue='week' role='radiogroup' aria-label='Calendar view'>
<SegmentedControlItem value='day'>
<SegmentedControlItemInput />Controlled/Stateful
Controlled/Stateful: date range driving a summary
The parent owns value and updates it from event.currentTarget.value in onChange. The visitor totals below are computed from the selected range.
import { useState } from 'react';
import {
SegmentedControl,
SegmentedControlIndicator,
SegmentedControlItem,
SegmentedControlItemInput,
Text,
} from '@vega-ui/react';
const dailyVisitors = [
1180, 1240, 990, 1430, 1510, 870, 760, 1320, 1290, 1405, 1388, 1460, 910, 820, 1500,
1530, 1475, 1610, 1580, 960, 880, 1620, 1700, 1655, 1590, 1720, 1010, 940, 1750, 1810,Form/Integration
Form/Integration: reading the value with FormData
The inputs are native radios with the root name, so FormData.get('export-format') returns the selected value. defaultValue becomes defaultChecked, so the form reset restores it.
import { FormEvent, useState } from 'react';
import {
Button,
Label,
SegmentedControl,
SegmentedControlIndicator,
SegmentedControlItem,
SegmentedControlItemInput,
Text,
} from '@vega-ui/react';
export default function NativeRadioGroupParticipation() {Layout/Overlay
Layout/Overlay: selector inside a dialog
The control lives in a form method='dialog': applying reads FormData, updates the value outside and closes the dialog. The dialog content remounts on open, so defaultValue starts from the applied value.
import { CSSProperties, FormEvent, useState } from 'react';
import {
Button,
Dialog,
DialogClose,
DialogContent,
DialogTrigger,
Heading,
Icon,
IconButton,
Label,
SegmentedControl,Error
Error: required choice with no default
Without defaultValue nothing is selected. required on the inputs blocks submit; their invalid event shows a HelperText linked to the group through aria-describedby, marks the inputs aria-invalid and focuses the first segment. Selecting a size clears the error.
import { FormEvent, useState } from 'react';
import {
Button,
HelperText,
Label,
SegmentedControl,
SegmentedControlIndicator,
SegmentedControlItem,
SegmentedControlItemInput,
} from '@vega-ui/react';
const sizes = ['XS', 'S', 'M', 'L', 'XL'];Disabled
Disabled: locked group and one unavailable segment
disabled on the root disables every input and keeps the selected segment visible. For a single segment, disabled on SegmentedControlItem only changes its look; put disabled on its SegmentedControlItemInput as well to make it unselectable.
import {
HelperText,
Label,
SegmentedControl,
SegmentedControlIndicator,
SegmentedControlItem,
SegmentedControlItemInput,
} from '@vega-ui/react';
export default function SelectedDisabledControl() {
return (
<div style={{ display: 'grid', gap: 'var(--spacing-12)', justifyItems: 'start', width: '100%' }}>Edge
Edge: icon-only segments
Icons carry no text, so every SegmentedControlItemInput gets an aria-label, and title on the item gives sighted mouse users the same name on hover.
import {
Icon,
SegmentedControl,
SegmentedControlIndicator,
SegmentedControlItem,
SegmentedControlItemInput,
} from '@vega-ui/react';
import { MonitorIcon, MoonIcon, SunIcon } from '@vega-ui/icons';
export default function IconOnlyModeSelector() {
return (
<SegmentedControl name='appearance' defaultValue='system' size='sm' role='radiogroup' aria-label='Appearance'>Edge: numeric values
Item values can be numbers, but event.currentTarget.value is always a string, so convert it with Number before storing it. The number of grid columns follows the selection.
import { useState } from 'react';
import {
Label,
SegmentedControl,
SegmentedControlIndicator,
SegmentedControlItem,
SegmentedControlItemInput,
} from '@vega-ui/react';
const columnOptions = [1, 2, 3, 4];
const photos = ['Harbor', 'Dunes', 'Glacier', 'Canyon', 'Forest', 'Delta', 'Reef', 'Ridge'];
Anatomy
Overview
SegmentedControl is a compound component for always-visible mutually exclusive options with a shared indicator, item-level native radio inputs, and a CSS-anchored selected-state indicator.
Required Parts
SegmentedControl
Required. Owns the group name, the controlled value or the initial defaultValue, size, variant, and disabled state.
SegmentedControlItem
Required. Renders one selectable segment and its visible content.
SegmentedControlIndicator
Required for the animated selected-state surface.
SegmentedControlItemInput
Required inside every item. The visually hidden native radio: focus, arrow keys, selection, the selected-segment style and form value come from it.
Optional Parts
There are no optional parts. Name the group with role='radiogroup' and aria-label or aria-labelledby on the root.
Composition Order
SegmentedControlSegmentedControlItemSegmentedControlItemInput- visible item content
SegmentedControlIndicator
Valid Composition Patterns
A group with text segments named by a visible label through aria-labelledby, and an icon-only group where every SegmentedControlItemInput gets its own aria-label.
import {
Icon,
Label,
SegmentedControl,
SegmentedControlIndicator,
SegmentedControlItem,
SegmentedControlItemInput,
} from '@vega-ui/react';
import { LayoutGridIcon, ListIcon } from '@vega-ui/icons';
export default function ValidCompositionPatterns() {
return (Invalid Composition Patterns
Items without a shared root
This breaks selected-state context and indicator positioning.
Hidden inputs omitted in form-driven flows
The control may look right while losing native radio-group semantics.
Large or highly variable option sets
Segmented control is for short, always-visible exclusive choices, not long menus.