How To: Pure CSS Carousel

May 23, 2016
Css, Design
~6min
Share on Twitter

The carousel can be pretty daunting. There are so many options, features, devices/browsers to please. Do you even need a carousel? 😕

“No!!! Seriously, you really shouldn’t” — shouldiuseacarousel.com(love the inline comments in the markup, check the console 😂)

If you decide you need a carousel, you don’t have to create one yourself. You might look at slick carousel or something from a large UI framework such as Bootstrap.

And this is where the disclaimer comes in 👉 If you’re looking for a feature rich carousel, a pure CSS “carousel”(heavy emphasis on the quotes) is not for you. You can close the browser tab, continue with your carousel and most likely no kittens will come to harm 😉

For those intrigued or looking to save kittens, let’s explore! 🔍

For those in camp TL;DR, Here’s a demo!

Check out this pen by Jhey (@jh3y) on CodePen.

A Background for Pure CSS Components permalink

Doing things in pure CSS as opposed to with a hint of JavaScript is not something new. I’ve written before about thinking CSS first.

The capability to create this “carousel” example isn’t something new that’s sprung up. A new CSS feature hasn’t landed. What it is, is another case of thinking outside the box a little and getting CSS to do more heavy lifting for us. Most pure CSS tricks come from observing how elements respond to user interaction 😎

The most simple pure CSS solution most will be familiar with is to show something like a tooltip or a menu. Hover state provides an opportunity to alter render state without touching any JavaScript.

The “carousel” trick 🎩 permalink

So, what’s the trick for getting this “carousel” to work?

The use of the label element in combination with input elements of type radio. In particular we leverage the for and checked attributes of each. We use the sibling combinator(~) and preprocessor looping. We can use these to generate nth-of-type selectors for the number of slides we are showing.

We are going to explore two styles of “carousel”. One will give us the sliding effect from side to side. The other will be more of a fade in/out deal(check the demo quick to see what I mean).

Consider the following markup which will give us the latter style.

NOTE; The only difference in markup structure is that we introduce a “track” element. The track wraps our slide elements for the sliding style.


            html
            
          <ul class="carousel my-carousel">
    <input class="carousel__activator" type="radio" id="A" name="activator" checked="checked"/>
    <input class="carousel__activator" type="radio" id="B" name="activator"/>
    <input class="carousel__activator" type="radio" id="C" name="activator"/>
    <div class="carousel__controls">
      <label class="carousel__control carousel__control--backward" for="B"></label>
      <label class="carousel__control carousel__control--forward" for="C"></label>
    </div>
    <div class="carousel__controls">
      <label class="carousel__control carousel__control--backward" for="C"></label>
      <label class="carousel__control carousel__control--forward" for="A"></label>
    </div>
    <div class="carousel__controls">
      <label class="carousel__control carousel__control--backward" for="A"></label>
      <label class="carousel__control carousel__control--forward" for="B"></label>
    </div>
    <li class="carousel__slide">
      <h1>A</h1>
    </li>
    <li class="carousel__slide">
      <h1>B</h1>
    </li>
    <li class="carousel__slide">
      <h1>C</h1>
    </li>
    <div class="carousel__indicators">
      <label class="carousel__indicator" for="A"></label>
      <label class="carousel__indicator" for="B"></label>
      <label class="carousel__indicator" for="C"></label>
    </div>
  </ul>

Got an idea of how it might work? If not consider this demo that lays out the elements with no “carousel” styling. Click some of the elements, in particular, the labels. It should make sense and you might be able to proceed with implementing it yourself based off this 👍

Clicking about a bit we see that clicking a label element will check the related radio button. The link is between the radio ID and the for attribute.

The sibling combinator ~ permalink

The order in the DOM is important. We welcome back the sibling combinator that I’ve written about before.

The sibling combinator enables us to select adjacent elements. Thos adjacent elements follow a given element.

Each time our input type radio changes, a new input becomes :checked. At this point we can say “Hey! show the relevant slide and controls that follows this particular checked input”. For this reason our input elements are first within the carousel.

Take this example;


            css
            
          .carousel__activator:nth-of-type(1):checked ~ .carousel__slide:nth-of-type(1) {
  display: block;
  animation: showSlide 0.5s forwards;
}

Here we are saying, “when the first input is active, then make the first slide visible”.

The use of the sibling combinator is key here.

nth-of-type selector permalink

The other important CSS feature we are leveraging is the nth-of-type selector. This selector gives us a little more freedom with the structure of our markup.

In this example we cater for five slides so we go up to nth-of-type(5). We can leverage CSS preprocessor looping to generate them. For a more robust solution we may tell our preprocessor that the number of slides can go up to 10.

The code used to generate these rules would be something like;


            stylus
            
          $panels = 10
for $num in (1..$panels)
  .carousel__activator:nth-of-type({$num}) ~ .carousel__slide:nth-of-type({$num})
    display block
    animation showSlide 0.5s forwards

This particular snippet is using Stylus. Using other preprocessors like SASS/LESS would be no issue though.

Styling it up — Fade in/out permalink

As mentioned before, we are going to produce two styles of carousel.

Our markup structure before was;


            html
            
          <ul class="carousel my-carousel">
    <input class="carousel__activator" type="radio" id="A" name="activator" checked="checked"/>
    <input class="carousel__activator" type="radio" id="B" name="activator"/>
    <input class="carousel__activator" type="radio" id="C" name="activator"/>
    <div class="carousel__controls">
      <label class="carousel__control carousel__control--backward" for="B"></label>
      <label class="carousel__control carousel__control--forward" for="C"></label>
    </div>
    <div class="carousel__controls">
      <label class="carousel__control carousel__control--backward" for="C"></label>
      <label class="carousel__control carousel__control--forward" for="A"></label>
    </div>
    <div class="carousel__controls">
      <label class="carousel__control carousel__control--backward" for="A"></label>
      <label class="carousel__control carousel__control--forward" for="B"></label>
    </div>
    <li class="carousel__slide">
      <h1>A</h1>
    </li>
    <li class="carousel__slide">
      <h1>B</h1>
    </li>
    <li class="carousel__slide">
      <h1>C</h1>
    </li>
    <div class="carousel__indicators">
      <label class="carousel__indicator" for="A"></label>
      <label class="carousel__indicator" for="B"></label>
      <label class="carousel__indicator" for="C"></label>
    </div>
  </ul>

By default, for the fade in/out style we will hide all our slides using the display property. We also position them absolutely within our carousel element.


            css
            
          .carousel__slide {
  height: 100%;
  right: 0;
  position: absolute;
  display: none;
  overflow-y: auto;
  top: 0;
  left: 0;
}

This ensures that our slides fill out the carousel element.

We then toggle the visibility of our selected slide using the ~ combinator;


            css
            
          .carousel__activator:nth-of-type(1):checked ~ .carousel__slide:nth-of-type(1) {
  display: block;
  animation: showSlide 0.5s forwards;
}

We can add an animation to our slide content so that they fade in also at this point to give a nice fade in effect.

It’s important to note that whilst showing a slide, we also show relevant controls for that slide. This applies to both styles of carousel.


            css
            
          .carousel__activator:nth-of-type(1):checked ~ .carousel__controls:nth-of-type(1) {
  display: block;
}

All that’s left to do from here with this style is add some extra nice touches and style themes that we desire.

Styling it up — Sliding permalink

The sliding style carousel works different to our fade in/out style. As such it requires a small tweak to the markup. We need to wrap our carousel__slide elements inside a container. We will call this element the carousel__track.


            html
            
          <div class="carousel__track">
  <div class="carousel__slide">
    <h1>A</h1>
  <div>
  <div class="carousel__slide">
    <h1>B</h1>
  <div>
</div>

The “trick” to making the slide work is to line up all slides side by side within the track. We then slide the track element within the carousel using CSS transforms. This will give the effect of our slides sliding from side to side.


            css
            
          .carousel__track .carousel__slide:nth-of-type(1) {
  transform: translateX(0%);
}
.carousel__track .carousel__slide:nth-of-type(2) {
  transform: translateX(100%);
}
.carousel__track .carousel__slide:nth-of-type(3) {
  transform: translateX(200%);
}

When a radio input becomes :checked we translate the track on the x-axis, scaled by the nth-child value. For example, to show the second slide we will translate our track on the x-axis by -100%.


            css
            
          .carousel__activator:nth-of-type(2):checked ~ .carousel__track {
  transform: translateX(-100%);
}

The scale is;


            stylus
            
          transform: translateX(-(($num - 1) * 100%))

We do need to make sure our track element has the transition property set. This is so that are sliding is not instant and we actually get the animated effect;


            css
            
          .carousel__track { transition: transform 0.5s; }

That’s all there is to it for sliding specific styles. Both styles share many of the styling rules.

Extra nice touches permalink

We can add some nice touches like showing indicators and extra controls. For example, here are the indicators for our carousels;


            css
            
          .carousel__indicator {
  height: 15px;
  width: 15px;
  border-radius: 100%;
  display: inline-block;
  z-index: 2;
  cursor: pointer;
  opacity: 0.35;
  margin: 0 2.5px 0 2.5px;
}
.carousel__indicator:hover {
  opacity: 0.75;
}

And this is how you show them as active when the appropriate slide is being shown.


            css
            
          .carousel__activator:nth-of-type(1):checked ~ .carousel__indicators .carousel__indicator:nth-of-type(1) {
  opacity: 1;
}

All that’s left to do is position things where you desire and theme them up!

A note on accessibility permalink

Accessibility is important when developing for the web. With pure CSS carousels we can strive to make things pretty accessible 👍 We are providing a list of content. We make a slight alteration to our implementation. We make it so that all content is always visible but not always in the carousel viewbox. That way, a screen reader such as OSX’s Voiceover Utility will read out all our slides to the user as intended.

Our slides before had top, rightand leftdefined for them with absolute positioning. Let’s make it so that only the visible slide gets this positioning and they are all visible at all times;


            css
            
          .carousel__activator:nth-of-type(1):checked ~ .carousel__slide:nth-of-type(1) {
  top: 0;
  left: 0;
  right: 0;
  animation: showSlide 0.5s forwards;
}
.carousel__slide {
  height: 100%;
  position: absolute;
  overflow-y: auto;
}

We also remove the list style from our carousel so the VoiceOver doesn’t tell us we have an empty bullet for each slide 👍


            css
            
          .carousel {
  height: 300px;
  width: 400px;
  overflow: hidden;
  text-align: center;
  position: relative;
  padding: 0;
  list-style: none;
}

For our markup, let’s make the slider version not use a list for the container. The track element inside it can become a list. Remove the padding and margin from the track;


            css
            
          .carousel__track {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  padding: 0;
  margin: 0;
  transition: transform 0.5s;
}

That’s it! permalink

We’ve explored creating pure CSS carousels. Taking advantage of element behavior and CSS features, we can do some pretty neat things. Which style of CSS only “carousel” do you prefer? Or should we banish carousels altogether? What else could we come up with? Be sure to check out the demo.

Check out this pen by Jhey (@jh3y) on CodePen.