A segmented control in CSS, with the sliding highlight
A segmented control switches between a few views. How to build one on radio inputs so the keyboard works, with a sliding highlight and the sizes Mac apps use.
A segmented control is a row of options where exactly one is selected, with a highlight that slides to the choice. It is native on Apple platforms and often faked on the web with buttons that break keyboard use.
Start from radios
<fieldset class="seg" style="--count: 3">
<legend class="visually-hidden">View</legend>
<input type="radio" name="view" id="v1" checked><label for="v1">List</label>
<input type="radio" name="view" id="v2"><label for="v2">Grid</label>
<input type="radio" name="view" id="v3"><label for="v3">Columns</label>
<span class="thumb" aria-hidden="true"></span>
</fieldset>Radios give you arrow-key movement between options and a correct announcement to screen readers, with no script.
The sliding highlight
.seg { position: relative; display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
padding: 2px; border-radius: 7px; background: var(--fill-tertiary); border: 0; }
.seg input { position: absolute; opacity: 0; }
.seg label { position: relative; z-index: 1; padding: 3px 10px; font-size: 12px;
text-align: center; cursor: default; }
.thumb { position: absolute; top: 2px; bottom: 2px; left: 2px;
width: calc((100% - 4px) / var(--count)); border-radius: 5px;
background: var(--fill-selected); transition: transform 200ms ease;
transform: translateX(calc(var(--i, 0) * 100%)); }
.seg:has(#v2:checked) { --i: 1; }
.seg:has(#v3:checked) { --i: 2; }
.seg input:focus-visible + label { outline: 2px solid var(--accent); border-radius: 5px; }Sizes
In a Mac app, segmented controls are small: around 22 to 24 pixels tall with 12 to 13px labels. On a touch website, make them 36 to 44 pixels tall. Using the web size in a desktop app is one of the tells covered in a native macOS look.
Motion
The slide should be short, around 200ms. Under reduced motion, remove the transition so it jumps; see prefers-reduced-motion.
The sliding segmented control is one of the app chrome recipes in macOS Native Dark UI, with the sidebar, search field and tinted destructive buttons beside it.
Questions
Should a segmented control use buttons or radio inputs?
Radio inputs, when it chooses one option from a few. They give you arrow-key navigation and the right semantics for free. Use a tab list pattern if it switches panels of content.
How do I animate the sliding highlight?
Draw one highlight element and move it with a transform based on the selected index. CSS custom properties, or :has() on the container, can set the index without JavaScript.
How many segments can it hold?
Two to five short labels. With more, or longer labels, a menu or tabs work better.