๐ CSS Overflow 5 carousels
Zero-JS Carousel
The browser generates and manages the prev/next buttons and pagination dots โ they don't even exist in your DOM. Accessibility, disabled states and snapping come free.
Use it for: Product showcases ยท testimonials ยท image galleries
arrows + dots are generated and managed entirely by the browser โ inspect the DOM: they aren't in it.
The complete source
This is the exact file rendering the demo above โ a single self-contained React client component with its styles inline. Copy or download it, drop it into any React / Next.js project, and it runs. No extra dependencies.
carousel.jsx
1"use client";23/* ============================================================4ZERO-JS CAROUSEL โ CSS Overflow Level 5.5::scroll-button() generates real, keyboard-accessible6prev/next buttons; ::scroll-marker() generates the pagination7dots โ the browser manages every state. Shipped: Chrome/Edge8135+, Safari 18.2+. Older browsers still get a perfectly9usable scroll-snap carousel (the buttons/dots just don't10render). Not one line of JavaScript.11============================================================ */1213const SLIDES = [14{ title: "Aurora", hue: "#7c3aed", text: "Conjured nightly above the workshop." },15{ title: "Tide", hue: "#06b6d4", text: "Momentum you can set a watch by." },16{ title: "Ember", hue: "#ffa500", text: "Every idea starts as one warm point." },17{ title: "Bloom", hue: "#f43f5e", text: "Growth follows attention. Always." },18{ title: "Night", hue: "#3b82f6", text: "Rest is a load-bearing feature." },19];2021export default function Carousel() {22return (23<div className="czjs">24<style>{`25.czjs-track {26display: grid; grid-auto-flow: column; grid-auto-columns: 88%;27gap: 1.6rem; overflow-x: auto; padding: 0.4rem;28scroll-snap-type: x mandatory;29scrollbar-width: none;30anchor-name: --czjs-track;31}32@media (min-width: 768px) { .czjs-track { grid-auto-columns: 46%; } }33.czjs-track::-webkit-scrollbar { display: none; }3435.czjs-slide {36scroll-snap-align: center;37border-radius: 24px; min-height: 38svh;38display: grid; place-content: center; text-align: center;39border: 1px solid rgba(255,255,255,.16);40padding: 3rem;41}42.czjs-slide h3 { font-size: 3.2rem; font-weight: 900; color: #fff; }43.czjs-slide p { font-size: 1.5rem; color: rgba(255,255,255,.85); margin-top: .8rem; }4445/* ---- browser-generated PREV/NEXT buttons ---- */46.czjs-track::scroll-button(left) { content: "โ" / "Scroll left"; }47.czjs-track::scroll-button(right) { content: "โ" / "Scroll right"; }48.czjs-track::scroll-button(*) {49position: fixed; /* anchor-positioned against the track */50position-anchor: --czjs-track;51width: 48px; height: 48px; border-radius: 50%;52border: 1px solid rgba(255,255,255,.3);53background: rgba(16,16,28,.85); color: #fff;54font-size: 2rem; cursor: pointer;55backdrop-filter: blur(6px);56}57.czjs-track::scroll-button(left) {58position-area: center span-left; left: calc(anchor(left) + 8px);59}60.czjs-track::scroll-button(right) {61position-area: center span-right; right: calc(anchor(right) + 8px);62}63.czjs-track::scroll-button(*):hover { background: rgba(124,58,237,.8); }64.czjs-track::scroll-button(*):disabled { opacity: .25; cursor: default; }6566/* ---- browser-generated pagination dots ---- */67.czjs-track {68scroll-marker-group: after;69}70.czjs-track::scroll-marker-group {71display: grid; grid-auto-flow: column; justify-content: center;72gap: .8rem; margin-top: 1.6rem;73}74.czjs-slide::scroll-marker {75content: "" / "Go to slide";76width: 12px; height: 12px; border-radius: 50%;77background: rgba(255,255,255,.25); border: none; cursor: pointer;78}79.czjs-slide::scroll-marker:target-current {80background: linear-gradient(90deg,#7c3aed,#06b6d4);81scale: 1.25;82}8384.czjs-note { text-align:center; font-size: 1.25rem; color: rgba(255,255,255,.5); margin-top: 1.2rem; }85`}</style>8687<div className="czjs-track" tabIndex={0} aria-label="Showcase carousel">88{SLIDES.map((s) => (89<div90key={s.title}91className="czjs-slide"92style={{93background: `radial-gradient(circle at 30% 20%, ${s.hue}55, transparent 70%), rgba(255,255,255,.04)`,94}}95>96<h3 style={{ textShadow: `0 0 40px ${s.hue}` }}>{s.title}</h3>97<p>{s.text}</p>98</div>99))}100</div>101<p className="czjs-note">102arrows + dots are generated and managed entirely by the browser โ103inspect the DOM: they aren't in it.104</p>105</div>106);107}108