Creating a CSS-only Responsive Image Slider

Neat and simple, animated responsive Image slider built with pure CSS (CSS3) – works on all modern web browsers.

Creating a CSS-only Responsive Image Slider
See the Demo

An Image slider is anything that helps you represet the content and images as creative galleries. Image and Content sliders are generally developed in JavaScript, and we have a plenty of such solutions available on the web. It doesn’t mean though, that we can’t do that in CSS.

It’s doable in CSS too, but that may turn out to be a bit complicated thing to achieve without much features.

I tried creating a CSS-only Image Slider and came up with an intermediate-level solution for that. In this article, I’m sharing how it is built—I hope you guys will find it useful.

Features

As I told you already, it doesn’t have much features but these:

  • Supports Captions
  • Control Navigation
  • Fade-in transition effect
  • Works on all modern browsers (Tested on Firefox 31.0, Chrome 36.0+)

See the demo

Without waiting much, lets get into it.

Workaround + Code

I started with the following markup structure, that seems simple.

HTML

<div class="slider-wrapper">
  <div class="slider">
    <input type="radio" name="slider" class="trigger" id="one" checked="checked" />
    <div class="slide">
      <figure class="slide-figure">
        <img class="slide-img" src="path/to/image-1.jpg" />
        <figcaption class="slide-caption"><p>...</p></figcaption>
      </figure><!-- .slide-figure -->
    </div><!-- .slide -->
    <input type="radio" name="slider" class="trigger" id="two" />
    <div class="slide">
      <figure class="slide-figure">
        <img class="slide-img" src="path/to/image-2.jpg" />
        <figcaption class="slide-caption"><p>...</p></figcaption>
      </figure><!-- .slide-figure -->
    </div><!-- .slide -->
    <input type="radio" name="slider" class="trigger" id="three" />
    <div class="slide">
      <figure class="slide-figure">
        <img class="slide-img" src="path/to/image-3.jpg" />
        <figcaption class="slide-caption"><p>...</p></figcaption>
      </figure><!-- .slide-figure -->
    </div><!-- .slide -->
  </div><!-- .slider -->
  <ul class="slider-nav">
    <li class="slider-nav__item"><label class="slider-nav__label" for="one">1</label></li>
    <li class="slider-nav__item"><label class="slider-nav__label" for="two">2</label></li>
    <li class="slider-nav__item"><label class="slider-nav__label" for="three">3</label></li>
  </ul><!-- .slider-nav -->
</div><!-- .slider-wrapper -->

You might be wondering why have I used form elements (radio buttons and labels) in the markup. For that you need to understand the basic idea of our slider.

The basic idea + Difficulties

I planned to use CSS CSS transitions and opaciyt to produce the animation effect for the slides. Both the properties are well-supported in all modern browsers.

The animation trigger control part was tricky. It was only possible with the radio inputs, input labels and CSS adjacent sibling selector.

Let me remind you again that I’m doing it with pure CSS without any JavaScript involvement, that’s why I needed the radio button trickery to scroll through the slides of the slideshow.

I’m using a separate .slide element to wrap each figure for our slider. A <figcaption> or .slide-caption will hold the caption text of each slider image inside the .slide-figure. This makes our 1 slide.

The .slider contains all the slides of our slider. Below the .slider, we have an unordered list of form labels that will act as the control navigation.

Above each slide inside the .slider, there exists a radio input with unique IDs. I’ve used form labels outside the #slider to control the radio buttons with the help of these IDs.

There’s a reason to place radio inputs above the slide – it will be easy to control and animate the slides (.slide) with the help of :checked pseudo-selector.

CSS

img {
  max-width: 100%;
  vertical-align: middle;
}

.trigger {
  display: none;
}

.slider, .slider-wrapper {
  position: relative;
  height: 250px;
}

.slide {
  background-color: black;
  width: 100%;
  overflow: hidden;
  position: absolute;
  height: 100%;
  left: 0;
  top: 0;
  z-index: 5000;
  opacity: 0;
  transition: opacity .5s ease-in-out;
}

.slide-img {
  filter: brightness(50%);
  height: 100%;
  object-fit: fill;
  display: block;
  margin: 0 auto;
}

.slide-figure {
  height: 100%;
  position: relative;
  margin: 0;
}

.slide-caption {
  position: absolute;
  bottom: 30%;
  width: calc(100% - 1rem);
  color: white;
  text-align: center;
  left: 50%;
}

.trigger:checked + .slide {
  z-index: 6000;
  opacity: 1;
}

.slider-nav {
  width: 100%;
  text-align: center;
  margin: 1rem 0;
}

.slider-nav__item {
  display: inline-block;
}

.slider-nav__label {
  font-size: 13px;
  background-color: #333;
  display: block;
  height: 2em;
  line-height: 2em;
  width: 2em;
  text-align: center;
  border-radius: 50%;
  color: white;
  cursor: pointer;
  transition: background-color .25s, color .25s ease-in-out;
}

.slider-nav__label:hover,
.slider-nav__label:active,
.slider-nav__label:focus {
  background-color: gray;
  color: black;
}

Responsiveness

@media only screen and (min-width: 1024px) {
  .slider, .slider-wrapper {
    height: 480px;
  }

  .slide-caption {
    width: 65%;
    font-size: 1.5rem;
  }
}

@media only screen and (max-width: 1023px) and (min-width: 768px) {
  .slider, .slider-wrapper {
    height: 360px;
  }

  .slide-caption {
    width: 75%;
    font-size: 1.25rem;
  }
}

@media only screen and (min-width: 768px) {
  .slide-img {
    width: 100%;
    height: auto;
  }
}

I’ve added some fancy styles to the slider to make it look good. Story begins with the responsive images which have a block-level display and 100% max-width.

Next comes the animation part. CSS3 keyframe animation makes it easy to create a fade-in animation preset to use later in our slides. If you’re new to this concept, learn more about it here.

There’s a .slider-wrapper that contains our slider (.slider) and the slider navigation (.slider-navigation). The .slider-wrapper and .slider are relatively positioned, so that we may (absolutely) position our slides one over the other, and then also postition our navigation over the slides comfortably.

The .slider-nav is outside the .slider, and whenever a navigation item (i.e. label) is pressed, it also activates the radio input of the corresponding ID.

Triggering the slider animation

So, we are able to activate the radio buttons from outside the slides (.slider) using the <label> or .slider-nav__label and we can align the slider navigation as we want. But how does it help in triggering the animation? Here comes the role of adjacent sibling selector:

.slide {
  position: absolute;
  z-index: 5000;
  ...
  opacity: 0;
  transition: opacity .5s ease-in-out;
}

The above CSS checks the slide (.slide) adjacent to the active radio input and adds an opacity fade animation because of the transition property we set for the .slide

.trigger:checked + .slide {
  z-index: 6000;
  opacity: 1;
}

All those slides which are adjacent to inactive radios will be kept hidden with the help of above CSS.

In the end, I’ve absolutely positioned the .slide and the .slider-nav and worked a bit on the alignment of both the elements.

Demo Slider

What I missed in the slider

I tried to style the active slider navigation item, but failed to achieve that, as for that I have to put the labels near the radio input elements which was killing the slider nav positioning inside the .slider.

I hope you guys enjoyed reading this, I believe this can be improved further. I welcome your thoughts, questions, and suggestions. Thanks much.