Topic: Web platform
Native CSS Carousels Are an Enhancement, Not a Rewrite
CSS Overflow 5 can generate carousel buttons and markers, but it is not Baseline yet. Start with a semantic list, scroll snap, and progressive enhancement before removing JavaScript.
Animated meme (expand/collapse)
When Chrome 135 shipped ::scroll-button() and ::scroll-marker(), “carousels no longer need JavaScript” became an irresistible headline.
It is only half true.
The browser can now generate previous and next buttons, navigation markers, current-position state, and keyboard focus behavior for a scroll container. Removing hand-written synchronization for scrollLeft, disabled states, and active dots is real progress.
But MDN still labels scroll-marker-group as limited availability and experimental. A 2026 Reddit discussion also found another boundary: native controls do not automatically loop the last item back to the first.
The reliable conclusion is not “delete the carousel library.” It is:
Build a semantic list that remains readable and horizontally scrollable without the new selectors. Add Overflow 5 controls as progressive enhancement. Keep the smallest JavaScript only when product requirements exceed the native model.
First ask whether the content needs a carousel
A carousel is excellent at fitting six things into space that reveals one. It is equally good at hiding the other five.
If every item matters, a responsive grid is usually more honest. If the content is a sequence of article steps, headings and normal document flow are easier to scan. If the homepage feels crowded, removing content may be more effective than adding navigation.
Reasonable carousel use cases are narrower:
- A related set of independently useful items with weak ordering.
- A small screen where each item needs substantial width.
- Content that remains complete when the only interaction is natural scrolling.
Make that decision before studying selectors. Otherwise, sophisticated CSS merely polishes arrows on the wrong information architecture.
The baseline should be an ordinary list
Chrome’s accessibility guidance recommends a <ul> when the content is semantically a list. That is not decorative markup. Screen readers and browsers without the new pseudo-elements still understand the relationship between items.
<section aria-labelledby="featured-title">
<h2 id="featured-title">Featured articles</h2>
<ul class="carousel">
<li data-label="Article 1">...</li>
<li data-label="Article 2">...</li>
<li data-label="Article 3">...</li>
</ul>
</section>
The first CSS layer uses mature overflow and scroll snap:
.carousel {
display: flex;
gap: 1rem;
overflow-x: auto;
overscroll-behavior-inline: contain;
scroll-snap-type: x mandatory;
}
.carousel > li {
flex: 0 0 min(85%, 24rem);
scroll-snap-align: start;
}
People using touch, a trackpad, a mouse wheel, or a scrollbar can reach every item. If every new feature disappears, the product still works. That is a fallback—not a banner asking people to switch browsers.
mandatory is not universal. If an item can be taller than the scrollport, forced snapping may make its middle difficult to reach. Use proximity or reconsider the layout in that case. Test the content before choosing the value.
Then let the browser generate controls
CSS Overflow Module Level 5 defines ::scroll-button() controls that move the scroller by a page and become disabled when no further scrolling is possible. ::scroll-marker represents an item, while :target-current identifies the active marker.
Browsers that do not support the selectors ignore these independent rules without harming the baseline:
.carousel::scroll-button(left) {
content: "←" / "Previous items";
}
.carousel::scroll-button(right) {
content: "→" / "Next items";
}
.carousel {
scroll-marker-group: after;
}
.carousel > li::scroll-marker {
content: attr(data-label);
}
.carousel > li::scroll-marker:target-current {
background: currentColor;
}
The content on a generated button is more than an arrow. Text after the slash provides an accessible name. content: "→" may look like a button without communicating its purpose to assistive technology.
Markers cannot all use an empty string either. MDN warns that content: "" creates unnamed controls. A data-label can provide “Article 2,” but automatic translation tools may not translate attributes. Multilingual sites must own those localized values.
New CSS and a little JavaScript can coexist
The immediate limitation is simple: these features are not Baseline. If your browser support matrix includes engines that have not implemented them, generated controls cannot be the only navigation path.
Animated meme (expand/collapse)
There are three sensible levels:
| Requirement | Smallest solution |
|---|---|
| Horizontally browsable cards | Semantic list, overflow, scroll snap |
| Arrows and dots in supporting browsers | Add ::scroll-button() and ::scroll-marker() |
| Identical controls across the support matrix | Keep real <button> elements and small JS, or a tested component |
The third level is not failure. Progressive enhancement exists so each layer still has another layer to stand on when it disappears.
Do not rewrite a mature, accessible, reasonably small carousel component merely to chase a new selector. Delete code when the support matrix and maintenance cost have actually changed, not when a demo makes migration look fashionable.
Which requirements still exceed the native model?
Native scroll controls are a good match for finite, manually navigated content. These features still require extra state or behavior:
- Infinite looping: Joining the final item to the first generally requires duplicated DOM, position resets, or a different product requirement.
- Autoplay: Pause and resume controls, focus and hover behavior, reduced motion, and rotation state remain necessary.
- Remote pagination: Loading near the end, retry handling, and position preservation are application logic.
- Precise analytics: Exposure, visibility thresholds, and interaction attribution do not appear automatically with an active marker.
- Complex synchronization: Thumbnails, media playback, URL state, or multiple linked scrollers need an explicit state model.
In particular, do not smuggle autoplay into a CSS animation and call it “zero JavaScript.” The WAI carousel pattern requires a stop/restart control, stops rotation when focus enters, and does not resume without an explicit request. Those are interaction requirements, not enemies of bundle size.
If the requirement is only “show four cards,” avoiding autoplay is usually best. Removing motion is cheaper than adding ten accessibility rules around it.
Accessibility cannot be outsourced to a pseudo-element
Browser-provided semantics and focus behavior remove code teams frequently get wrong. Chrome’s own guidance still says no UI is accessible out of the box; real testing remains necessary.
At minimum, verify:
- The carousel has an understandable visible heading or accessible label.
- Tab order matches visual control order;
scroll-marker-group: before/aftermatches the layout. - Every button and marker has a unique, localized name.
- Focus rings are obvious, and disabled state does not rely only on low contrast.
- Off-screen interactive content does not send keyboard users through invisible links.
- Zoom, RTL, touch, keyboard, screen readers, and reduced motion are tested.
A single-item carousel can combine a scroll-state query with interactivity: inert so non-active slides leave the focus flow. A multi-item carousel where several cards are visible should not make every other visible item inert. Focus behavior must match the visual model; it cannot be copied wholesale from a demo.
A practical adoption order
- Prove a carousel is better than a grid or normal flow for the content.
- Use semantic HTML that remains readable without JavaScript.
- Add overflow and scroll snap; test touch, keyboard, and long content.
- Put Overflow 5 behavior in independent selectors so older browsers ignore it safely.
- Test against the production browser matrix and assistive technology.
- Add the smallest JS only for real looping, autoplay, loading, or consistent-control requirements.
- Measure whether people reach later items. If not, replace the carousel with a grid.
Conclusion: a native feature is an opportunity to delete code, not an order to rewrite
The best part of ::scroll-button() and ::scroll-marker() is not a social post claiming “0 KB JavaScript.” They return scrolling, disabled state, active markers, and part of keyboard behavior to the browser, reducing how many teams rebuild the same fragile state machine.
The reliable adoption order remains boring: semantic content, a usable baseline, native enhancement, and necessary JavaScript last.
Modern CSS is powerful. Use it so unsupported browsers never need to know what they missed.