CSS Scroll Carousel Generator
Build a zero-JavaScript carousel with dot navigation using ::scroll-marker and scroll-snap
Live preview
Preview shows the scroll-snap behavior only — dots/buttons need the generated CSS below rendered by a browser that supports them.
<ul class="carousel">
<li class="carousel-item">Slide 1</li>
<li class="carousel-item">Slide 2</li>
<li class="carousel-item">Slide 3</li>
<li class="carousel-item">Slide 4</li>
<li class="carousel-item">Slide 5</li>
</ul>.carousel {
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory;
gap: 16px;
list-style: none;
padding: 0;
margin: 0;
scroll-marker-group: after;
}
.carousel-item {
scroll-snap-align: start;
flex: 0 0 auto;
width: 240px;
}
/* Auto-generated dot navigation — one ::scroll-marker per item, grouped by ::scroll-marker-group */
.carousel::scroll-marker-group {
display: flex;
gap: 8px;
justify-content: center;
margin-top: 12px;
}
.carousel-item::scroll-marker {
content: "";
width: 10px;
height: 10px;
border-radius: 50%;
background: #333333;
cursor: pointer;
}
.carousel-item::scroll-marker:target-current {
background: #a855f7;
}
/* Prev/next buttons — browsers without support simply won't render them (progressive enhancement) */
.carousel::scroll-button(left) {
content: "←";
}
.carousel::scroll-button(right) {
content: "→";
}
.carousel::scroll-button(left),
.carousel::scroll-button(right) {
position: absolute;
color: #a855f7;
cursor: pointer;
}About this tool
The CSS Scroll Carousel Generator builds a fully functional, zero-JavaScript carousel using the CSS scroll-snap and scroll-marker APIs — scroll-snap-type/scroll-snap-align for the core scrolling-and-snapping behavior (broadly supported for years), plus ::scroll-marker, ::scroll-marker-group, and ::scroll-button() for auto-generated dot navigation and prev/next buttons (newer, reaching Chrome/Edge 135+ and Safari 19+ support through 2026). The dots aren't manually positioned elements you'd have to keep in sync with the slide count — scroll-marker-group automatically generates one marker per scrollable child that declares a ::scroll-marker, and the :target-current pseudo-class lets you style whichever marker corresponds to the currently-snapped slide, entirely through CSS.
When to use it
- →Building an image or testimonial carousel without shipping any carousel JavaScript library
- →Adding accessible dot navigation to a scroll-snap gallery without manually tracking the active slide in JS
- →Prototyping a product card carousel with prev/next buttons that degrade gracefully on unsupported browsers
- →Learning the ::scroll-marker / scroll-marker-group / ::scroll-button() syntax by example
Tips
- ◆The core scrolling and snapping (scroll-snap-type, scroll-snap-align) works in every modern browser regardless of ::scroll-marker support — the generated dots and buttons are progressive enhancement, not a hard requirement for the carousel to function.
- ◆Firefox has scroll-snap support but keeps ::scroll-marker/::scroll-button() behind a flag as of 2026 — test on your actual target browsers before relying on the generated dot navigation being visible everywhere.
- ◆scroll-marker-group: before vs. after only changes where the dot row sits in the DOM's visual and tab order, not which element it's attached to — the generated markers always correspond to the direct scrollable children.
Frequently asked questions
Do I need any JavaScript at all for this carousel to work?
No — scrolling, snapping, dot navigation, and prev/next buttons are all pure CSS. The dots and buttons are clickable by nature (::scroll-marker and ::scroll-button() generate real interactive elements the browser handles), so there's genuinely no JavaScript carousel library needed.
What happens in a browser that doesn't support ::scroll-marker yet?
The carousel still scrolls and snaps normally via plain scroll-snap-type/scroll-snap-align — those have been well-supported for years. You simply won't see the generated dots or buttons; the content remains fully scrollable by swipe, wheel, or keyboard.
Can I style the 'current' dot differently from the others?
Yes — that's what the ::scroll-marker:target-current pseudo-class is for. It targets whichever generated marker corresponds to the slide currently snapped into view, letting you give it a different color or size without any scroll-position JavaScript.