Eric Niquette UI, UX, Accessibility

Building a minimalistic carousel-like feature using CSS and the browser's built-in scrollbar

Published Updated

My take on a barebones, carousel-like feature utilizing flex, overflow, and the browser's built-in scrollbar for navigation... with a few caveats you should probably keep in mind.


During the development of this website, one of the goals I had set for myself was to avoid the use of JavaScript if I could help it; keep it lean. In my search for CSS-based carousels, I came across several impressive works, but I only needed very basic functionality.

While simple, this technique is lightweight and provides a good amount of creative flexibility while remaining completely free of JavaScript, albeit with some annoying caveats and limitations.

A recent Chromium update has introduced a bug affecting scrollbar functionality in some scenarios and prevents clicking on segments of the scrollbar. See Chromium bug 1409053 for more information. Until then, please keep in mind that some examples may not function as expected.


The following is an example of the carousel's basic functionality with the addition of coloured scrollbar sections. Once you've transferred focus to one of the slides, you can press the arrow keys to navigate or use the scrollbar by dragging the thumb or by clicking on individual segments. If you're on mobile, you can tap the scrollbar sections or swipe through the slides.

Interactive demo

After transferring focus to the carousel, you can navigate using the arrow keys, the segments on the scrollbar, or by scrolling the bar or swiping on mobile.

Note that if you're using Firefox or a browser not based on the WebKit or Blink engine, you probably won't see a custom scrollbar and likely will only see a coloured bar. It should otherwise not impact the general functionality.


Screenshot of a sample carousel with vivid colours delineating individual slides in the scroll bar

How it works

The basic premise involves placing a set of slides off-screen in an overflowed container, which then be brought into view by scrolling. With the help of the scroll-snap-type property, the scrollbar gently snaps to the center of the slides. The scrollbar can then be customized to blend in with the rest of the design and serves as the navigation.


As far as I can tell, this should be pretty accessible. You can navigate through the slides using the arrow keys, tab through links, and the content is not excluded from the DOM so it's entirely accessible to screen readers. When transferring focus to a slide, it scrolls into view and doesn't break or override default browser behavior in any way.

It can, however, be difficult to understand that scrolling is required, particularly if the scrollbar's design isn't too intuitive. This is especially true in Firefox due to the browser's modern and thin design scrollbar design. This could potentially be mitigated by an icon, a "swipe me" animation, or some other explanatory text if desired.

Most browsers will automatically transfer focus to scrolling elements, though you could manually set a tabindex="0" value to the container to meet WCAG Success Criterion 2.1.1.


Given its minimal nature, there are a few limitations to this solution:

  • Scrollbar customization is a Blink and WebKit feature, so it should work on Chrome, Edge, Brave, etc. Firefox, however, does not support full customization and instead supports the WCAG specs, which are — by comparison — rather restrictive. While it doesn't break functionality, it certainly does make it less visually attractive.
  • Mobile browsers do not allow "picking up" and dragging scrollbar thumbs. This can cause scrollbars to feel counterintuitive if styled improperly. It does, however, natively allow tapping of the track to move forward and back, as well as swiping through individual slides.
  • You cannot skip directly to a specific entry using the scrollbar and must cycle through the previous entries individually to reach a specific item. In other words, you can't jump from the first to the third slide without scrolling past the second. While it is possible to provide direct links to specific slides, this unfortunately populates an entry to the browser's history.
  • Scrollbars have a lot of design limitations. For example, you cannot specify a value for the width of the bar. Instead, we have to rely on margins to trim the sides, which can be a little fiddly.
  • You cannot infinitely scroll through the items and automatically loop back. It's a linear start-to-end scroll that stops at the last entry.


Basic implementation

We need to create a series of slides that we'll be cycling through. You can add as many as you need but keep in mind that the larger the total width, the smaller the scrollbar's thumb will be.

<div class="carousel">
  <div class="slide" id="slide1">
    <h2>Slide 1</h2>
  <div class="slide" id="slide2">
    <h2>Slide 2</h2>
  <div class="slide" id="slide3">
    <h2>Slide 3</h2>
  <div class="slide" id="slide4">
    <h2>Slide 4</h2>

Using the flex display mode, we can have them line up side by side. The flex-wrap: nowrap property prevents them from breaking to new lines. The overflow property will create a scrolling area to accommodate the contents' width.

By adding scroll-snap-type, the browser will to snap to the elements. With scroll-snap-align, we set the desired location of that snap to the center of the slide.

.carousel {
  display: flex;
  flex-wrap: nowrap;
  gap: 5%;
  overflow-x: scroll;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;

.slide {
  box-sizing: border-box;
  flex-shrink: 0;
  margin-bottom: 1rem;
  scroll-snap-align: center;
  width: 100%;

Customizing the scrollbar

Now that we have content scrolling in a box, we can customize the scrollbar to blend in with the website's UI. For non webkit-based browsers, the best we can do is set the scrollbar-color property.

It's worth noting that the scrollbar's length can only be controlled by using margins. I'm using fairly random values for the sake of this example so you'll need to customize it to suit your layout's width.

.carousel {
  scrollbar-color: #AA6262 transparent;

.carousel::-webkit-scrollbar {
  height: 2rem;

  .carousel::-webkit-scrollbar-track {
    background: gainsboro;
    padding: 0.625rem;
    margin: 0 10vw; /* Customize this value */

  .carousel::-webkit-scrollbar-thumb {
    background-color: slategrey;
    border: 0.5rem solid gainsboro;

Other customizations

Adding space between slides

You can control the whitespace that appears between individual slides as you scroll with the gap attribute.

If you do not want to have a gap, keep in mind that when your container scales to a decimal value you may see a ghostly half-pixel line of the previous slide as your move through the entries. It's really only apparent when your slides have strong contrasting colours, but something to keep in mind.

.carousel { gap: 2rem; }

Linking to a specific slide

You can link to a specific slide by adding an ID to it.

<div class="slide" id="slide2"></div>

When linked to, the browser will automatically scroll over to that slide to slide. Note that this will add an entry to the user's history, so use with caution.

<a href="#slide2">Link to slide 2</a>

Previous and next buttons

Navigation buttons can be added but will add a bit of fluff to your HTML as you need to add links in every slide. Every activation will also add an entry to the user's history on every click, so use with caution.

<div class="slide" id="slide1">
  <a href="#slide4">Previous</a>
  <a href="#slide2">Next</a>
<div class="slide" id="slide2">
  <a href="#slide1">Previous</a>
  <a href="#slide3">Next</a>
<div class="slide" id="slide3">
  <a href="#slide2">Previous</a>
  <a href="#slide4">Next</a>
<div class="slide" id="slide4">
  <a href="#slide3">Previous</a>
  <a href="#slide1">Next</a>

Coloured scrollbar sections

As seen in the previous demo, I've added coloured markings to the scrollbar. This is done using a gradient background with overlapping segment start and stop points, creating solid lines. It can be finnicky to initially set up, but it does provide visual breakpoints.

The border on the scrollbar track makes the bar smaller by blending with the background colour, whereas the one on the thumb increases its size for a bit of extra contrast.

.carousel::-webkit-scrollbar-track {
  border-top: 0.5rem solid white;
  border-bottom: 0.5rem solid white;
  background: linear-gradient(
    to right,
    linen 25%,
    lightsalmon 25%,
    lightsalmon 50%,
    lightblue 50%,
    lightblue 75%,
    gainsboro 75%

.carousel::-webkit-scrollbar-thumb {
  border: 0.5rem solid slategrey;

Bullets or markers

To mimic the look of a classic carousel, we can add bullets or markers to the scrollbar with a little bit of work. The basic idea is to place a series of background images — that look like list bullets — to the scrollbar's track at specific intervals.

Note that this positioning is rarely pixel-perfect; it'll usually end up pixel or less off. Instead of trying to get it to align to a fraction of a percent, I use a slightly larger image as my thumb than I do in the background to compensate and hide the gap.


Example of an image-based scrollbar track that mimics the look of a classic carousel navigation scheme

.carousel::-webkit-scrollbar-track {
  background-image: url('dot.svg'), url('dot.svg'), url('dot.svg'), url('dot.svg');
  background-repeat: no-repeat;
  background-position: 10%, 37%, 63%, 90%; /* Customize these values */

.carousel::-webkit-scrollbar-thumb {
  background: url('marker.svg') center no-repeat;

Other resources

Pure CSS Carousel with Thumbnails

Ronny Siikaluoma | CodePen

CSS-Tricks Card Carousel

William Goldsworthy | CodePen

Infinite autoplay carousel

Jack Oliver | CodePen

CSS Carousel with keyboard controls

David Lewis | CodePen