SvSegmented

A segmented control: a compact row of mutually-exclusive options in a shared track - the modern alternative to a small radio group or a tab strip for a setting.

SvSegmented renders single-select options as pill buttons in one track. It reuses the radio-group core, so it is a proper role="radiogroup" with arrow-key roaming and Space/Enter selection, and it themes from the shared --sg-* tokens. Reach for it for view switchers, range pickers, and small enum settings.

Related: SvRadioGroup · SvButtonGroup · SvTabs

Installation

Add it with the CLI - this drops a ready-to-edit SvSegmented starter into your app:

Prefer to see it first? npx @svgrid/ui try segmented opens it in a throwaway sandbox - no project needed.

Or install the package and import it directly. SvSegmented ships free in @svgrid/grid (dependency-free):

The examples on this page import from @svgrid/grid:

<script lang="ts">
  import { SvSegmented } from '@svgrid/grid'

  // The bound value behind each example below.
  let align = $state('')
  let period = $state('')
</script>
import { SvSegmented } from '@svgrid/grid'

Example

Open the live example: Segmented control (Buttons & Toggles)

<script lang="ts">
  import { SvSegmented } from '@svgrid/grid'
  let view = $state('board')
</script>

<SvSegmented
  bind:value={view}
  options={[
    { value: 'board', label: 'Board' },
    { value: 'table', label: 'Table' },
    { value: 'calendar', label: 'Calendar' },
  ]}
/>

Props

Prop Type Default Description
options SegmentedOption[] - The choices: { value; label; disabled?; icon? }.
value string | number | null null Selected value (bindable).
onChange (value) => void - Fires when the selection changes.
size sm | md | lg md Control height and font size.
block boolean false Stretch full width and split options evenly.
disabled boolean false Disable the whole control.
label / hint / error / required - - Optional SvField chrome around the control.
dir EditorDir (ltr | rtl | auto) - Text direction.
name string - Emit a hidden input carrying the value for form posts.

SegmentedOption is { value; label; disabled?; icon?: Snippet }.

Examples

Full width with icons

Set block to split the track evenly, and give options an icon snippet:

<SvSegmented block bind:value={align} options={[
  { value: 'left', label: 'Left', icon: leftIcon },
  { value: 'center', label: 'Center', icon: centerIcon },
  { value: 'right', label: 'Right', icon: rightIcon },
]} />

As a field

Pass label / hint / error to wrap it in the shared field chrome:

<SvSegmented label="Billing period" bind:value={period} required
  error={period ? undefined : 'Pick a period'}
  options={[{ value: 'monthly', label: 'Monthly' }, { value: 'yearly', label: 'Yearly' }]} />

Accessibility

Sizes

Every control takes the same three sizes, so a dense toolbar and a roomy form can share components.

<script lang="ts">
  import { SvSegmented } from '@svgrid/grid'

  let view = $state('')
</script>

<SvSegmented bind:value={view} size="sm" />
<SvSegmented bind:value={view} size="md" />
<SvSegmented bind:value={view} size="lg" />

In a form

The shared field props behave the same on every editor: label names it, hint explains it, and error plus invalid mark it - which is why a validated form does not need per-component handling.

<script lang="ts">
  import { SvSegmented } from '@svgrid/grid'

  let view = $state('')
</script>

<SvSegmented
  bind:value={view}
  label="Label"
  hint="A short hint"
  required
/>

<SvSegmented
  bind:value={view}
  label="Label"
  error="Something is wrong"
  invalid
/>

See also