This demo is a carousel of single pages, with each item taking up the full page. We have included scroll markers to enable the user to navigate to any page with the click of a marker.
HTML
The HTML consists of an unordered list, with each list item containing some sample content:
<ul>
<li>
<h2>Page 1</h2>
</li>
<li>
<h2>Page 2</h2>
</li>
<li>
<h2>Page 3</h2>
</li>
<li>
<h2>Page 4</h2>
</li>
</ul>
CSS
We first convert our <ul> into a carousel by setting the display to flex, creating a single, non-wrapping row of <li> elements. The overflow-x property is set to auto, meaning if the items overflow their container on the x-axis, the content will scroll horizontally. We then convert the <ul> into a scroll-snap container, ensuring that items always snap into place when the container is scrolled with a scroll-snap-type value of mandatory.
ul {
display: flex;
gap: 4vw;
padding-left: 0;
overflow-x: auto;
overscroll-behavior-x: contain;
scroll-snap-type: x mandatory;
}
Next, we style the <li> elements, using the flex property to make them 100% of the width of the container. The scroll-snap-align value of start causes the left-hand side of the left-most visible item to snap to the left edge of the container when the content is scrolled.
li {
list-style-type: none;
background-color: #eee;
flex: 0 0 100%;
height: 200px;
padding-top: 20px;
scroll-snap-align: start;
text-align: center;
}
Next, the list's scroll-marker-group property is set to after, so the ::scroll-marker-group pseudo-element is placed after the list's DOM content in the tabbing order and layout box order; this means it comes after the scroll buttons:
ul {
scroll-marker-group: after;
}
Next, the list's ::scroll-marker-group pseudo-element is laid out using flexbox, with a justify-content value of of center and a gap of 20px so that its children (the ::scroll-marker pseudo-elements) are centered inside the ::scroll-marker-group with a gap between each one.
ul::scroll-marker-group {
display: flex;
justify-content: center;
gap: 20px;
}
Next, the scroll markers themselves are styled. The look of each one is handled by setting width, height, background-color, border, and border-radius, but we also need to set a non-none value for the content, property so they are actually generated.
li::scroll-marker {
width: 16px;
height: 16px;
background-color: transparent;
border: 2px solid black;
border-radius: 50%;
content: "";
}
Note: Generated content is inline by default; we can apply width and height to our scroll markers because they are being laid out as flex items.
Finally, the :target-current pseudo-class is used to select whichever scroll marker corresponds to the currently visible "page", highlighting how far the user has scrolled through the content. In this case, we set the background-color to black so it is styled as a filled-in circle.
li::scroll-marker:target-current {
background-color: black;
}
Result