A few options in one well; a sheet glides to the current one.
On this page
A segmented control is a well holding a few mutually exclusive options; the current one rises as a sheet. The track's radius is the option's radius plus its 4px inset, so the corners stay concentric, and each size is exactly as tall as the matching button.
Each option is a segmented-item. When the choice is a value, make it a label around a native radio with segmented-input, so the group keeps arrow-key navigation and works in forms:
Where the browser supports CSS anchor positioning, the raised sheet is one thumb that travels to the current option instead of blinking from one to the next. The current option names itself as an anchor, the track scopes that name to itself with anchor-scope, and a single pseudo-element is drawn on the anchor's edges; when the choice changes, those edges transition. No script measures anything, and several segmented controls on a page do not interfere. Elsewhere, and with reduced motion, the current option carries the sheet itself and changes at once.
/* * Segmented control: a well holding a few options; the current one rises as a sheet. * The track's padding is the inset around each option, and its radius is the option's radius plus * that inset, so the corners stay concentric. Total heights match buttons: 32, 38 and 44px. * With anchor positioning the sheet is one thumb that glides to the current option; elsewhere the * current option carries the sheet itself. */@layer components {.segmented{--segmented-inset:4px;--segmented-item-height:1.875rem;--segmented-item-radius:var(--radius-control-sm);--segmented-font-size:.8125rem;--icon-size:.875rem;display:inline-flex;flex-wrap:wrap;gap:2px;max-width:100%;padding:var(--segmented-inset);border-radius:calc(var(--segmented-item-radius)*var(--corner-scale)+var(--segmented-inset));corner-shape:var(--corner-shape);background:var(--color-surface-inset);box-shadow:var(--shadow-pressed);&.segmented-sm{--segmented-inset:3px;--segmented-item-height:1.625rem;--segmented-item-radius:var(--radius-chip);--segmented-font-size:.75rem;--icon-size:.75rem;}&.segmented-lg{--segmented-item-height:2.25rem;--segmented-item-radius:var(--radius-control);--segmented-font-size:.875rem;--icon-size:1rem;}@media(forced-colors:active){outline:1pxsolid CanvasText;outline-offset:-1px;}}/* An option: a button with aria-pressed, a link with aria-current, or a label around a .segmented-input radio. */.segmented-item{position:relative;display:inline-flex;flex:10auto;align-items:center;justify-content:center;gap:.375rem;min-width:3.5rem;min-height:var(--segmented-item-height);margin:0;padding:0.75rem;border:0;border-radius:calc(var(--segmented-item-radius)*var(--corner-scale));corner-shape:var(--corner-shape);background:transparent;color:var(--color-text-muted);font:var(--font-weight-control)var(--segmented-font-size)/1var(--font-family-sans);font-feature-settings:inherit;text-decoration:none;white-space:nowrap;appearance:none;user-select:none;-webkit-user-select:none;-webkit-tap-highlight-color:transparent;cursor:pointer;touch-action:manipulation;/* The raised face swaps instantly; only ink and depth ease, so no frame shows a half-transparent face. */transition-property:color, box-shadow;transition-duration:var(--duration-quick);transition-timing-function:var(--easing-glide);@media(hover:hover){&:hover{color:var(--color-text-strong);}}/* Pressed: the strong ink, as under the pointer, so a tap is answered too. */&:active{color:var(--color-text-strong);}&:is([aria-pressed="true"],[aria-selected="true"],[aria-current="page"],:has(.segmented-input:checked)){@apply--surface-sheet;color:var(--color-text-strong);box-shadow:var(--shadow-raised);}&:is(:focus-visible,:has(.segmented-input:focus-visible)){@apply--focus-ring;z-index:1;}&:is(:disabled,[aria-disabled="true"],:has(.segmented-input:disabled)){color:var(--color-text-disabled);cursor:not-allowed;}/* On touch the track is 44px at every size, so it still matches the button beside it. */@media(pointer:coarse){min-height:max(var(--segmented-item-height),calc(2.75rem-var(--segmented-inset)*2));}@media(forced-colors:active){&:is([aria-pressed="true"],[aria-selected="true"],[aria-current="page"],:has(.segmented-input:checked)){outline:2pxsolid Highlight;outline-offset:-2px;}}}/* The radio inside an option covers it invisibly, so the label is the target and the radio keeps its semantics. */.segmented-input{position:absolute;inset:0;width:100%;height:100%;margin:0;padding:0;border:0;border-radius:inherit;opacity:0;appearance:none;cursor:inherit;}/* * The gliding thumb. The current option names itself as an anchor; the track scopes that name to itself and * draws one sheet on the anchor's edges. When the choice changes the edges transition, so the sheet travels * between options instead of blinking from one to the next. */@supports(anchor-scope: --segmented-thumb){.segmented{position:relative;anchor-scope: --segmented-thumb;/* :has() cannot nest, so the track asks for a current option or a checked radio directly. */&:has(.segmented-item:is([aria-pressed="true"],[aria-selected="true"],[aria-current="page"]),.segmented-input:checked)::before{content:'';position:absolute;position-anchor: --segmented-thumb;inset:anchor(top)anchor(right)anchor(bottom)anchor(left);border-radius:calc(var(--segmented-item-radius)*var(--corner-scale));corner-shape:var(--corner-shape);@apply--surface-sheet;box-shadow:var(--shadow-raised);pointer-events:none;transition-property:top,right,bottom,left;transition-duration:var(--duration-base);transition-timing-function:var(--easing-glide);@media(prefers-reduced-motion:reduce){transition:none;}}/* In forced colors the current option draws its own outline; the thumb has nothing to add. */@media(forced-colors:active){&::before{display:none;}}}.segmented-item:is([aria-pressed="true"],[aria-selected="true"],[aria-current="page"],:has(.segmented-input:checked)){anchor-name: --segmented-thumb;background:none;box-shadow:none;}}}