v154 · scroll-marker-group · migration

The ARIA tabs migration

On the left, the widget every design system carries: a WAI-ARIA tabs implementation with roving tabindex, an arrow-key handler, aria-selected bookkeeping and panel hiding — all of it script, all of it working, all of it yours to maintain. On the right, the same interface where scroll-marker-group: before tabs makes the browser responsible for every one of those jobs.

Same widget, two owners

Hand-rolled ARIA (works everywhere)

64 lines of markup + script on this page. Use arrow keys on the tabs.

Ticket #4021 — the browser knows nothing about these tabs; every behaviour below is scripted.

CSS tabs mode (Chrome 154)

Zero ARIA attributes, zero tab-switching script.

Ticket #4021 — the strip above is a ::scroll-marker-group; in tabs mode the browser assigns tablist/tab/tabpanel roles itself.

Three comments, one status change. No hidden toggling: inactive panels are pruned from the accessibility tree by the mode.

Two attachments. Roving focus is the focusgroup's job now, not a keydown handler's.

The CSS version, in full

.css-tabs {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-marker-group: before tabs;
}
.css-tabs > div { min-width: 100%; scroll-snap-align: center; }
.css-tabs > div::scroll-marker { content: attr(data-label); }
.css-tabs > div::scroll-marker:target-current { background: black; color: white; }
What the one keyword deletes
jobhand-rolled ARIA versiontabs mode
rolesrole="tablist" / "tab" / "tabpanel" on 7 elementsimplicit from the mode
selection statearia-selected toggled in script:target-current
roving tabindextabindex="-1" juggling in a keydown handlerfocusgroup behaviour of the mode
arrow keysArrowLeft/ArrowRight listener, wrap-around logicbuilt in
hiding inactive panelshidden attribute toggled per switchaccessibility-tree pruning
focus into paneltabindex="0" on each panelmarker is a focus-navigation-scope owner

see also