The CSS Overflow Module Degree 5 specification defines a few new options which are designed for creating carousel UI patterns:
- Scroll Buttons: Buttons that the browser offers, as in literal
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
parts that scroll to a selected carousel merchandise when clicked.
Chrome has prototyped these options and launched them in Chrome 135. Adam Argyle has a fantastic explainer over on the Chrome Developer weblog. Kevin Powell has an equally fantastic video the place he follows the explainer. This put up is me taking notes from them.
First, some markup:
First, let’s set these up in a CSS auto grid that shows the record objects in a single line:
.carousel {
show: grid;
grid-auto-flow: column;
}
We will tailor this so that every record merchandise takes up a certain quantity of house, 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 record objects by transferring left and proper. We will use CSS Scroll Snapping to make sure that scrolling stops on every merchandise within the heart somewhat than scrolling proper previous them.
.carousel {
show: grid;
grid-auto-flow: column;
grid-auto-columns: 40%;
hole: 2rem;
scroll-snap-type: x obligatory;
> li {
scroll-snap-align: heart;
}
}
Kevin provides a bit extra flourish to the .carousel
in order that it's simpler to see what’s occurring. Particularly, he provides a border
to your entire factor in addition to padding
for inner spacing.
Up to now, what we've got 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 combo. 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 would possibly count on, we get two new pseudo-elements for enabling and styling these buttons:
::scroll-button(left)
::scroll-button(proper)
Apparently sufficient, should 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 assist the CSS content material
property, which we use to insert a label into the buttons. Let’s preserve issues easy and keep on with “Left” and “Proper” as our labels for now:
.carousel::scroll-button(left) {
content material: "Left";
}
.carousel::scroll-button(proper) {
content material: "Proper";
}
Now we've got 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 a superb motive for it and we’ll get extra gentle shed on it in some unspecified time in the future.
However clicking the buttons on this particular instance will advance the scroll just one record 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.
Keep 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 facet and the proper button on the carousel’s proper facet:
.carousel {
/* ... */
anchor-name: --carousel; /* outline the anchor */
}
.carousel::scroll-button(*) {
place: mounted; /* set containment on the goal */
position-anchor: --carousel; /* set the anchor */
}
.carousel::scroll-button(left) {
content material: "Left";
position-area: heart left;
}
.carousel::scroll-button(proper) {
content material: "Proper";
position-area: heart 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 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. Every one is an component anchored to a selected record merchandise within the carousel in order that, when clicked, you get scrolled on to that merchandise.
We get a brand new pseudo-element for your entire 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 heart of the carousel’s inline measurement:
.carousel::scroll-marker-group {
show: flex;
justify-content: heart;
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 stable CanvasText;
border-radius: 100%;
width: 20px;
}
}
When clicking on a marker, it turns into the “lively” 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 recognize that we are able to the entire advantages of focus states in addition to the power to cycle via the carousel objects when reaching the tip of the markers. It’s wonderful what we’re getting at no cost by way of consumer 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 consumer’s movement preferences enable 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 house. Kevin needed to fiddle together with his grid-auto-columns
sizing to get issues good, however confirmed how Adam’s instance took a special sizing method. It’s a matter of fussing with issues to get them good.
That is only a tremendous early take 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 acquainted with the ideas, after which be open-minded to adjustments sooner or later because the CSS Overflow Degree 5 specification is up to date and different browsers start constructing assist.