๐ŸŽ  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

Aurora

Conjured nightly above the workshop.

Tide

Momentum you can set a watch by.

Ember

Every idea starts as one warm point.

Bloom

Growth follows attention. Always.

Night

Rest is a load-bearing feature.

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";
2
3/* ============================================================
4 ZERO-JS CAROUSEL โ€” CSS Overflow Level 5.
5 ::scroll-button() generates real, keyboard-accessible
6 prev/next buttons; ::scroll-marker() generates the pagination
7 dots โ€” the browser manages every state. Shipped: Chrome/Edge
8 135+, Safari 18.2+. Older browsers still get a perfectly
9 usable scroll-snap carousel (the buttons/dots just don't
10 render). Not one line of JavaScript.
11 ============================================================ */
12
13const 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];
20
21export default function Carousel() {
22 return (
23 <div className="czjs">
24 <style>{`
25 .czjs-track {
26 display: grid; grid-auto-flow: column; grid-auto-columns: 88%;
27 gap: 1.6rem; overflow-x: auto; padding: 0.4rem;
28 scroll-snap-type: x mandatory;
29 scrollbar-width: none;
30 anchor-name: --czjs-track;
31 }
32 @media (min-width: 768px) { .czjs-track { grid-auto-columns: 46%; } }
33 .czjs-track::-webkit-scrollbar { display: none; }
34
35 .czjs-slide {
36 scroll-snap-align: center;
37 border-radius: 24px; min-height: 38svh;
38 display: grid; place-content: center; text-align: center;
39 border: 1px solid rgba(255,255,255,.16);
40 padding: 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; }
44
45 /* ---- 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(*) {
49 position: fixed; /* anchor-positioned against the track */
50 position-anchor: --czjs-track;
51 width: 48px; height: 48px; border-radius: 50%;
52 border: 1px solid rgba(255,255,255,.3);
53 background: rgba(16,16,28,.85); color: #fff;
54 font-size: 2rem; cursor: pointer;
55 backdrop-filter: blur(6px);
56 }
57 .czjs-track::scroll-button(left) {
58 position-area: center span-left; left: calc(anchor(left) + 8px);
59 }
60 .czjs-track::scroll-button(right) {
61 position-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; }
65
66 /* ---- browser-generated pagination dots ---- */
67 .czjs-track {
68 scroll-marker-group: after;
69 }
70 .czjs-track::scroll-marker-group {
71 display: grid; grid-auto-flow: column; justify-content: center;
72 gap: .8rem; margin-top: 1.6rem;
73 }
74 .czjs-slide::scroll-marker {
75 content: "" / "Go to slide";
76 width: 12px; height: 12px; border-radius: 50%;
77 background: rgba(255,255,255,.25); border: none; cursor: pointer;
78 }
79 .czjs-slide::scroll-marker:target-current {
80 background: linear-gradient(90deg,#7c3aed,#06b6d4);
81 scale: 1.25;
82 }
83
84 .czjs-note { text-align:center; font-size: 1.25rem; color: rgba(255,255,255,.5); margin-top: 1.2rem; }
85 `}</style>
86
87 <div className="czjs-track" tabIndex={0} aria-label="Showcase carousel">
88 {SLIDES.map((s) => (
89 <div
90 key={s.title}
91 className="czjs-slide"
92 style={{
93 background: `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">
102 arrows + dots are generated and managed entirely by the browser โ€”
103 inspect the DOM: they aren&apos;t in it.
104 </p>
105 </div>
106 );
107}
108