demo · v135
CSS-only carousel: ::column snap + ::scroll-marker dots
Both ::column and ::scroll-marker shipped in Chrome 135, and they compose. ::column { scroll-snap-align: start } turns each multi-column fragment into a snap target; ::scroll-marker on each card generates a dot row with the active slide highlighted automatically via :target-current — no JavaScript, no wrapper divs, no bookkeeping.
No wrapper divs
The markup is a flat list of .card elements. The multicol engine fragments them into columns; ::column makes each fragment a snap target. No slide containers needed.
Dots for free
Each card owns a ::scroll-marker. The browser collects them into the container's ::scroll-marker-group and toggles :target-current on the active one. Zero JS.
Both features, one milestone
::column and ::scroll-marker both shipped in Chrome 135. Together they deliver the spec's “CSS-only carousel” vision: snap behaviour + navigation indicators, pure CSS.
Accessible by default
Markers are focusable and keyboard-navigable out of the box — the browser exposes them as links in the accessibility tree. No ARIA dance required.
Style the active dot
Use ::scroll-marker:target-current to style the in-view dot. Here it fills solid black. You could animate it, scale it, or change its shape entirely — it's just a pseudo-element.
The complete CSS
Five declarations, zero JS, works from a flat card list. See the code panel below for the exact rules that power this carousel.
the complete CSS
.carousel {
columns: 1; /* one card = one column */
column-fill: auto;
height: 260px; /* fixed height so multicol fragments horizontally */
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-behavior: smooth;
}
/* ::column — Chrome 135+: each generated fragment is a snap target */
.carousel::column {
scroll-snap-align: start;
}
/* ::scroll-marker-group — Chrome 135+: the dot container */
.carousel::scroll-marker-group {
display: flex;
gap: 8px;
justify-content: center;
padding: 12px 0;
}
/* ::scroll-marker — Chrome 135+: one dot per card */
.card::scroll-marker {
content: "";
width: 10px;
height: 10px;
border: 2px solid black;
background: white;
}
.card::scroll-marker:target-current {
background: black; /* active dot */
}