The CSS Overflow Module Level 5 spec introduces two native mechanisms for building carousels: Scroll Buttons (browser-provided <button> elements that scroll content by 85% of the area) and Scroll Markers (linked <a> elements that jump to specific items). Chrome 135 shipped an experimental prototype of both features. This overview captures the core concepts and current implementation details.

Building the Basic Scroller

The foundation is a standard list of items placed in a CSS grid that lays them out in a single line:

.carousel {
  display: grid;
  grid-auto-flow: column;
}

Each item can be sized relative to the container, for example 40% width with a gap between items:

.carousel {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 40%;
  gap: 2rem;
}

To keep the scroll position predictable, CSS Scroll Snapping can be applied so the viewport settles on each item rather than sliding past:

.carousel {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 40%;
  gap: 2rem;

  scroll-snap-type: x mandatory;

  > li {
    scroll-snap-align: center;
  }
}

A border and internal padding on the carousel container helps visually frame the scrolling area.

Adding Scroll Buttons

Two new pseudo-elements enable and style the directional scroll buttons: ::scroll-button(left) and ::scroll-button(right). In Chrome's DevTools, these are actually exposed using logical properties — ::scroll-button(inline-start) and ::scroll-button(inline-end).

DevTools with an arrow pointing at the two scroll buttons in the HTML showing them with logical naming.

Both pseudo-elements support the CSS content property, which supplies the button label:

.carousel::scroll-button(left) {
  content: "Left";
}
.carousel::scroll-button(right) {
  content: "Right";
}

The buttons can be selected together with a group selector or the universal selector:

.carousel::scroll-button(*) {
  /* Styles */
}

CSS Anchor Positioning can then place the left button on the container's left edge and the right button on its right edge:

.carousel {
  /* ... */
  anchor-name: --carousel; /* define the anchor */
}

.carousel::scroll-button(*) {
  position: fixed; /* set containment on the target */
  position-anchor: --carousel; /* set the anchor */
}

.carousel::scroll-button(left) {
  content: "Left";
  position-area: center left;
}
.carousel::scroll-button(right) {
  content: "Right";
  position-area: center right;
}

A notable feature: the buttons automatically become disabled at either end of the scrollable area — behavior that normally requires JavaScript.

One quirk: the spec defines an 85% scroll distance per click. With scroll snapping enabled, each click advances exactly one item because the snap point overrides the default distance.

Scroll Markers as Navigation Dots

Markers are <a> elements anchored to individual carousel items. The ::scroll-marker-group pseudo-element styles the container holding them. For a horizontal row of centered dots, Flexbox is a straightforward choice:

.carousel::scroll-marker-group {
  display: flex;
  justify-content: center;
  gap: 1rem;
}

Position the marker group relative to the carousel using the scroll-marker-group property, which accepts before (above) or after (below):

.carousel {
  /* ... */
  scroll-marker-group: after; /* displayed below the content */
}

Each marker itself is styled with the ::scroll-marker pseudo-element:

.carousel {
  /* ... */

  > li::scroll-marker {
    content: "";
    aspect-ratio: 1;
    border: 2px solid CanvasText;
    border-radius: 100%;
    width: 20px;
  }
}

The active item is targeted via the :target-current pseudo-class:

li::scroll-marker:target-current {
  background: CanvasText;
}

Hover and focus states can refine the markers further:

li::scroll-marker:hover,
li::scroll-marker:focus-visible {
  background: LinkText;
}

Native focus management is included, meaning keyboard users can tab through markers and cycle through carousel items without custom handlers.

Smooth Scrolling Caveats

scroll-behavior: smooth can animate the scroll. It is best applied conditionally using the user's prefers-reduced-motion settings:

.carousel {
  /* ... */

  @media (prefers-reduced-motion: no-preference) {
    scroll-behavior: smooth;
  }
}

In practice, smooth scrolling conflicts with scroll snapping: the 85% default scroll amount from the buttons does not align cleanly with snap points. Adjusting grid-auto-columns helps in some cases, but the two features still require manual tuning to work together dependably.

Current Status

Both features are experimental and only available in Chrome 135 and later. The CSS Overflow Level 5 specification is still evolving, so expect changes in behavior and syntax as the proposal matures and other browsers implement support.