Skip to content

HabSegmented ​

A compact choice among two to four short options, drawn as one control — a mode, or a two-way switch inline in a row.

A mode that changes what is shown beside it, and a two-way choice inline in a row.
vue
<HabSegmented
	v-model="kind"
	label="Case kind"
	:options="[
		{ value: 'set', label: 'Set a value' },
		{ value: 'calc', label: 'Calculate' },
	]"
/>

Which control ​

You are choosing…Use
a mode that changes what is shown nearby, in a few words eachHabSegmented
between sections of a pageHabTabs
an answer to a form question, where each option needs a help lineHabRadio
one of five or moreHabSelect

One option is always chosen — there is no "none". If "none" is a real answer, it is an option with that label.

Geometry ​

A 2px track around 24px segments makes the control --hab-control-sm (28), so it sits flush beside compact buttons and fields. The 2px is the track reading as the control's own edge rather than a gap — the same reason as the gutter between raised list strips. The segment radius is the control radius less 2px, so the corners stay concentric.

Neither was in the design source when the component was built; both were added to Core on 2026-09-29 — the 2px track to its exception table, the derived radius to its radius rule. The chosen segment's shadow is still open there. See design-source.md.

fill stretches the track to its container and shares the width equally between the segments — for a control that is a row of its own in a narrow panel, where a short track left-aligned leaves the row looking unfinished. Inline in a sentence or a toolbar, leave it off.

Every segment is font-medium, chosen or not. Weight changes width, so a segment that went bold when chosen would nudge its neighbours on every click; the choice is carried by the fill, the shadow and the ink.

The chosen segment looks different in each theme.

  • Light: a raised white pill with a faint accent outline. bg-surface with shadow-elev-1 on the contained track, and a 1px inset ring in accent-on at 30%. This is below the 3:1 contrast WCAG asks of a selected state (the outline is about 1.5:1, the pill 1.07:1 against the track), by decision: an inverted near-black pill and a solid accent ring both cleared it and were rejected as too heavy on a light page. The choice is carried by the raised pill and its shadow.
  • Dark: inverted, bg-fg-3 with text-surface: a mid grey pill, about 4.5:1 against the track. White, and the lighter fg-2 grey, were too bright on a dark page. The track is one step up, contained-2, and the idle labels are fg-2, because fg-3 does not hold 4.5:1 on that step. tests/contrast.spec.ts holds every pair.

A disabled control keeps the quiet fill and no ring in both themes.

The track itself is not held to 3:1 against what it sits on. WCAG 1.4.11 asks for a boundary only where it is needed to identify the control, and the labels do that; outlining the track would box the control on every surface.

Read-only ​

readonly shows the choice to be read: every option still drawn, the chosen one marked exactly as before, at full contrast. A click, an arrow key or Space changes nothing — arrows still move focus between the segments, so each can be read out — and the group is announced as read-only. Unlike disabled, which says "not now", dims the whole control and outranks it. See HabReadonly for the difference, and for making a whole region read-only at once.

Read-only beside disabled: the answer at full contrast, against one dimmed
vue
<HabSegmented v-model="kind" label="Case kind" readonly :options="kinds" />

Accessibility ​

  • It is a radio group: one tab stop, arrow keys move between segments, and the selection follows focus, as a native radio group's does. A screen reader announces the group's name and "2 of 2".
  • Give it a label naming the question ("Case kind", "Combine conditions"), or an aria-label. Habitus warns when it has neither. The segments name themselves.
  • A segment keeps the browser's native focus indicator.

API ​

Props ​

PropTypeDefaultDescription
disabledbooleanfalseEvery segment at once.
fillbooleanfalseThe track takes its container's width and the segments share it equally, instead of the control sitting at its content width. For a control that is a row of its own in a narrow panel — an inspector's AND/OR — where a short track left-aligned leaves the rest of the row empty and reads as unfinished. Off by default: inline in a sentence or a toolbar, the control should be as wide as its words. Like HabTabs' fill.
labelstringundefinedAccessible name for the group — what is being chosen ("Case kind", "Combine conditions"). The segments name themselves; this names the question.
modelValuestringundefinedThe chosen option's value, for v-model.
options(string | HabSegmentedOption)[][]Two to four short options, or plain strings where value and label are the same.
readonlybooleanundefinedThe choice is shown to be read, not changed: every option still drawn and the chosen one marked as before, but a click, an arrow key or Space changes nothing. Still focusable and announced as read-only — unlike disabled, which says "not now", is dimmed and outranks it. Unset, it follows a surrounding HabReadonly; false keeps this group editable inside one.

Events ​

EventPayloadDescription
update:modelValuevalue: string—

Proprietary — internal use only.