The CSS Overflow Module Stage 5 specification defines a few new options which are designed for creating carousel UI patterns:
- Scroll Buttons: Buttons that the browser gives, as in literal
<button>
parts, that scroll the carousel content material 85% of the world when clicked. - Scroll Markers: The little dots that act as anchored hyperlinks, as in literal
<a>
parts that scroll to a particular carousel merchandise when clicked.
Chrome has prototyped these options and launched them in Chrome 135. Adam Argyle has a great explainer over on the Chrome Developer weblog. Kevin Powell has an equally great video the place he follows the explainer. This put up is me taking notes from them.
First, some markup:
<ul class="carousel">
<li>...</li>
<li>...</li>
<li>...</li>
<li>...</li>
<li>...</li>
</ul>
First, let’s set these up in a CSS auto grid that shows the checklist objects in a single line:
.carousel {
show: grid;
grid-auto-flow: column;
}
We will tailor this so that every checklist merchandise takes up a certain quantity of area, say 40%
, and insert a hole
between them:
.carousel {
show: grid;
grid-auto-flow: column;
grid-auto-columns: 40%;
hole: 2rem;
}
This offers us a pleasant scrolling space to advance via the checklist objects by transferring left and proper. We will use CSS Scroll Snapping to make sure that scrolling stops on every merchandise within the middle slightly than scrolling proper previous them.
.carousel {
show: grid;
grid-auto-flow: column;
grid-auto-columns: 40%;
hole: 2rem;
scroll-snap-type: x necessary;
> li {
scroll-snap-align: middle;
}
}
Kevin provides a little bit extra flourish to the .carousel
in order that it’s simpler to see what’s happening. Particularly, he provides a border
to all the factor in addition to padding
for inside spacing.
Up to now, what we now have is a brilliant easy slider of kinds the place we are able to both scroll via objects horizontally or click on the left and proper arrows within the scroller.
We will add scroll buttons to the combination. We get two buttons, one to navigate one route and one to navigate the opposite route, which on this case is left and proper, respectively. As you may count on, we get two new pseudo-elements for enabling and styling these buttons:
::scroll-button(left)
::scroll-button(proper)
Apparently sufficient, in the event you crack open DevTools and examine the scroll buttons, they’re really uncovered with logical phrases as a substitute, ::scroll-button(inline-start)
and ::scroll-button(inline-end)
.

And each of these help the CSS content material
property, which we use to insert a label into the buttons. Let’s maintain issues easy and stick to “Left” and “Proper” as our labels for now:
.carousel::scroll-button(left) {
content material: "Left";
}
.carousel::scroll-button(proper) {
content material: "Proper";
}
Now we now have two buttons above the carousel. Clicking them both advances the carousel left or proper by 85%. Why 85%? I don’t know. And neither does Kevin. That’s simply what it says within the specification. I’m positive there’s an excellent cause for it and we’ll get extra mild shed on it sooner or later.
However clicking the buttons on this particular instance will advance the scroll just one checklist merchandise at a time as a result of we’ve set scroll snapping on it to cease at every merchandise. So, regardless that the buttons wish to advance by 85% of the scrolling space, we’re telling it to cease at every merchandise.
Bear in mind, that is solely supported in Chrome on the time of writing:
We will choose each buttons collectively in CSS, like this:
.carousel::scroll-button(left),
.carousel::scroll-button(proper) {
/* Types */
}
Or we are able to use the Common Selector:
.carousel::scroll-button(*) {
/* Types */
}
And we are able to even use newer CSS Anchor Positioning to set the left button on the carousel’s left aspect and the best button on the carousel’s proper aspect:
.carousel {
/* ... */
anchor-name: --carousel; /* outline the anchor */
}
.carousel::scroll-button(*) {
place: fastened; /* set containment on the goal */
position-anchor: --carousel; /* set the anchor */
}
.carousel::scroll-button(left) {
content material: "Left";
position-area: middle left;
}
.carousel::scroll-button(proper) {
content material: "Proper";
position-area: middle proper;
}
Discover what occurs when navigating all the best way to the left or proper of the carousel. The buttons are disabled, indicating that you’ve got reached the tip of the scrolling space. Tremendous neat! That’s one thing that’s usually in JavaScript territory, however we’re getting it at no cost.
Let’s work on the scroll markers, or these little dots that sit under the carousel’s content material. Each is an <a>
ingredient anchored to a particular checklist merchandise within the carousel in order that, when clicked, you get scrolled on to that merchandise.
We get a brand new pseudo-element for all the group of markers referred to as ::scroll-marker-group
that we are able to use to model and place the container. On this case, let’s set Flexbox on the group in order that we are able to show them on a single line and place gaps between them within the middle of the carousel’s inline dimension:
.carousel::scroll-marker-group {
show: flex;
justify-content: middle;
hole: 1rem;
}
We additionally get a brand new scroll-marker-group
property that lets us place the group both above (earlier than
) the carousel or under (after
) it:
.carousel {
/* ... */
scroll-marker-group: after; /* displayed under the content material */
}
We will model the markers themselves with the brand new ::scroll-marker
pseudo-element:
.carousel {
/* ... */
> li::scroll-marker {
content material: "";
aspect-ratio: 1;
border: 2px strong CanvasText;
border-radius: 100%;
width: 20px;
}
}
When clicking on a marker, it turns into the “energetic” merchandise of the bunch, and we get to pick and elegance it with the :target-current
pseudo-class:
li::scroll-marker:target-current {
background: CanvasText;
}
Take a second to click on across the markers. Then take a second utilizing your keyboard and admire that we are able to all the advantages of focus states in addition to the power to cycle via the carousel objects when reaching the tip of the markers. It’s superb what we’re getting at no cost when it comes to person expertise and accessibility.
We will additional model the markers when they’re hovered or in focus:
li::scroll-marker:hover,
li::scroll-marker:focus-visible {
background: LinkText;
}
And we are able to “animate” the scrolling impact by setting scroll-behavior: easy
on the scroll snapping. Adam neatly applies it when the person’s movement preferences permit it:
.carousel {
/* ... */
@media (prefers-reduced-motion: no-preference) {
scroll-behavior: easy;
}
}
Buuuuut that appears to interrupt scroll snapping a bit as a result of the scroll buttons are trying to slip issues over by 85% of the scrolling area. Kevin needed to fiddle along with his grid-auto-columns
sizing to get issues good, however confirmed how Adam’s instance took a distinct sizing method. It’s a matter of fussing with issues to get them good.
That is only a tremendous early have a look at CSS Carousels. Keep in mind that that is solely supported in Chrome 135+ on the time I’m scripting this, and it’s purely experimental. So, mess around with it, get conversant in the ideas, after which be open-minded to modifications sooner or later because the CSS Overflow Stage 5 specification is up to date and different browsers start constructing help.