Tabs & segmented controls
Tabs change what you are looking at; a segmented control changes how the same content is shown.
Tabs
Code
const TABS = [
{ id: "overview", label: "Overview" },
{ id: "constraints", label: "Constraints", badge: 5 },
{ id: "build", label: "Build" },
{ id: "history", label: "History", disabled: true, disabledReason: "Coming with saved projects" }
]
<GdTabs v-model="tab" :tabs="TABS" label="Parcel sections" />Segmented control
Code
const MODES = [
{ id: "explore", label: "Explore uses" },
{ id: "specific", label: "Something specific" }
]
<GdSegmented v-model="mode" :options="MODES" label="Search mode" />Choice list
Code
const AUDIENCES = [
{ id: "owner", eyebrow: "Homeowner", title: "What can I build on my own block?", meta: "lots assessed in the region", metaLead: "1.42 m" },
{ id: "buyer", eyebrow: "Buyer", title: "Is this listing worth what they are asking for it?", meta: "median days on market", metaLead: "38" }
]
<GdChoiceList v-model="audience" :options="AUDIENCES" label="Who you are" />API
GdTabs
Props
modelValuerequiredstring—The active tab's `id`, as `v-model`. Required — a tablist with nothing selected has no roving tabindex to give.
tabsrequired{ id: string; label: string; badge?: number; disabled?: boolean; disabledReason?: string }[]—The list. `badge` renders a count that goes accent on the active tab; `disabled` renders the tab visibly inert, and `disabledReason` becomes its `title`.
labelrequiredstring—The accessible name for the tablist. A group of tabs with no name is an unlabelled region.
Events
update:modelValuestringThe newly selected tab's `id`. Emitted from a click, an arrow key, Home or End — never from a disabled tab. GdTabs has no slots: it owns the tab list, and the panels are the caller's.
GdSegmented
Props
modelValuerequiredstring—The chosen option's `id`, as `v-model`.
optionsrequired{ id: string; label: string }[]—Two or three of them. Past four the segments get too narrow to label honestly.
labelrequiredstring—The accessible name for the group. It is a `radiogroup`, so this is what a screen reader reads before "2 of 3".
Events
update:modelValuestringThe chosen option's `id`. No slots — the options are data.
GdChoiceList
Props
modelValuerequiredstring—The chosen option's `id`, as `v-model`. Required for the same reason it is on GdTabs — a tablist with nothing selected has no roving tabindex to give.
optionsrequired{ id: string; eyebrow?: string; title: string; meta?: string; metaLead?: string; disabled?: boolean }[]—The choices. `title` WRAPS, which is the whole reason this exists rather than a row of tabs; `metaLead` is the leading fragment of `meta`, set in the mono face and full ink, for the figure in "1,240 lots assessed".
labelrequiredstring—The accessible name for the list. It announces as a tablist, so this is what is read before "2 of 4".
controlsstring—The `id` of the panel these choices control, written onto each option's `aria-controls`.
progressMsnumber | nullnullThe countdown rule drawn on the SELECTED option, in milliseconds. `null` draws nothing — a list that is not advancing must not imply that it is. The component does not own the timer; see Usage.
Events
update:modelValuestringThe newly chosen option's `id`. From a click, an arrow key, Home or End — never from a disabled option.
Usage Guidelines
- Use tabs when each option reveals a different panel of content — Overview, Constraints, Build.
- Use a segmented control when the content stays and only the lens changes.
- Use a select instead past four options. The segments get too narrow to label honestly and start being truncated.
- Disable a tab rather than removing it when the section is coming, and give it a
disabledReason. - Always pass
label. It is the accessible name of the group, and it is required on both components. - Render the panels yourself.
GdTabsowns the tab list and nothing else — give each panelid="{tabId}-panel"to match thearia-controlsit writes. - Reach for
GdChoiceListwhen an option needs more than a label — a question that wraps to two lines, a figure under it. Same contract, same composable, different shape. - Keep the auto-advance timer yourself.
GdChoiceListdraws the countdown you pass inprogressMsand owns nothing else about it: when to start, whether a hover pauses it and whether the first click ends it for good are policy about one page, not about a list of choices.
Behavior & Anatomy
Which control
Reach for tabs when each option reveals a different panel of content — Overview, Constraints, Build. Reach for a segmented control when the content stays and only the lens changes.
The underline lives on the tab and is transparent when inactive, so selecting a tab does not change its height by 2px and shift the row beneath it.
Why the segmented control is not a second set of tabs
Two or three options. Past four it is a select — the segments get too narrow to label honestly and start being truncated.
It carries radio semantics rather than tab semantics: it picks a value, it does not reveal a panel, and a screen reader should say "2 of 3".