Desktop components

Segmented controls

A segmented control is a linear set of two or more segments, each of which functions as a button. One segment is always selected. Use it to switch between views or filter a collection, not to trigger actions.

When to use it

Use a segmented control when people pick one of two to five views that swap in place. Day, Week, Month. List, Grid.

Use tabs when the sections carry their own content areas. Use radios when the choice is part of a form and takes effect on submit. Avoid more than five segments on desktop; labels truncate and targets shrink.

Anatomy

PartValue
Track24px tall, 6px radius, --p-segmented-track background (black 10%, white 9% in dark mode)
SegmentsEqual width, 54px minimum including padding, 10px horizontal padding
Label13px, weight 510. Unselected --p-text-primary, selected white
Selected pillFull height, no inset, 6px radius, flat --p-control-accent fill
Separators1px, black at 25% (white at 25% in dark mode), 14px tall, hidden next to the selection

Basic use

<div class="p-segmented" role="group" aria-label="Calendar view">
<label class="p-segment"><input type="radio" name="view" checked /><span>Day</span></label>
<label class="p-segment"><input type="radio" name="view" /><span>Week</span></label>
<label class="p-segment"><input type="radio" name="view" /><span>Month</span></label>
</div>

It is a radio group wearing a control. Native inputs carry the state, so keyboard navigation, form submission, and screen reader semantics come free. No JavaScript.

Separators sit between segments and disappear next to the selection, exactly like macOS. Pressing an unselected segment tints it with --p-fill-tertiary before the selection moves.

Sizes

Five sizes share the macOS control ladder with buttons and pop-up buttons. The two largest turn into capsules: the track and the selected pill both round fully.

<div class="p-segmented p-segmented-large" role="group" aria-label="View">
<label class="p-segment"><input type="radio" name="view" checked /><span>Day</span></label>
<label class="p-segment"><input type="radio" name="view" /><span>Week</span></label>
</div>
SizeClassHeightRadiusLabelPaddingSeparator
Mini.p-segmented-mini16px4px10px6px10px
Small.p-segmented-small20px5px11px8px12px
Regular(default)24px6px13px10px14px
Large.p-segmented-large28pxcapsule13px12px18px
Extra large.p-segmented-xlarge36pxcapsule13px14px20px

Every label is weight 510. Mini and small are for dense inspectors and toolbars; they sit well below the 44px touch minimum, so keep them to pointer interfaces.

Gray style

<div class="p-segmented p-segmented-gray" role="group" aria-label="Density">
<label class="p-segment"><input type="radio" name="sc-density" checked /><span>Compact</span></label>
<label class="p-segment"><input type="radio" name="sc-density" /><span>Cozy</span></label>
<label class="p-segment"><input type="radio" name="sc-density" /><span>Roomy</span></label>
</div>

.p-segmented-gray swaps the accent pill for a quiet gray pill (black 10% over the track, 19% pressed) and keeps the label color. Consider it for secondary controls that should not compete with the view’s primary action.

Changing the color

<div class="p-segmented p-sys-purple" style="background: var(--p-segmented-track);" role="group" aria-label="Purple example">
<label class="p-segment"><input type="radio" name="sc-tint1" checked /><span>One</span></label>
<label class="p-segment"><input type="radio" name="sc-tint1" /><span>Two</span></label>
</div>
<div class="p-segmented p-sys-green" style="background: var(--p-segmented-track);" role="group" aria-label="Green example">
<label class="p-segment"><input type="radio" name="sc-tint2" checked /><span>One</span></label>
<label class="p-segment"><input type="radio" name="sc-tint2" /><span>Two</span></label>
</div>

Add a .p-sys-* class to retint the selected pill through --p-color-current, the same mechanism as buttons and form controls. The color class paints its swatch background on the track, so restate the track fill with an inline style or a utility class when you tint.

States

Dark mode

The track flips to white at 9%, the gray pill to white at 10%, separators to white at 25%, and the accent pill picks up the dark accent (#0091FF) from colors.css. No extra setup.

The slide

macOS slides the pill between segments, and so does this control, still without JavaScript. The track watches its own radios through :has(), derives the selected index and the segment count, and slides a single pill to index / count of its width over 0.25s. Segments are equal-width grid columns, so the position math is exact at any control width. Browsers without :has() fall back to a 0.15s crossfade between per-segment pills; the markup is identical either way.

Migration from v1

v1v2
.p-segmented-controls + .active.p-segmented with radio inputs
.p-segmented-controls-alt.p-segmented-gray (closest)
.p-segmented-grey.p-segmented-gray
.p-segmented-radius, -outline, -outline-alt.p-segmented-large or -xlarge for a capsule; no outline equivalent

The v1 classes stay for backwards compatibility and will be removed in a future major. The v2 control needs no .active class management in JavaScript; the checked radio is the state.

Copied