CSS Scroll Snap: Carousels and Full-Page Sections

Scroll snap makes a scrolling box come to rest on chosen children instead of anywhere. Put scroll-snap-type: x mandatory (or y, or proximity) on the scroll container, then scroll-snap-align: start, center or end on each child. Add scroll-padding for fixed headers and scroll-snap-stop: always to stop on every item.

Scroll snap properties at a glance
PropertySet onValues
scroll-snap-typeScroll containernone, x, y, block, inline, both + mandatory or proximity
scroll-snap-alignEach childnone, start, center, end (one or two values)
scroll-snap-stopEach childnormal (may skip past), always (must stop)
scroll-paddingScroll containerLength or percentage inset, like padding
scroll-marginEach childLength outset around the snap area

Updated

Scroll snap used to need a carousel library. Today a few lines of CSS give you swipeable cards, a paged gallery or full-screen sections, with momentum scrolling and keyboard support handled by the browser. Use the playground to feel the difference between each value before you copy any code.

Scroll snap playground

.scroller {
  overflow-x: auto;
  scroll-snap-type: x mandatory;
}
.scroller > * {
  scroll-snap-align: start;
  scroll-snap-stop: normal;
}

CSS-only card carousel

Horizontal carousel with a peeking next card

Swipe, scroll sideways or click inside and use the arrow keys. Each card is 80 percent wide, so the next one shows at the edge.

<style>
  .track { display: flex; gap: 12px; overflow-x: auto; scroll-snap-type: x mandatory;
           scroll-padding-inline: 16px; padding: 8px 16px 16px; overscroll-behavior-x: contain }
  .track > li { flex: 0 0 80%; scroll-snap-align: start; list-style: none; height: 140px;
           border-radius: 14px; display: grid; place-items: center; font: 600 20px system-ui; color: #fff }
  li:nth-child(1) { background: #4F98FB } li:nth-child(2) { background: #7c3aed }
  li:nth-child(3) { background: #10b981 } li:nth-child(4) { background: #f59e0b }
</style>
<ul class="track" tabindex="0" aria-label="Example slides">
  <li>One</li><li>Two</li><li>Three</li><li>Four</li>
</ul>

Full-page sections

Vertical sections that snap under a fixed header

Snapping is on html, the page scroller. scroll-padding-top keeps sections clear of a 64px header, and proximity avoids trapping long sections.

html {
  scroll-snap-type: y proximity;
  scroll-padding-top: 64px;
}
section.panel {
  min-height: calc(100svh - 64px);
  scroll-snap-align: start;
}
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}

Next and previous buttons

Drive the carousel with scrollBy

The browser finishes the move on the nearest snap point, so scrolling by one card width lands exactly on the next card.

const track = document.querySelector(".track");
const step = () => track.firstElementChild.getBoundingClientRect().width + 12; // card + gap

document.querySelector(".next").addEventListener("click", () => {
  track.scrollBy({ left: step(), behavior: "smooth" });
});
document.querySelector(".prev").addEventListener("click", () => {
  track.scrollBy({ left: -step(), behavior: "smooth" });
});

Carousels people can actually use

Let the next card peek in by making each slide narrower than the container, for example flex: 0 0 85%. The visible edge tells people there is more to swipe, which a perfectly fitted slide does not.

Give the scroller a label such as aria-label="Customer photos" and keep slides as a list. Do not hide off-screen slides from screen readers, and never autoplay without a pause control.

Full-page sections without the trap

Full-height snapping looks impressive in a demo and annoys readers when one section has more text than fits the screen, because mandatory snapping keeps yanking them past it. Use proximity for page sections, or make sure every section really fits a phone screen.

Respect reduced motion by turning off scroll-behavior: smooth inside a prefers-reduced-motion: reduce query. Snapping itself is fine to keep, since it moves only as far as the reader asked.

How it works

  1. Pick an axis and strictness for the container in the playground.
  2. Choose how each slide aligns and whether scrolling must stop on every slide.
  3. Scroll the preview with a trackpad, mouse wheel, touch or the arrow keys after clicking into it.
  4. Copy the generated CSS, or take one of the complete patterns further down.

Tips

Frequently asked questions

What is the difference between mandatory and proximity?

With mandatory the container always settles on a snap position once scrolling stops. With proximity it only snaps when the scroll ends close to one, which leaves people free to read in between. proximity is the default when you write only the axis.

Why is scroll snap not working?

The usual causes are: scroll-snap-type is on the children instead of the scrolling parent, the parent does not actually overflow (set overflow-x: auto and a fixed size), or the children have no scroll-snap-align. For page-level snapping the container is html, not body.

How do I stop a fixed header covering snapped sections?

Set scroll-padding-top on the scroll container to the header height, for example html { scroll-padding-top: 64px; }. Snapped sections and anchor jumps then stop below the header instead of underneath it.

Can I build a carousel with no JavaScript?

Yes for swiping and scrolling: a flex row with overflow-x: auto and x mandatory snapping is a working carousel. Dot indicators that show the current slide, autoplay and looping still need a little script.

Is CSS scroll snap supported everywhere?

scroll-snap-type has been Baseline widely available since 2022 across Chrome, Edge, Firefox and Safari, and scroll-snap-stop since July 2022. You can use it without a fallback; browsers without it simply scroll freely.

Related: React carousel with scroll snap, Flexbox and grid cheat sheet, Scroll animations guide, prefers-reduced-motion guide, CSS parallax scrolling.

Start for free or see all free tools. We.Inc is free to start, with no card needed. AI building is on paid plans from $20 a month.

More in Guides

We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.

Product

Who It's For

Features

Resources

Company

View Sitemap