Entry 01
Carousel on native scroll
Use it for any horizontal row of cards. The carousel is a real scroll container, so swipe, trackpad, keyboard and momentum all work before any script loads.
Code
.carousel {
display: flex;
gap: 1rem;
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-padding-inline: 1.5rem;
padding-inline: 1.5rem;
overscroll-behavior-x: contain;
scrollbar-width: none;
}
.carousel > * {
flex: none;
scroll-snap-align: start;
}
@media (prefers-reduced-motion: no-preference) {
.carousel { scroll-behavior: smooth; }
}Arrow buttons are the one part that needs a script.
<button data-carousel-dir="-1" aria-label="Previous">←</button>
<button data-carousel-dir="1" aria-label="Next">→</button>const carousel = document.querySelector(".carousel");
const [prev, next] = document.querySelectorAll("[data-carousel-dir]");
const sign = getComputedStyle(carousel).direction === "rtl" ? -1 : 1;
const step = (dir) => {
const card = carousel.firstElementChild.getBoundingClientRect().width;
carousel.scrollBy({ left: sign * dir * card });
};
const sync = () => {
const max = carousel.scrollWidth - carousel.clientWidth;
const scrolled = Math.abs(carousel.scrollLeft);
prev.disabled = scrolled <= 1;
next.disabled = scrolled >= max - 1;
};
prev.addEventListener("click", () => step(-1));
next.addEventListener("click", () => step(1));
carousel.addEventListener("scroll", sync, { passive: true });
sync();Why it works, the rules and the credits
Why it works
- Scroll snap makes the browser do the scrolling and the landing. Every input method gets the same result.
scrollBymoves one card width and snapping picks the final position. The script never calculates an offset.- In a right-to-left row
scrollLeftstarts at 0 and goes negative.signandMath.abs()make the script work in both directions. scroll-behavior: smoothin CSS animates the script's scroll. The media query turns the animation off for people who ask for reduced motion.overscroll-behavior-x: containstops a swipe at the end of the row from triggering the browser's back gesture.
Rules
- Never move slides with
transform, and never handle wheel or touch events yourself. - Match
scroll-padding-inlinetopadding-inlineso a snapped card lines up with the page content. - Chrome 135 can draw the arrows and dots in CSS with
::scroll-button()and::scroll-marker. Firefox and Safari have neither, so use the script.
Credits
Borrowed from: Steve Sewell, Build buttery smooth carousels with pure CSS like Nike. This version swaps his scrollIntoView() for scrollBy(), which can only scroll the carousel and never the page.
Docs: Adam Argyle, Carousels with CSS · MDN: scroll snap · MDN: scrollBy()