v135 · css

Tabbed sections

::scroll-marker turns each scrollable panel into a tab button automatically. ::scroll-marker-group collects them into the tab bar. No <button role="tab"> elements to maintain, no click listeners, no ARIA sync — the browser handles focus, keyboard navigation, and active state via :target-current.

Feature detection: checking…
Before: manual tab implementation
Separate <button role="tab" aria-selected="…"> elements, JS click listeners, ARIA sync on every state change, keyboard handlers for arrow navigation. Each panel needs an ID, each tab needs aria-controls.
After: scroll-marker-group
Each panel gets a ::scroll-marker with its label from content: attr(data-tab). ::scroll-marker-group lays them in a row. :target-current styles the active tab. The browser handles keyboard navigation.
Overview
scroll-marker-group

The scroll-marker-group property creates a set of interactive markers that correspond to scroll-snap targets inside a scroll container.

When set to before or after, the browser generates a ::scroll-marker-group pseudo-element that collects all ::scroll-marker pseudo-elements from the container's children.

  • No JS required for basic tab/carousel navigation
  • Keyboard arrow navigation built in
  • :target-current styles the active marker
  • Works with any scroll-snap container
CSS code
the full implementation
.tab-panels { display: flex; overflow-x: scroll; scroll-snap-type: x mandatory; scroll-marker-group: before; } .tab-panel { flex: 0 0 100%; scroll-snap-align: start; } .tab-panel::scroll-marker { content: attr(data-tab); /* standard button styling */ } .tab-panel::scroll-marker:target-current { /* active tab styling */ background: white; border-bottom: 2px solid white; }
Keyboard navigation
built into the browser

The browser handles keyboard navigation automatically for the generated markers:

  • ←/→ Arrow — move between tab markers
  • Home/End — jump to first/last tab
  • Enter/Space — activate focused marker
  • Tab — enters/exits the marker group

Focus management follows the roving tabindex pattern without any JavaScript.

Browser support
Chrome 135+

::scroll-marker and ::scroll-marker-group shipped in Chrome 135. The demo detects support and falls back to a JS-powered tab implementation for older browsers.

  • Chrome 135+ — native
  • Edge 135+ — native
  • Firefox — not yet
  • Safari — not yet

The fallback uses scrollIntoView() + click handlers — same visual result, no CSS elegance.

Use cases beyond carousels
tabs, wizards, steppers

While designed for image carousels, scroll-marker-group works for any paginated scroll container:

  • Tabbed panels — this demo
  • Step wizards — installation or onboarding flows
  • Image galleries — original use case
  • Long-form sections — scroll through a document with chapter markers
  • Product variants — color/size swatches linked to product images
/* Tab UI with zero JavaScript — pure CSS scroll-markers */
.tab-panels {
  display: flex;
  overflow-x: scroll;
  scroll-snap-type: x mandatory;
  scroll-marker-group: before; /* generates ::scroll-marker-group before panels */
  scrollbar-width: none;
}

.tab-panel {
  flex: 0 0 100%;
  scroll-snap-align: start;
}

.tab-panel::scroll-marker {
  content: attr(data-tab); /* label from HTML attribute */
  /* style like a button */
}

.tab-panel::scroll-marker:target-current {
  background: white; /* active tab */
  border-bottom: 2px solid white; /* "lift" effect */
}

see also

references