Design reference

01Coverflow carousel

Sliders that show the system moving.

One carousel, three layers. Switch between the raw snap track, the controls, and the scroll-driven depth cue, then copy the same shape into a real page block.

Layer switch

Choose carousel layer

Motion layer The same live carousel changes its visible layer without changing the component.

  1. Layer 1 Plain scroll snap. The track still moves, lands, and takes keyboard focus.
  2. Layer 2 Buttons, dots, and live status come online after the site script runs.
  3. Layer 3 Depth and progress are scrubbed by the track position, not autoplay.

Progressive enhancement

How it keeps working.

The reference notes sit after the live specimen now. On small screens this block collapses, so the carousel stays close to the top.

How it works Six rules
  • Layer 1 — it works with nothing. The track is a CSS scroll-snap container. No JavaScript, no animation support, no library: it still scrolls, still snaps, and is still a tab stop with native arrow-key scrolling. Specimen 03 is that carousel with the script taken away, live.
  • Layer 2. The buttons show up only once the script runs. app.js adds the back and next buttons, dot jumps, and a live “slide n of m” count. They stay hidden until it runs, because a dead button is worse than none.
  • Layer 3 — motion behind an @supports gate. The depth cue and the progress rail run on view() and scroll() timelines. Browsers without scroll-driven animations never match the gate and keep the plain snap track — which is a correct resting state, not a broken one.
  • Position is read, never counted. Which slide you are on comes from the scroll spot each frame. The slide whose centre is nearest the track centre wins. So a flick, a key, and a button can never disagree with the dots.
  • The snap element is never transformed. If you move the snapping item itself, you move its own snap spot too. That feeds back into the scroll, and every snap point drifts off centre. So the depth runs on a figure inside it, on a timeline the parent sets.
  • No autoplay, ever. Nothing moves unless you move it. Images carry set width/height so there is no layout shift, only transform and opacity animate, and prefers-reduced-motion stops every sample moving while leaving them all usable.
  1. 01 Coverflow carousel content-band media-carousel
  2. 02 Buyer path carousel content-band media-carousel
  3. 03 Peek strip media-carousel media-carousel--strip
  4. 04 Baseline track (unenhanced) media-carousel (no data-carousel)
02

Buyer path carousel

.content-band media-carousel · Production example

The same component shaped like a real Lead Caravan page block: a visitor path from first click to qualified next step. This is the version to copy when the carousel needs to sell the system, not document the engine.

03

Peek strip

.media-carousel media-carousel--strip · All three layers, without the depth (the progress bar is still layer 3)

The browse-it-all one. Many small slides at once, snapped to the START of the track, not the middle. No depth cue — at this size a rotation reads as wobble, and the point of the variant is that every slide is equally legible. Thumbnails, logo rails, avatar sets.

04

Baseline track (unenhanced)

media-carousel (no data-carousel) · Layers 1 and 3. Everything the script was doing, gone

The same section with the data-carousel hook removed, so app.js never wires it. This is exactly what a visitor gets with JavaScript off or blocked: no buttons, no dots — and a carousel that still works. Note that the depth cue survives: it is CSS, so it never needed the script in the first place. Scroll it, or tab to it and use the arrow keys.

—

Anatomy

Three files, nothing else needed. Every sample above uses the same shared code and styles. No page has its own slider CSS.

  1. public/styles.css

    .media-carousel and its parts — the snap track, slides, figures, control row, dots, and the progress rail — plus the @supports gate that adds the scroll-driven layer and the --strip variant.

  2. public/motion.css

    lc-coverflow and lc-scroll-progress — the two scroll-driven keyframe sets, built from the vendored --motion-* tokens and declared inside the reduced-motion no-preference query, so under reduced motion they don't exist at all.

  3. public/app.js

    The [data-carousel] controller — buttons, dot jumps, the live status line, and the rAF-coalesced scroll read that decides which slide is current. It adds .carousel-ready, which is what unhides the controls.

Lead Caravan

Want this shape on a real page?

The classes are shared, not page-scoped — dropping .media-carousel onto a marketing page is markup, not new CSS. The section gallery carries it in the catalogue of every reusable section shape.

Back to the section gallery