GodUIGodUI
129Follow on X

Anatomy of the Segmented Control

How a tab-like control renders one pill that lives inside whichever tab is active, and lets Framer's layoutId spring it between segments instead of cross-fading.

01/03One track, one pill
Track
role="tablist": bg-muted, rounded, p-1
Segment buttons
one role="tab" per option
Sliding pill
bg-background, absolute inset-0 of the active tab
tsx
<div role="tablist" className="inline-flex items-center rounded-lg border bg-muted p-1">{options.map((opt) => {  const active = opt.value === value;  return (    <button key={opt.value} role="tab" aria-selected={active} onClick={() => select(opt.value)}>      {active && (        <motion.span          layoutId={layoutId}          className="absolute inset-0 rounded-md bg-background shadow-sm"        />      )}      <span className="relative">{opt.label}</span>    </button>  );})}</div>
01

One track, one pill

A segmented control could be built as N buttons each tracking their own "am I selected" background. GodUI builds it as N buttons and exactly one pill, which is only ever rendered inside the currently active tab. Switching segments moves that one element from the old button to the new one.

The track is a role="tablist" with bg-muted, rounded corners, and a little padding; each option is a role="tab" button. The pill itself is absolute inset-0 of whichever button is active. It is conditionally rendered, so it only ever exists in one place in the DOM at a time:

Because the pill is a child of the button, not the track, it's positioned with plain inset-0. No coordinate math is needed to find the tab it sits under; the tab's own layout does that for free.

02/03layoutId slide

layoutId spring: stiffness 520, damping 32

tsx
let pillSeed = 0;const layoutId = React.useMemo(() => `segmented-pill-${pillSeed++}`, []);<motion.spanlayoutId={layoutId}transition={{ type: "spring", stiffness: 520, damping: 32 }}className="absolute inset-0 rounded-md bg-background shadow-sm"/>
02

layoutId slide

When value changes, the pill unmounts from the old button and mounts in the new one. Normally that would be two separate elements popping in and out. layoutId tells Framer Motion these two mounts are "the same" element: it measures the old box, measures the new box, and animates a transform between them instead of letting either one appear:

The per-instance pillSeed counter matters more than it looks: layoutId is matched globally across the page, so two SegmentedControls rendered at once must never share an id, or Framer would try to slide a pill between them. Incrementing a module-level counter on every mount guarantees each instance gets its own namespace without a prop the consumer has to supply.

There's no separate "pressed" boolean to track. Every tab button carries active:scale-[0.97], a plain CSS active-state utility rather than a Framer whileTap, so the dip is instantaneous on both mouse and touch and costs nothing while the control is idle:

tsx
 
className="... active:scale-[0.97] disabled:pointer-events-none disabled:opacity-40"
03/03Result

Viewing week range

03

The result

Two lines of state (value, and a layoutId that only exists to keep instances apart) plus one conditionally rendered pill. The slide is Framer measuring two boxes and animating between them; the control tracks no motion of its own.

Accessibility

The track is a tablist/tab pair. aria-selected mirrors active directly off the same boolean that decides whether to render the pill, so the visual state and the accessible state can never drift apart. Focus is native <button> focus (focus-visible:ring-2, offset for contrast against the muted track); there's no roving-tabindex manager, because segmented controls are typically small enough that browser tab order is enough. useReducedMotion collapses the pill's spring to { duration: 0 }, so the pill jumps to the new tab without sliding.

Motion Score

Segmented ControlCC: Paint-triggering
SscaleScale spring / press
SopacityFade / cross-fade
Each property is graded by how the browser runs it, from S (composited off the main thread) down to F (layout thrashing); the component takes the worst. MotionScore methodology →