Scroll-Snap Peek Carousel — Free CSS Snap Carousel With Edge Peeking

Scroll-Snap Peek Carousel · Carousels · Plain HTML, CSS & JS · Live preview

About this UI Snippet

Scroll-Snap Peek Carousel — Neighbors Always Peek, Scale Follows Real Position

Screenshot of the Scroll-Snap Peek Carousel snippet rendered live

A one-slide-per-view carousel hides everything except the current slide. This one deliberately doesn't: each slide is sized to 72% of the track's width so the previous and next slides always peek in at both edges, giving a visual hint that there's more to scroll to. It's built with native CSS scroll-snap-type, so dragging, trackpad scrolling, and keyboard navigation all work for free — with a small vanilla JS layer adding scale and opacity driven by real position, not just snap-point membership.

CSS does the snapping, JS does the emphasis

.pc-track sets scroll-snap-type: x mandatory and each .pc-slide sets scroll-snap-align: center, which is enough on its own to make the carousel snap crisply to each slide with zero JavaScript — this works with touch drags, a trackpad, a mouse wheel with shift, or a scrollbar. JavaScript is layered on top purely for the visual polish: scaling the active card up and neighboring cards down as a continuous function of position.

Scale as a continuous function of distance, not a binary state

Rather than a simple "is this the active slide, yes or no" check, the scroll handler measures each slide's actual distance from the track's horizontal center via getBoundingClientRect() and computes a proximity value that falls off smoothly to zero at roughly 90% of a slide's width away. That proximity value drives both scale (0.86 to 1.0) and opacity (0.5 to 1.0) continuously — so mid-drag, before a slide has snapped, you see genuinely intermediate scale values on both the outgoing and incoming cards, not a jump-cut between two fixed states.

72% slides guarantee the peek

Each slide is sized to flex: 0 0 72% with generous 14vw horizontal padding on the track, which mathematically guarantees roughly 14% of both neighbors are visible at the track's edges at all times — an intentional layout choice, not an accident of viewport width, so the peek amount stays consistent across screen sizes.

Dots as both indicator and control

A row of dots below the track is generated to match the slide count; the closest slide to center is highlighted on scroll (computed in the same pass as the scale update), and clicking a dot calls scrollIntoView with inline: 'center', which respects the track's native snap behavior for a smooth scroll to that slide.

Customizing it

Adjust the 72% slide width for a wider or narrower peek, change the proximity falloff curve for a sharper or gentler scale transition, or add autoplay that calls scrollIntoView on an interval. Pair it with scroll-snap gallery or a scroll reveal grid for other layout patterns.

What's included

Features

Native CSS snap
scroll-snap-type handles drag, wheel, and touch for free.
Guaranteed edge peek
72% slide width always leaves neighbors visible.
Continuous scale function
Proximity to center drives scale smoothly, not binary.
Position-driven, not snap-driven
Scale updates from real getBoundingClientRect distance.
rAF-throttled scroll handler
One calculation per frame regardless of event frequency.
Generated dot indicators
Dots build automatically from the slide count.
Click-to-navigate dots
scrollIntoView respects native snap behavior.
No dependencies
Pure CSS scroll-snap plus a small vanilla JS layer.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSAn intro, a peeking carousel track, dots, and an outro render.
  2. 2
    Drag or scroll the trackSlides snap to center; neighbors peek at both edges.
  3. 3
    Watch the scaleThe centered card scales up; neighbors shrink and dim.
  4. 4
    Drag slowly mid-scrollScale and opacity shift continuously, not in a jump-cut.
  5. 5
    Click a dotThe track smooth-scrolls that slide to center.
  6. 6
    Adjust the peek amountChange .pc-slide's flex-basis percentage.

Real-world uses

Common Use Cases

Portfolio showcases
Present projects with neighbouring slides always visible at both edges, because each slide is sized to 72 percent of the track width.
E-commerce image sets
Use a peek carousel for product images, where a slide's scale rises continuously as it approaches the centre of the track.
Testimonial sliders
Hint at more quotes without hiding them, with native scroll-snap-type handling drag, wheel and touch for free.
Peeking intro cards
Pair with a scroll snap gallery pattern for intro cards, with scale driven by real getBoundingClientRect distance rather than the snapped index.
Feature rows and card decks
Contrast with a scroll reveal grid for feature highlights, or use as a touch-first mobile card deck.
Related: Staggered Reveal on Scroll — IntersectionObserver, One Timer
See the Staggered Reveal on Scroll — IntersectionObserver, One Timer for a related scroll pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

No — scroll-snap-type: x mandatory on the track and scroll-snap-align: center on each slide are enough on their own for a fully functional snapping carousel that supports drag, trackpad, mouse wheel, and keyboard scrolling. The JavaScript only adds the scale-and-fade visual polish on top of behavior the browser already provides natively.

On scroll, each slide's getBoundingClientRect gives its actual horizontal center position, which is compared against the track's center to get a distance in pixels. That distance is converted to a proximity value between 0 and 1 that falls off smoothly as distance approaches about 90% of a slide's width, and proximity linearly drives both the scale (0.86 to 1.0) and opacity (0.5 to 1.0) — a continuous calculation, not a lookup based on which slide is "currently active."

Each slide is sized to flex: 0 0 72% of the track's content box, with the track itself padded 14vw on each side. Combined, this guarantees that when a slide is centered, the remaining ~28% of track width is split between showing slivers of the previous and next slides — a deliberate layout ratio, not something that depends on viewport width.

Yes — the scroll handler runs on every scroll event (throttled to once per animation frame), and because it measures live getBoundingClientRect positions rather than checking which slide has snapped, dragging the track partway between two slides shows genuinely intermediate scale and opacity values on both the outgoing and incoming cards.

Render the track and slides with refs, generate the dots array from your slide data, and in a mount effect attach the same rAF-throttled scroll listener to the track ref, reading getBoundingClientRect on the slide refs. Return a cleanup that removes the scroll and resize listeners. The CSS scroll-snap properties need no changes.

Source Code

<section class="pc-intro"><h1>Drag or scroll →</h1><p>Each slide is sized so the next and previous slides peek in at the edges, with the active slide scaling up based on real scroll position within the track.</p></section>
<div class="pc-track" id="pcTrack">
  <div class="pc-slide"><div class="pc-card"><span class="pc-num">01</span><h3>Northwind</h3><p>Brand identity for a climate-tech studio.</p></div></div>
  <div class="pc-slide"><div class="pc-card"><span class="pc-num">02</span><h3>Cadence</h3><p>A scheduling app rebuilt from the ground up.</p></div></div>
  <div class="pc-slide"><div class="pc-card"><span class="pc-num">03</span><h3>Lumen</h3><p>Dashboard design for a solar analytics firm.</p></div></div>
  <div class="pc-slide"><div class="pc-card"><span class="pc-num">04</span><h3>Harbor</h3><p>E-commerce for an independent boatyard.</p></div></div>
  <div class="pc-slide"><div class="pc-card"><span class="pc-num">05</span><h3>Verde</h3><p>Packaging system for a plant-based label.</p></div></div>
  <div class="pc-slide"><div class="pc-card"><span class="pc-num">06</span><h3>Ember</h3><p>Motion design for a live events platform.</p></div></div>
</div>
<div class="pc-dots" id="pcDots"></div>
<section class="pc-outro"><p>Notice neighboring slides never fully disappear — they peek at both edges.</p></section>

Build with AI

Build, Understand, Optimize, and Extend It With AI

Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain how sizing each slide to 72% of the track's width combined with 14vw of track padding mathematically guarantees neighboring slides peek in at both edges regardless of viewport size, and why computing a continuous proximity value from getBoundingClientRect distance (rather than checking which slide has snapped) is what makes the scale and opacity respond smoothly to mid-drag positions instead of jump-cutting between fixed states. It's also useful for extending the carousel: ask it to add autoplay that calls scrollIntoView on an interval while pausing on hover or touch, or to make the peek percentage and card count responsive so mobile shows a tighter peek than desktop.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a "scroll-snap peek carousel" in plain HTML, CSS, and vanilla JavaScript (no libraries).

Requirements:
- A horizontally scrollable track using CSS scroll-snap-type: x mandatory, with each slide using scroll-snap-align: center, so the browser handles snapping natively for drag, trackpad, mouse wheel, and touch input with zero JavaScript drag-handling code.
- Size each slide so it does NOT fill the full track width — use something like flex: 0 0 72% combined with horizontal padding on the track (e.g. 14vw) so that when any slide is centered, slivers of the previous and next slides remain visible ("peeking") at both edges of the viewport. This peek behavior must hold at different viewport widths, not just accidentally at one size.
- On scroll (throttled with a requestAnimationFrame guard so it runs at most once per frame), measure every slide's real position with getBoundingClientRect, compute each slide's horizontal distance from the track's center, and convert that distance into a continuous "proximity" value between 0 and 1 that falls off smoothly rather than being a hard cutoff.
- Use that continuous proximity value to set each card's CSS scale (e.g. from about 0.86 up to 1.0) and opacity (e.g. from about 0.5 up to 1.0) directly via inline styles, so that dragging the track slowly and stopping mid-scroll — before any slide has snapped — shows genuinely intermediate scale and opacity values on the cards nearest center, not a binary "active vs inactive" jump.
- Generate a row of dot indicators matching the slide count, highlight the dot for whichever slide is currently closest to the track's center, and make each dot clickable to scroll its slide to center using scrollIntoView with inline: 'center' so it respects the native snap points.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.