Skip to content
v2.5.0
English
Русский
Documentation

SegmentedControl

SegmentedControl is a compound component for short, always-visible exclusive option sets, with item-level native radio inputs and an animated selected-state indicator.
segmented-control.tsx · 16 lines
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
Tested in
19 tests

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

  • SegmentedControlProps
  • SegmentedControlItemProps
  • SegmentedControlIndicatorProps
  • SegmentedControlItemInputProps
  • SegmentedControlSize
  • SegmentedControlVariant

Minimal Composition

minimal-composition.tsx · 22 lines
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 owner
  • SegmentedControlItem: visible selectable segment
  • SegmentedControlItemInput: native radio inside every item — focus, arrow keys, selection and form value
  • SegmentedControlIndicator: selected-state surface

Optional Parts

  • None

Composition Order

  1. SegmentedControl
  2. SegmentedControlItem
  3. SegmentedControlItemInput
  4. visible item content
  5. 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 Select when the option set is no longer small and always-visible.
  • If each option needs title, description, or card-like content, SegmentedControl is 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.

view-range-selector.tsx · 30 lines
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.

parent-owned-selected-segment.tsx · 49 lines
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.

native-radio-group-participation.tsx · 50 lines
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.

exclusive-selector-inside-overlays.tsx · 74 lines
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.

required-visible-choice-without-a-selection.tsx · 63 lines
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.

selected-disabled-control.tsx · 56 lines
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.

icon-only-mode-selector.tsx · 28 lines
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.

numeric-values.tsx · 56 lines
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

  1. SegmentedControl
  2. SegmentedControlItem
  3. SegmentedControlItemInput
  4. visible item content
  5. 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.

valid-composition-patterns.tsx · 45 lines
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.