Skip to main content
xriptxr

Segmented

Info:Controls
67

A single-select toggle bar: pick one of a few options from a connected button group.

segmented control toggle group button group single select pills

Live demo

live · @xtyle/astro

Segmented

Choices worth showing at once

View
Alignment
Theme

Sizes & disabled

Small
Medium (default)
Large
Locked

Structured options

Pass { value, label }[] when the label differs from the value the handler needs, or carries a comma or colon the shorthand can't survive.

Export

Per-option disabled & badges

A data-conditional view switch: report carries a count while text and log stay disabled until their data exists. Arrow keys skip the disabled segments, and the default selection lands on the first enabled one.

Run output

Per-option hover hints

When a short pill label needs context, give the option a title for a native hover hint. Here each source pill spells out its kind on hover, so a picker of terse names doesn't have to fall back to a bespoke tooltip-wrapped button set.

Source

Icon segments

Drop <Segment> children holding an <Icon> in place of text labels. Each carries a value and an accessible label; selection, roving focus, and the form value work exactly as with options. The option owns its name and tooltip (hover a segment), so the icon stays decorative. Drop the visible heading with aria-label for a bare toolbar bar, as in the Playback control.

Every tone

Accents

accent
accent-2
accent-3
accent-4
neutral

Statuses

success
info
warn
danger

Named hues

red
orange
yellow
green
blue
purple
brown
pink
cyan
gray
white
black

Segmented picks one option from a small, fixed set rendered as a connected button bar. It's the compact alternative to a radio group when the choices are few and worth showing at once.

It is a role="radiogroup" of role="radio" buttons with roving tabindex: the selected segment is the tab stop, arrow keys move and select with wraparound, and Home/End jump to the ends. Options are declared as a comma-separated options string (bare labels, or label:value pairs), or as a structured { value, label }[] when a label differs from its value or carries a comma or colon. For rich segments, drop in <Segment value="…" label="…"> children holding an icon or other markup instead of a text label; they win over options when both are present. It's form-associated; give it a name and the chosen value submits. Three sizes: sm, md, lg.

When to use

How this component composes with the rest of the set.

Reach for it over a radio group when there are two to five options worth showing at once: view switches, alignment, light/dark/auto.
For many options, a long list, or free text, use Select instead.
Pair with a form and a name so the chosen value submits like any field.
For icon-only or icon-plus-text segments, use <Segment> children instead of options, and give each an accessible label.

Props

9 props, straight from the manifest.

PropTypeDefaultBindingsDescription
value string
html svelte astro
The selected option's value. Defaults to the first option. Reflected and form-submitted.
options string | { value: string; label?: string; disabled?: boolean; badge?: string; title?: string }[]
html svelte astro
The options. The comma-string shorthand takes bare labels (`Day,Week`, the label is the value) or `label:value` pairs (`Left:start`). For labels that differ from their value or carry a comma or colon, pass a `{ value, label }[]` instead (the JS property in html / svelte, a JSON array attribute in Astro); a bare `string[]` works too. The structured form also takes a per-option `disabled` (a choice the current data can't offer, skipped by pointer and keyboard), a `badge` (trailing text like a count), and a `title` (a hover hint on the segment, for a short label that needs disambiguating context) per segment.
disabled boolean false
html svelte astro
Disables selection and mutes the bar.
size Size
sm md lg
md
html svelte astro
Control size: `sm`, `md`, or `lg`.
tone FullTone
accent neutral danger success warn info accent-2 accent-3 accent-4 red orange yellow green blue purple brown pink cyan gray white black
accent
html svelte astro
Color of the selected segment's fill. Any semantic role, accent variant, or named hue.
label string
html svelte astro
Visible label, also the accessible name via `aria-labelledby`.
labelledby string
html svelte astro
ID of an external element that names the group. Takes precedence over `label`.
aria-label string
html svelte astro
Names the group with no visible label, for a compact icon bar (a toolbar) where a heading would be noise. `labelledby` and a visible `label` both win over it.
name string
html svelte astro
Form field name; the selected value submits with the form.

Appearance

Sizes

sm

.xtyle-segmented--sm

Compact.

md

default
.xtyle-segmented

Default.

lg

.xtyle-segmented--lg

Large.

States

selected

.xtyle-segmented__option[aria-checked="true"]

The chosen segment: filled with the accent and its readable foreground. When the theme's --selection-cue resolves to marker (a high-contrast or redundant-cues algorithm), the selected segment gains a non-color check glyph so selection never rests on color alone.

hover

.xtyle-segmented__option:hover::after

Pointer over an unselected segment; an overlay paints the hover tint.

focus-visible

.xtyle-segmented__option:focus-visible

Keyboard focus on a segment: a token ring plus a transparent outline that becomes real in forced-colors mode.

disabled

.xtyle-segmented--disabled

The whole group disabled: muted, non-interactive.

Anatomy

The named parts that make up the component, with their selectors.

field

.xtyle-segmented-field

The wrapper stacking the optional label over the toggle bar.

--font-sans --space-2

segmented

.xtyle-segmented

The role="radiogroup" track holding the options.

--space-1 --bg-1 --border-thin --line-2 --radius-md

option

.xtyle-segmented__option

Each role="radio" segment; the selected one fills with the accent.

--fg-1 --fg-0 --text-sm --text-body --weight-medium --space-1 --space-2 --space-3 --space-4 --radius-sm --accent --accent-fg --state-hover --state-press

label

.xtyle-segmented__label

The optional visible label, referenced as the group's accessible name.

--fg-1 --text-sm

badge

.xtyle-segmented__badge

A per-option trailing count/status inside its segment; reach it at ::part(badge).

--space-1

Tokens & coverage

What the component consumes, checked live against what the algorithm produces.

Success:fully covered 67/67 consumed tokens produced default register: 305 tokens

Live coverage check against the xtyle-default register (derive(xtyleDefault, { anchors })coverComponent(manifest, register)). Every token this component consumes must be a key the algorithm produces.

--accent --accent-2 --accent-2-fg --accent-3 --accent-3-fg --accent-4 --accent-4-fg --accent-fg --bg-1 --black --black-fg --blue --blue-fg --border-normal --border-thick --border-thin --brown --brown-fg --cyan --cyan-fg --danger --danger-fg --duration-fast --ease-standard --fg-0 --fg-1 --fg-disabled --font-sans --gray --gray-fg --green --green-fg --info --info-fg --line-2 --neutral --neutral-fg --orange --orange-fg --pink --pink-fg --purple --purple-fg --radius-md --radius-sm --red --red-fg --ring --selection-cue --space-1 --space-2 --space-3 --space-4 --state-disabled --state-hover --state-press --success --success-fg --text-body --text-sm --warn --warn-fg --weight-medium --white --white-fg --yellow --yellow-fg

Slots

segment
html svelte astro

Rich-content segments: one [slot="segment"] child per option, each carrying value, an accessible label, and markup like an <Icon>. The element derives selection, roving tabindex, and the form value from them exactly as it would from options, and projects each child's live content into its radio. Children win over options when both are given. In Astro and Svelte, use the <Segment> component rather than writing slot="segment" yourself: <Segment> carries the marker on its own root element, whereas a bare slot="segment" child of <Segmented> is a routing directive Astro consumes, and the content is discarded.

Accessibility

The bar is role="radiogroup" and each segment role="radio" with aria-checked, never conveying selection by color alone.
Roving tabindex: the selected segment is the single tab stop; arrow keys move and select with wraparound, Home/End jump to the ends, click selects.
Requires an accessible name: labelledby wins, then a visible label, then a bare aria-label for an unlabeled icon bar; the binding warns at runtime when none is present.
An icon segment owns its option's name and tooltip: the radio carries aria-label and a title from the <Segment>'s label, so the projected icon stays decorative and hovering the option (not the glyph) shows what it selects.
Focus is shown with a token ring and a transparent outline the forced-colors base rule promotes to a real system outline.
Selection carries a non-color channel on demand: when the theme sets --selection-cue: marker, the selected segment gains a check glyph alongside the color fill, satisfying WCAG 1.4.1. High-contrast emits marker by default, and any algorithm can opt in via the cues knob.
The group-level disabled blocks selection, drops the segments out of the tab order, and mutes the bar; a per-option disabled does the same for one segment, so the arrow keys skip it and the default selection lands on the first enabled option.
A per-option badge is trailing text inside the segment (a count); it rides along with the option's label as part of that radio's accessible name.

Code

Labels, value pairs, and sizes

A view switch, an alignment control using label:value pairs, the compact size, and a disabled group.

<xtyle-segmented label="View" options="Day,Week,Month" value="Week"></xtyle-segmented>

<xtyle-segmented label="Align" options="Left:start,Center:center,Right:end" value="center"></xtyle-segmented>

<xtyle-segmented size="sm" label="Theme" options="Light,Dark,Auto" value="Auto"></xtyle-segmented>

<xtyle-segmented label="Locked" options="One,Two,Three" value="Two" disabled></xtyle-segmented>
<script lang="ts">
	import { Segmented } from "@xtyle/svelte";

	let view = $state("Week");
</script>

<Segmented label="View" options="Day,Week,Month" bind:value={view} />

<Segmented label="Align" options="Left:start,Center:center,Right:end" value="center" />

<Segmented size="sm" label="Theme" options="Light,Dark,Auto" value="Auto" />

<Segmented label="Locked" options="One,Two,Three" value="Two" disabled />
---
import { Segmented } from "@xtyle/astro";
---

<Segmented label="View" options="Day,Week,Month" value="Week" />

<Segmented label="Align" options="Left:start,Center:center,Right:end" value="center" />

<Segmented size="sm" label="Theme" options="Light,Dark,Auto" value="Auto" />

<Segmented label="Locked" options="One,Two,Three" value="Two" disabled />

Structured options

An export-format row built from a { value, label }[], showing Markdown / HTML / EPUB while the handler receives md / html / epub.

<xtyle-segmented id="fmt" label="Export"></xtyle-segmented>
<script>
	document.getElementById("fmt").options = [
		{ value: "md", label: "Markdown" },
		{ value: "html", label: "HTML" },
		{ value: "epub", label: "EPUB" },
	];
</script>
<script lang="ts">
	import { Segmented } from "@xtyle/svelte";

	const formats = [
		{ value: "md", label: "Markdown" },
		{ value: "html", label: "HTML" },
		{ value: "epub", label: "EPUB" },
	];
	let format = $state("md");
</script>

<Segmented label="Export" options={formats} bind:value={format} />
---
import { Segmented } from "@xtyle/astro";
const formats = [
	{ value: "md", label: "Markdown" },
	{ value: "html", label: "HTML" },
	{ value: "epub", label: "EPUB" },
];
---

<Segmented label="Export" options={formats} value="md" />

Per-option disabled and badges

A data-conditional view switch: a report segment carries a count while text / log stay disabled until their data exists, so the arrow keys skip them and the selection lands on the first enabled option.

<xtyle-segmented id="run-view" label="Run output"></xtyle-segmented>
<script>
	document.getElementById("run-view").options = [
		{ value: "report", label: "Report", badge: "128", title: "The parsed summary view" },
		{ value: "text", label: "Text", disabled: true, title: "Raw text (no run yet)" },
		{ value: "log", label: "Log", disabled: true, title: "Execution log (no run yet)" },
	];
</script>

Icon segments

Drop <Segment> children holding an <Icon> in place of text labels; each carries a value and an accessible label, and the bar keeps its selection, roving focus, and form value unchanged. The children project live, so framework-owned content stays reactive.

<xtyle-segmented label="Severity" value="info">
	<span slot="segment" value="info" label="Info"><xtyle-icon name="info"></xtyle-icon></span>
	<span slot="segment" value="warn" label="Warnings"><xtyle-icon name="warning"></xtyle-icon></span>
	<span slot="segment" value="error" label="Errors"><xtyle-icon name="error"></xtyle-icon></span>
</xtyle-segmented>
<script lang="ts">
	import { Segmented, Segment, Icon } from "@xtyle/svelte";

	let severity = $state("info");
</script>

<Segmented label="Severity" bind:value={severity}>
	<Segment value="info" label="Info"><Icon name="info" /></Segment>
	<Segment value="warn" label="Warnings"><Icon name="warning" /></Segment>
	<Segment value="error" label="Errors"><Icon name="error" /></Segment>
</Segmented>
---
import { Segmented, Segment, Icon } from "@xtyle/astro";
---

<Segmented label="Severity" value="info">
	<Segment value="info" label="Info"><Icon name="info" /></Segment>
	<Segment value="warn" label="Warnings"><Icon name="warning" /></Segment>
	<Segment value="error" label="Errors"><Icon name="error" /></Segment>
</Segmented>