Segmented
A row of two to five short options where exactly one is on.
SourcePackage @weft-js/warpPlayground
Preview
tsx
import { state } from '@weft-js/core'import { Segmented } from '@weft-js/warp'export function Basic() { const v = state('week') return <Segmented label="Period" value={v.value} options={PERIODS} onChange={next => (v.value = next)} />}const PERIODS = [{ value: 'day', label: 'Day' }, { value: 'week', label: 'Week' }, { value: 'month', label: 'Month' }]Installation
npm install @weft-js/warpUsage
tsx
import { Segmented } from '@weft-js/warp'Sizes
Use size to pick sm or md.
tsx
import { state } from '@weft-js/core'import { Segmented } from '@weft-js/warp'export function Sizes() { const v = state('week') return ( <> <Segmented label="Period" size="sm" value={v.value} options={PERIODS} onChange={next => (v.value = next)} /> <Segmented label="Period" value={v.value} options={PERIODS} onChange={next => (v.value = next)} /> </> )}const PERIODS = [{ value: 'day', label: 'Day' }, { value: 'week', label: 'Week' }, { value: 'month', label: 'Month' }]Icons
Give an option an icon to draw it beside its label.
tsx
import { state } from '@weft-js/core'import { Segmented } from '@weft-js/warp'import type { SegmentedOption } from '@weft-js/warp'export function Icons() { const v = state('list') return <Segmented label="View" value={v.value} options={VIEWS} onChange={next => (v.value = next)} />}const VIEWS: SegmentedOption<'list' | 'grid'>[] = [{ value: 'list', label: 'List', icon: 'list' }, { value: 'grid', label: 'Grid', icon: 'grid' }]A disabled option
Mark an option disabled to show it without letting it be picked.
tsx
import { state } from '@weft-js/core'import { Segmented } from '@weft-js/warp'export function DisabledOption() { const v = state('all') return <Segmented label="Logs" value={v.value} options={LOGS} onChange={next => (v.value = next)} />}const LOGS = [{ value: 'all', label: 'All' }, { value: 'info', label: 'Info' }, { value: 'trace', label: 'Trace', disabled: true }]Filter with counts
An option's count shows how many match.
tsx
import { derived, state } from '@weft-js/core'import { Row, Segmented, Stack, Text } from '@weft-js/warp'export function Deploys() { const show = state('all') const rows = derived(() => DEPLOYS.filter(d => show.value === 'all' || d.status === show.value).slice(0, 4)) return ( <Stack gap="group"> <Segmented label="Show deploys" value={show.value} options={DEPLOY_FILTERS} onChange={next => (show.value = next)} /> <Stack gap="tight"> {rows.value.map(d => <Row key={d.id} justify="between" gap="group"><Text>{d.project}</Text><Text subtle>{d.commit}</Text></Row>)} </Stack> </Stack> )}const DEPLOYS = [ { id: 'd-4821', project: 'storefront', branch: 'main', commit: '9f3ac21', message: 'Cache product images at the edge', author: 'Priya Raman', status: 'Live', minutes: 2, at: '2026-10-31T16:42' }, { id: 'd-4820', project: 'search-api', branch: 'ranking-v2', commit: '1be07d4', message: 'Rank exact matches first', author: 'Lena Fischer', status: 'Building', minutes: 6, at: '2026-10-31T16:10' }, { id: 'd-4819', project: 'mobile-bff', branch: 'main', commit: 'c44e190', message: 'Retry timed out upstream calls', author: 'Sam Okafor', status: 'Failed', minutes: 4, at: '2026-10-31T15:27' }, { id: 'd-4818', project: 'checkout', branch: 'main', commit: '70d2fa8', message: 'Add Apple Pay to the last step', author: 'Tomás Oliveira', status: 'Live', minutes: 3, at: '2026-10-31T14:03' }, { id: 'd-4817', project: 'edge-cache', branch: 'main', commit: 'e81b6c2', message: 'Lower the stale window to 30s', author: 'Jun Tanaka', status: 'Live', minutes: 1, at: '2026-10-31T11:55' }, { id: 'd-4816', project: 'billing', branch: 'main', commit: '2ad9e03', message: 'Prorate seats on plan change', author: 'Mara Lindqvist', status: 'Live', minutes: 3, at: '2026-10-29T17:20' },]const countOf = (status: string) => DEPLOYS.filter(d => d.status === status).lengthconst DEPLOY_FILTERS = [ { value: 'all', label: 'All', count: DEPLOYS.length }, { value: 'Live', label: 'Live', count: countOf('Live') }, { value: 'Failed', label: 'Failed', count: countOf('Failed') },]When to use
Use it for
- Switching a view or a period
- Options of a word or two
Reach for something else
- Tabs, when each option has its own panel of content
- RadioGroup, when the options need a sentence each
- Chip, when several can be on
Playground
Size
import { Segmented } from '@weft-js/warp'<Segmented value={v.value} options={PERIODS} onChange={next => (v.value = next)} label="Period" />Accessibility
- ←→
- Moves to the next option and picks it
- Tab
- Moves in and out
- Native radios in a
radiogroup, solabelnames the group even though it isn't drawn.
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | — | The group's accessible name. Not drawn. |
value | V | — | The value that is on. |
options | SegmentedOption<V>[] | — | The options: { value, label, icon, count, disabled }. |
size | "sm" | "md" | — | sm or md. |
name | string | — | The form field name. Made up for you when left out. |
| Event | Type | Default | Description |
|---|---|---|---|
onChange | (v: V) => void | — | Called with the new value. |
Parts
Every element carries a stable name, so you can style it from your own CSS: [data-scope="segmented"] [data-part="indicator"]
indicatorsweepitemcount
RelatedTabsRadio groupToggleChip