Skip to content
weftGitHub

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/warp

Usage

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, so label names the group even though it isn't drawn.

API Reference

PropTypeDefaultDescription
labelstring—The group's accessible name. Not drawn.
valueV—The value that is on.
optionsSegmentedOption<V>[]—The options: { value, label, icon, count, disabled }.
size"sm" | "md"—sm or md.
namestring—The form field name. Made up for you when left out.
EventTypeDefaultDescription
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