How can I make an image carousel with only CSS?

That’s easy! Just use radio buttons and targeted labels.

Radio buttons have the (necessary) behavior of only allowing one to be selected at any one time—just like an image in our carousel.

Demo

div.wrap2 {
  float: left;
  height: 500px;
  width: 422px;
}
div.group input {
  display: none;
  left: -100%;
  position: absolute;
  top: -100%;
}
div.group input ~ div.content {
  border: solid 1px black;
  display: none;
  height: 350px;
  margin: 0px 60px;
  position: relative;
  width: 300px;
}
div.group input:checked ~ div.content {
  display: block;
}
div.group input:checked ~ label.previous,
div.group input:checked ~ label.next {
  display: block;
}
div.group label {
  background-color: #69c;
  border: solid 1px black;
  display: none;
  height: 50px;
  width: 50px;
}
img {
  left: 0;
  margin: 0 auto;
  position: absolute;
  right: 0;
}
p {
  text-align: center;
}
label {
  font-size: 4em;
  margin: 125px 0 0 0;
}
label.previous {
  float: left;
  padding: 0 0 30px 5px;
}
label.next {
  float: right;
  padding: 0 5px 25px 0;
  text-align: right;
}
<div class="wrap">
  <div class="wrap2">
    <div class="group">
      <input type="radio" name="test" id="0" value="0">
      <label for="4" class="previous">&lt;</label>
      <label for="1" class="next">&gt;</label>
      <div class="content">
        <p>panel #0</p>
        <img src="http://i.stack.imgur.com/R5yzx.jpg" width="200" height="286">
      </div>
    </div>
    <div class="group">
      <input type="radio" name="test" id="1" value="1">
      <label for="0" class="previous">&lt;</label>
      <label for="2" class="next">&gt;</label>
      <div class="content">
        <p>panel #1</p>
        <img src="http://i.stack.imgur.com/k0Hsd.jpg" width="200" height="139">
      </div>
    </div>
    <div class="group">
      <input type="radio" name="test" id="2" value="2">
      <label for="1" class="previous">&lt;</label>
      <label for="3" class="next">&gt;</label>
      <div class="content">
        <p>panel #2</p>
        <img src="http://i.stack.imgur.com/Hhl9H.jpg" width="140" height="200">
      </div>
    </div>
    <div class="group">
      <input type="radio" name="test" id="3" value="3" checked="">
      <label for="2" class="previous">&lt;</label>
      <label for="4" class="next">&gt;</label>
      <div class="content">
        <p>panel #3</p>
        <img src="http://i.stack.imgur.com/r1AyN.jpg" width="200" height="287">
      </div>
    </div>
    <div class="group">
      <input type="radio" name="test" id="4" value="4">
      <label for="3" class="previous">&lt;</label>
      <label for="0" class="next">&gt;</label>
      <div class="content">
        <p>panel #4</p>
        <img src="http://i.stack.imgur.com/EHHsa.jpg" width="96" height="139">
      </div>
    </div>
  </div>
</div>

TLDR: Important notes

  • Make sure at least one input(type="radio") is checked by default, or the whole carousel will be hidden.
  • Hide the input radios and use labels as the previous/next buttons
  • Make sure the labels correctly target the previous/next radio input (see labels section at the end on how to do the targeting)
  • Show an image when its corresponding input radio is :checked
  • Use cute kitten pictures

Explanation

Here’s what the basic HTML structure should look like:

div#holder
    div.group
        input(type="radio")
        label.previous
        label.next
        div.content
            img
    div.group
        // ... repeat as necessary

div#holder will hold all of our content in place. Then, we’ll group our radio buttons, labels, and images all under a div.group. This makes sure our radio inputs don’t suffer from destructive interference (pun).

The key is in the selectors (and the labels—make sure to read that section)

First, we’ll hide our radio buttons—they’re ugly anyway:

div.group input {
    display: none;
    position: absolute;
    top: -100%;
    left: -100%;
}

We won’t ever have to click the radio buttons. Instead, we’ll style our labels and add targets (for properties), so that they redirect the click to the appropriate radio input block.

Most of our labels should be hidden:

div.group label {
    display: none;
}

(I will omit all aesthetic styling, so as to make the styling easier to understand. You can see the better-looking version in the stack snippet.)

Except for those next to a radio input that is toggled on, or :checked

div.group input:checked ~ label.previous,
div.group input:checked ~ label.next {
    display: block;
}

In addition, the div.content following a checked input should also be displayed:

div.group input:checked ~ div.content {
    display: block;
}

However, when the radio button is not checked, div.content should be hidden:

div.group input ~ div.content {
    display: none;
    position: relative;
}

Bazinga! Now our carousel should be fully mostly functional, albeit a little ugly. Let’s move our labels to the correct position:

label.previous { float: left; }
label.next { float: right; }

And center our images within their respective divs:

img {
    left: 0;
    margin: 0 auto;
    position: absolute;
    right: 0;
}

The last step is how you set up your labels:

<input type="radio" id="1">
<label class="previous" for="0">&lt;</label>
<label class="next" for="2">&gt;</label>

Note how, given a radio input with an id of n, the label.previous will have a for attribute of (n - 1) % M and the label.next will have a for attribute of (n + 1) % M, where M is the number of images in the carousel.

Extra

If you’re using Jade (or some other template engine), you can set it up with a simple for-loop like this:

div.wrap2
    - var imgs = [[200, 286], [200, 139], [140, 200], [200, 287], [96, 139]];
    - for (var i = 0; i < imgs.length; i++)
        div.group
            input(type="radio" name="test" id="#{i}" value="#{i}" checked="#{input == 3}")
            label(for="#{(i - 1 + imgs.length) % imgs.length}").previous &lt;
            label(for="#{(i + 1) % imgs.length}").next &gt;
            div.content
                p panel ##{i}
                img(src="http://placekitten.com/g/#{imgs[i].join("https://stackoverflow.com/")}"
                    height="#{imgs[i][1]}"
                    width="#{imgs[i][0]}"
                )

Leave a Comment