WebCraft

:has() + translate

A segmented control with a sliding indicator

CaveatNever size the indicator in pixels. Hardcode a width and a shift and the pill lines up until someone renames an option — then it sits half over the next label, with no error to tell you. Equal columns and a shift of 100% of itself need no measuring.

FallbackKeep real radios underneath: rebuilt from divs it loses arrow-key navigation and the announced group. Without :has() the pill just stays on the first option and the radios still work.

CSS
.seg {
  display: inline-grid;
  grid-auto-flow: column;
  /* equal, whatever the label says */
  grid-auto-columns: 1fr;
}
.seg::after {
  width: calc((100% - 8px) / 3);
  transition: translate .28s;
}
/* one column, measured by the browser */
.seg:has(input:nth-of-type(2):checked)::after {
  translate: 100% 0;
}

Published