Cover Flow
Apple's Cover Flow rebuilt on scroll snap and per-item view timelines — covers rotate through 3D as you scroll, no JavaScript.
scroll-driven
How it works
- The base is utilities only:
snap-x snap-mandatory overflow-x-autoon the list,snap-center shrink-0on each cover. That scrolls, snaps, and works everywhere — swipe, trackpad, arrow keys.[padding-inline:calc(50%-var(--cover-size)/2)]gives the first and last covers enough room to reach dead-center. - Each
<li>declares its own scroll-drivenview-timeline(--cf-item,inlineaxis) that runs 0 → 100% as the cover crosses the track. Two animations bound to it viaanimation-timelinedo the work:cf-rotate-coveron the inner cover andcf-adjust-z-indexon the<li>so the centered cover stacks above its neighbors. - The 3D is
perspective: 40emon the<li>plusrotateY/translateZ/scaleon the cover: side covers rotate 45° away, the centered one straightens, scales up, and pulls forward. - The transform runs on the inner cover
<div>, never the<li>. Rotating the<li>changes its layout box, which changes the scroller’s total length and makes the whole row flicker as it scrolls. The<li>stays a static box and only carries the perspective. - The mirror is
-webkit-box-reflect— a Blink/WebKit-only, purely decorative reflection that simply doesn’t render elsewhere.
Why the CSS block
@keyframes, view-timeline, and the animation-timeline binding have no utility syntax,
so they live in a CSS block. It’s scoped behind @supports (animation-timeline: --t), so
the block is optional: skip it and you keep a working scroll-snap row of covers.
Accessibility
- The covers are a plain scrollable list. Each tile carries a visible text label, so it has
an accessible name; swap the gradient tiles for
<img>with realalttext for your own art. - The track is
tabindex="0"with a focus outline, so it’s keyboard-focusable and the arrow keys scroll it;snap-centerkeeps parking a cover in the middle. - What’s missing versus the JS original: no click-a-cover-to-center, and no drag or momentum. Those need JavaScript, so this is scroll- and keyboard-driven only.
The fallback story
- Without scroll-driven animations (Firefox stable, older Safari) the covers stay in a flat, horizontal scroll-snap row — fully scrollable and still snapping to center, just without the 3D flow.
- Outside Blink/WebKit the reflection is absent; nothing else changes.
- Under
prefers-reduced-motion: reducethe flow drops to the same flat row. The global reduced-motion rule only clamps animation duration, which a scroll-driven animation ignores, so the demo disables the rotation explicitly.
Browser support
Minimum stable version per engine, resolved at build time from MDN's browser-compat-data; Baseline status from the official web-features dataset. Everything degrades gracefully — the “when missing” column is the actual behavior, not a broken page.
| Feature | Chrome | Edge | Firefox | Safari | When missing |
|---|---|---|---|---|---|
| Scroll-driven animations (view-timeline / timeline-scope)† | 116 | 116 | ✕ | 26 | WebKit/Chromium show a plain secondary track with no filled portion; Firefox still fills natively via ::-moz-range-progress. The slider stays fully usable — only the progress-style fill is missing. |
† The badge tests animation-timeline as a stand-in — the range fill also needs view-timeline, timeline-scope and @property, which all shipped together with scroll-driven animations in every engine that has them.