Exhibit 04 of 17

A carousel on rails

Swipe, scroll or use the dots. The carousel is a scroll box with scroll-snap, so it always stops square on a slide.

How it works

The track is a row of full-width slides with overflow-x: auto. scroll-snap-type: x mandatory on the track and scroll-snap-align: start on each slide make every scroll end exactly on a slide. The numbered dots are ordinary links to each slide’s id; following one scrolls it into view.

Where the browser supports them, ::scroll-marker and ::scroll-button() let CSS generate the dots and the arrows itself, with the current dot marked by :target-current, and the link dots step aside.

The key CSS

Cut from this exhibit’s stylesheet as it is served.

/* the track is a row of full-width slides that scrolls sideways and stops on each one */
.x-carousel .track {
  display: grid; grid-auto-flow: column; grid-auto-columns: 100%; gap: 16px;
  overflow-x: auto; overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory; scroll-behavior: smooth;
}
.x-carousel .slide { scroll-snap-align: start; scroll-snap-stop: always; }

/* newer browsers: CSS makes the dots and the arrows itself, so the link dots step aside */
@supports selector(::scroll-marker) {
  .x-carousel .track { scroll-marker-group: after; }
  .x-carousel .track::scroll-marker-group { display: flex; justify-content: center; gap: 10px; padding-top: 14px; }
  .x-carousel .slide::scroll-marker { content: ""; width: 14px; height: 14px; border: 1.5px solid var(--ink); border-radius: 50%; }
  .x-carousel .slide::scroll-marker:target-current { background: var(--ink); }
  .x-carousel .track::scroll-button(left) { content: "←" / "Previous"; }
  .x-carousel .track::scroll-button(right) { content: "→" / "Next"; }
  .x-carousel .dots { display: none; }
}

Accessibility

The track takes keyboard focus (tabindex="0", which is HTML) so the arrow keys scroll it, and it is labelled as a region. The link dots move the page’s scroll position and fragment, so the page may jump to show the carousel, and the URL changes. Nothing auto-advances. A script carousel could announce “slide 2 of 4” as it moves; here each slide is labelled, but nothing is announced when it changes.

Browser support

  • scroll-snap Works in this browser.Not in this browser: Universal. Checked 2026-10-14: Chromium 149 yes, Firefox 141 yes.
  • ::scroll-marker Works in this browser.Not in this browser: The numbered links under the slides do the same job. Checked 2026-10-14: Chromium 149 yes, Firefox 141 no.
Slop at the top