Skip to content

CSS slideshows with scroll snapping

I was redoing my portfolio page recently, and really wanted to turn it into an avant-garde slideshow of sorts, going over the various projects I have worked on over the years, with pictures and texts scattered around. And... that required me to make a slideshow!

A screenshot of the final CSS-only slideshow that changes slides when the user presses the left or right arrows. Play with it here.

Now, my website is 100% JavaScript-free, as a fun, self-imposed challenge—and I wanted to keep that going with the Projects page.

Radio buttons? That's so 2000s...

The usual trick to use when making a CSS-interactive page is to save state with a <input type="checkbox"> or <input type="radio">, read the state with the ~ and + sibling selectors, and define any clickable areas with <label>-s. It is a very universal solution, and I use it for making my site's menu collapse on small screens:

Here's a brief example of how the technique works, if you haven't seen it before:

Show code
<input type="checkbox" id="nav-toggle"/> <!-- A checkbox can store 1 bit of state -->
<nav>
  <label class="nav-toggle-label" for="nav-toggle" title="Open menu"></label> <!-- A label can toggle any checkbox by ID -->
  <a href="/about/">About</a> <!-- ... content ... -->
</nav>
#nav-toggle {
  display: none; /* Hide the checkbox itself */
}
#nav-toggle ~ nav {
  max-height: 6rem; /* However tall the menu is when it should be closed, could be 0 */
  transition: max-height 0.3s;
  overflow-y: hidden; /* Hide the extra links of the menu */
}
#nav-toggle:checked ~ nav {
  max-height: 40rem; /* Expand the menu to some large size it won't overflow */
}
.nav-toggle-label {
  /* Style and possition the label as needed */
}
Menu
An example of the usual checkbox-based collapsible menu

Naturally, the same thing could be used for a slideshow. You would want to have each radio button right before the slide it refers to, so that you can use the + next-sibling CSS selector and not have IDs inside your CSS; and the rest is wiring up the labels and transitions/animations as needed.

Here is a minimal example:

Show code
<div class="slides-container">
  <!-- Slide 0: -->
  <input type="radio" name="slides" id="show-slide-0" checked/>
  <div class="slide">
    <label class="next-slide" for="show-slide-1">Next</label>
  </div>
  <!-- Slide 1: -->
  <input type="radio" name="slides" id="show-slide-1"/>
  <div class="slide">
    <label class="prev-slide" for="show-slide-0">Prev</label>
    <label class="next-slide" for="show-slide-2">Next</label>
  </div>
  <!-- Slide 2, etc. .. -->
</div>
.slides-container {
  overflow: hidden;
  position: relative;
}
.slide {
  position: absolute;
  left: -100%; /* Hide slide left of the screen by default */
  transition: left 0.5s; /* Animate slides */
  top: 0;
  width: 100%;
  height: 100%;
}
input:checked ~ .slide {
  left: 100%; /* Reposition slide right of the screen when it's after the selected slide */
}
input:checked + .slide {
  left: 0; /* Show slide when radio is checked */
}
1
2
3
Example slideshow with radio buttons

However, I've used radio buttons and checkboxes before, and I wanted to try something new.

Something novel.

Something that caught my attention the moment I read the property name.

Scroll-based slideshows 😎

Enter, scroll-snap-type and scroll-snap-stop.

Those two properties let you define how and where the browser should attempt to snap the scrolling position to. For example, you can use these two properties for a JavaScript-free version of those popular single-page websites where scrolling down reveals the next section of the webpage all at once.

Combined with scroll-behavior: smooth, you can make it so that transitions between slides are smoothened out, no matter how the user navigates between them. And for the navigation itself, we can use links with targets. This way, it's even possible to share the link to a specific slide.

The only thing left for us to do is to arrange the slides horizontally (or in horizontally, if you want to), make them take up the whole screen, and voila: we've got a slideshow.

Show code
<div class="slides-container">
  <div class="slide" id="slide-0">
    <a href="#slide-1" class="next-slide">Next</a>
  </div>
  <div class="slide" id="slide-1">
    <a href="#slide-0" class="prev-slide">Prev</a>
    <a href="#slide-2" class="next-slide">Next</a>
  </div>
  <!-- Etc. -->
</div>
.slides-container {
  overflow-x: scroll;
  scroll-behavior: smooth;
  scroll-snap-type: x mandatory;
  display: flex; /* To arrange the slides */
  flex-direction: row;
}
.slide {
  scroll-snap-stop: always;
  /* scroll-snap-align might be useful if you have slides horizontally larger than one container width */
  width: 100%;
  flex-shrink: 0; /* To make sure they overflow */
  /* overflow-y: auto; - If you want slides to be scrollable vertically, as I did for the last slide on my portfolio */
  /* scrollbar-width: none; - if you want to hide the scrollbar completely */
  /* scrollbar-color: black transparent; - to get the cool black scrollbar look I used here */
}
Example slideshow with scroll snapping—feel free to play around with it

Best of all, using scrolling for the slideshow allows the user to move around with their mouse's scroll wheel or with the arrow keys, both of which would require JavaScript to achieve with radio buttons.

(Technically, there might be ways to (ab)use focus to move between slides with arrow keys, but chances are you would lose the ability to put links on slides)

Conclusion

Comparing the pros and cons for each of the two types of CSS-only slideshows discussed in this article, we can see that...

Radio-button-based slideshows:

Meanwhile, scroll-snap-based slideshows:

Personally, I quite enjoyed working with the scroll-* CSS properties. It adds a whole new dimension to page styling which was previously achievable only with extremely taxing scroll-linked event in JavaScript. And in a slideshow, using scroll snapping lets users navigate the page by scrolling, swiping, or using the keyboard, none of which is possible with a radio-button-based slideshow.

A curious snag I had in the actual portfolio page was that I initially tried linking to only to a smaller part of the slide (the text box), instead of the whole slide. Unfortunately, while this worked fine on Firefox, Chrome ended up sometimes not scrolling to the element if it was near the edge of a slide... so in the end, I resorted to putting IDs on the slides themselves.

But that's enough from me! Have you made a slideshow with CSS? I hope this article proves inspiring in case you ever need to do that!


  1. Though, scroll snapping polyfills exist.↩︎