webdevpuneet.com

Native Scroll-Snap Carousel with view-timeline Active-Slide Scale

Scroll-Snap Carousel (view-timeline Scale) · Carousels · Plain HTML, CSS & JS · Live preview

Copy & paste

Source Code

<div class="ssv-wrap">
  <p class="ssv-hint">Drag, swipe, or scroll the strip below. Native CSS <code>scroll-snap-type</code> handles the snapping and the active slide's scale-up comes purely from <code>animation-timeline: view()</code> — no carousel library, no JS position tracking.</p>
  <div class="ssv-track">
    <div class="ssv-slide" style="--c1:#4338ca;--c2:#7c3aed"><span>01</span><h3>Launch Week</h3></div>
    <div class="ssv-slide" style="--c1:#0891b2;--c2:#0d9488"><span>02</span><h3>Field Notes</h3></div>
    <div class="ssv-slide" style="--c1:#db2777;--c2:#ea580c"><span>03</span><h3>Behind the Scenes</h3></div>
    <div class="ssv-slide" style="--c1:#16a34a;--c2:#65a30d"><span>04</span><h3>Studio Visit</h3></div>
    <div class="ssv-slide" style="--c1:#eab308;--c2:#dc2626"><span>05</span><h3>Year in Review</h3></div>
  </div>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Native scroll-snap-type + scroll-snap-align handle all snapping, dragging, and keyboard nav
animation-timeline: view() on the inline axis drives the active-slide scale and fade
contain-based animation-range peaks emphasis closer to true visual center than cover would
No JS activeIndex state — every slide computes its own emphasis independently
Slides can be added, removed, or reordered with zero script changes
Compositor-friendly transform: scale() and opacity keep the effect smooth while dragging
@supports fallback keeps the carousel fully functional, just without the scale emphasis
Works with touch drag, trackpad, mouse wheel, and keyboard out of the box

About this UI Snippet

Scroll-Snap Carousel with view-timeline Scale — No Carousel Library, No JS Position Math

Screenshot of the Scroll-Snap Carousel (view-timeline Scale) snippet rendered live

A "story" or "peek" carousel — a horizontal row of cards where the centered card is emphasized while neighbors sit smaller and dimmer at the edges — is normally built with a carousel library computing each slide's distance from center on scroll and writing an inline transform. This snippet builds the identical visual result with two entirely native CSS features working together: scroll-snap-type for the snapping mechanics, and a per-slide animation-timeline: view() for the scale-and-fade emphasis — zero JavaScript touches slide position at any point.

Two independent native systems, stacked

scroll-snap-type: x mandatory on .ssv-track plus scroll-snap-align: center on each .ssv-slide is the entire snapping mechanism — dragging, trackpad scrolling, and keyboard arrow navigation (once a slide receives focus) all snap crisply to the nearest slide's center with no JavaScript at all, the same technique behind Scroll Snap Peek Carousel. The scale-and-fade emphasis is a completely separate system layered on top: each slide's own view-timeline-axis: inline timeline, tracking that slide's transit across the horizontally scrolling track, drives a @keyframes animation that scales the slide up and fades it to full opacity right around the point it is centered.

Why contain instead of cover for animation-range

animation-range: contain 0% contain 100% uses the contain range rather than cover — contain represents the portion of the timeline during which the *entire* slide is contained within the scrollport, rather than any part of it being visible at all. For a slide that is narrower than the viewport, this produces a scale peak much closer to the slide's true centered position than cover would, since cover starts as soon as any single pixel of the slide is visible.

No carousel library, no active-index state

A typical JS carousel tracks an activeIndex in state and recomputes classes or inline styles for every slide whenever it changes. Here there is no index anywhere — each slide answers "how emphasized should I be right now" purely from its own view-timeline, so slides can be added, removed, or reordered in the DOM with no JavaScript changes required at all.

Browser support

scroll-snap-type has been supported everywhere for years and works today regardless of browser. The view-timeline-driven scale is the newer piece: Chromium-based browsers (Chrome, Edge, Opera, Brave) support animation-timeline: view() today, while Firefox and Safari support is still landing. An @supports not (animation-timeline: view()) block shows every slide at full scale and opacity in that case — the carousel still snaps and functions perfectly, it simply loses the scale emphasis.

Customizing it

Adjust the 0.82 minimum scale and .5 minimum opacity in @keyframes ssv-scale for a subtler or more dramatic emphasis, change the slide width or gap to control how many neighbors peek in at the edges, or add a small dot pagination indicator that mirrors scroll-snap-align targets via :target or a tiny scroll-position script for keyboard-only navigation. Pair it with Dot Pagination Carousel for an added position indicator.

Step by step

How to Use

  1. 1
    Paste the HTML, CSS, and JSA five-slide horizontal scroll-snap carousel renders — no library needed.
  2. 2
    Drag or scroll the stripEach slide snaps to center; the centered slide scales up and brightens automatically.
  3. 3
    Try keyboard navigationFocus the track and use arrow keys or Tab — native scroll-snap handles keyboard scroll targets.
  4. 4
    Adjust the emphasis amountTune scale(.82) and opacity: .5 in @keyframes ssv-scale for a subtler or bolder effect.
  5. 5
    Change slide count or widthAdd, remove, or resize .ssv-slide elements — no JS index state to update anywhere.
  6. 6
    Export in your formatClick "HTML" for a standalone file, "JSX" for a React component, or "Tailwind" for a React + Tailwind version.

Real-world uses

Common Use Cases

Story and highlight carousels
A CSS-only alternative to a JS carousel library for Recently Viewed Carousel-style highlight rows.
Product or portfolio spotlight rows
Emphasize whichever item is centered without maintaining any active-index state in JavaScript.
Learn contain vs cover animation-range
A focused comparison point for how contain narrows a view-timeline range differently than cover.
Testimonial or quote carousels
Pair with Testimonial Carousel patterns for a native-CSS emphasis effect.
Replace a JS carousel library
Removes the dependency on a carousel library purely for the peek-and-scale visual behavior.
Related: Scroll Snap Peek Carousel
See the Scroll Snap Peek Carousel for the JS-driven version of a similar peek-and-scale carousel.

Got questions?

Frequently Asked Questions

No. Snapping is entirely native scroll-snap-type and scroll-snap-align, and the scale/fade emphasis is entirely native animation-timeline: view(). The included JavaScript only logs whether the browser supports view() timelines — it never tracks slide position or drives the animation.

cover represents the range during which any part of the element is visible in the scrollport, starting the instant a single pixel appears. contain represents the range during which the entire element is fully contained within the scrollport at once. For a narrower-than-viewport slide, contain produces a peak much closer to the slide's true centered position than cover would.

It doesn't "know" in the sense of comparing itself to siblings — each slide's own view timeline simply reports how far through its contain range it currently is, and the @keyframes animation happens to peak in scale and opacity at the 50% keyframe stop, which corresponds to the middle of that range. Because scroll-snap-align: center pulls the active slide toward the viewport center, its contain range midpoint and its visually centered position coincide.

Yes. Every .ssv-slide computes its own emphasis from its own view-timeline-name, so there is no index array, no activeIndex state, and no per-slide JavaScript wiring to update — adding or removing a slide element in the HTML is the only change required.

The @supports not (animation-timeline: view()) block resets every slide to full scale and full opacity with no animation, so Firefox and Safari users still get a fully functional, native scroll-snap carousel — it simply does not scale the centered slide up.

Yes, all three work natively because scroll-snap-type is a browser-level scrolling feature, not a JS-simulated one. Touch drags, trackpad gestures, mouse wheel scrolling, and keyboard-driven scroll (once an element inside the track has focus) all trigger the same native snap behavior.