CSS3 Powered Slider With Autoplay Tutorial

Reading Time: 8 minutes

Do you believe that it is possible to create autoplay slider only with HTML5 and CSS3? Seriously, you don’t need a single line of JavaScript! There is a bunch of various sliders using either vanilla JavaScript or jQuery library. Honestly, it got kinda boring. Why not to make something different? Ditch the old-fashioned scripted sliders and get your hands on perks offered by CSS3. In this tutorial, you will learn to create slider with autoplay using only CSS. Are you ready to get started?

You can see the slider from this tutorial in action on Codepen and download the source files from my GitHub repository.

Gathering Assets

Let’s start, as usually, by gathering the assets your are going to use in this tutorial. In this CSS3 Powered Slider, you will not use any framework, icon fonts or fonts in general. You will only “need” reset stylesheet to “reset” and unity the styles across available browsers and autoprefixer plugin that will take care about vendor prefixes for partially supported CSS properties. That’s all and we can move to creating HTML structure.

Note: Images used in this tutorial for individual slides are provided by unsplash and their api. These images are published under Creative Commons Zero license. You can use them in your projects as you want for free.

Creating Slider Structure

Structure of the slider will be pretty simple. The whole slider will consist of three main building blocks. One block will be for hidden checkbox with id and namesliderSwitchand attribute “hidden” to hide the checkbox for all supported browsers and devices. For the rest, we will create a CSS fallback. This checkbox will be used for toggling the autoplay functionality. Second block will be a div with class “slider”. In this div will be nested unordered list with classslider__list” composed of four list items for individual slides. Inside every list item will be img element for pictures.

The last building blog of this CSS3 powered slider will be a div with class “slider__control” containing label element with for attribute of “sliderSwitch”. This label will be wired to hidden checkbox in the top of HTML.

<input id="sliderSwitch" class="slider__switch" type="checkbox" name="sliderSwitch" hidden />
<div class="slider">
 <ul class="slider__list">
  <li class="slider__slide"><img src="https://source.unsplash.com/rHi-KR_NA24/650x420" alt="Slide image number 1 with plane" /></li>
  <li class="slider__slide"><img src="https://source.unsplash.com/FxU8KV7psMY/650x420" alt="Slide image number 2 with Golden Gate Bridge" /></li>
  <li class="slider__slide"><img src="https://source.unsplash.com/Aa8_X_YgrO4/650x420" alt="Slide image number 3 with Flatiron Building in New York" /></li>
  <li class="slider__slide"><img src="https://source.unsplash.com/8bzsuFNkiT8/650x420" alt="Slide image number 4 with mountain climber" /></li>
 </ul>
</div>
<div class="slider__control">
 <label for="sliderSwitch"></label>
</div>

Adding Styles

Next step, after creating structure for slider is to style it appropriately. If you have included reset stylesheet mentioned in the beginning, most of the styling work – resetting margins and padding and removing the dots on the left side of list items – on slider list is already done. That being said, we can go right to setting the base – styling the body element and creating fallback for hidden attribute. Styling body will be simple and quick – just a little change in background color to light grey (#f1f1f1) and font-size to “100%”.

Fallback for hidden attribute will be even easier – just set its display property to “none”.

Note: I also included a SASS function to help me convert pixels to rems. For those of you who are not familiar with SASS, don’t try to use it in plain CSS. It will not work.

// Remy function to convert pixels to rems
@function remy($value) {
 @return ($value / 16px) * 1rem;
}
/**
 * Base
 */
body {
 font-size: 100%;
 background: #f1f1f1;
}
/* Fallback for hidden attribute */
hidden {display: none;}

Next thing is to create keyframe that will be used to move the unordered list (slider__list”) when autoplay will be enabled. This keyframe will compose of four steps (0%, 25%, 50%, 100%) each changing left property. The images used in this tutorial are 650 pixels wide and 420 pixels high. So in every step we will subtract the width of the slide (650px) from left. You can also use right property and add 650px instead.

/**
 * Keyframes for autoplay
 */
@keyframes autoplay {
 /* position of the first slide */
 0% {left: 0;}
 /* position of the second slide */
 25% {left: remy(-650px);}
 /* position of the third slide */
 50% {left: remy(-1300px);}
 /* position of the fourth slide */
 100% {left: remy(-1950px);}
}

Now, let’s style the slider. We have to set its width to the width of the slide and the same for height. Overflow have to be set to “hidden”, so the inactive slides are not visible. I also center the slider using margin-left and margin-right set to “auto”. The idea behind the slider is to move the list of slides inside the “slider” div. Meaning, we have to set the position of the “slider” div to “relative”. Last, I added light box-shadow to get rid of flatness.

/**
 * Slider
 */
.slider {
 position: relative;
 /* top margin is for purposes of demo */
 margin-top: 3rem;
 margin-right: auto;
 margin-left: auto;
 overflow: hidden;
 width: remy(650px);
 height: remy(420px);
 box-shadow: 0 4px 14px rgba(0,0,0,.25);
}

Styling of the list and its items will be fast. We will set position of list “slider__list” to “absolute”, left to “0” and width to 2600px. To explain it, width of “slider__list” is counted by multiplying the number of slides by their width. Basically, we want to create something like a film strip. List items will have float property set to “left” to assemble them side by side.

.slider__list {
 position: absolute;
 left: 0;
 width: remy(2600px);
}
.slider__slide {float: left;}

Now comes the hardest part – styling of the label. It is the hardest pat because I customized its styling to make it look like a switch. Since it would take almost a book to describe all the steps used to create the switch style, I will rather skip it and just give you the code.

Note: I set the slider__control width to fixed size and margin right and left to center the switch.

/**
 * Slider control
 */
.slider__control {
 margin-right: auto;
 margin-left: auto;
 width: remy(72px);
 font-family: sans-serif;
}
.slider__control label {
 position: relative;
 display: block;
 margin-top: 2rem;
 margin-bottom: 1rem;
 width: 4.5rem;
 height: 2rem;
 font-size: 1rem;
 font-weight: normal;
 line-height: 1.5;
 color: transparent;
 background: #ddd;
 border-radius: 2rem;
 cursor: pointer;
 transition: left 0.15s ease-out;
 &:before {
  content: "autoplay";
  position: absolute;
  top: 2.5rem;
  left: 0;
  color: #333;
  font-size: .95rem;
  font-weight: bold;
  text-transform: uppercase;
 }
 &:after {
  content: "";
  position: absolute;
  top: .25rem;
  left: .25rem;
  display: block;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: 2rem;
  background: #fff;
  transition: left 0.15s ease-out;
  transform: translate3d(0, 0, 0);
 }
}

The last part of this tutorial is to create the mechanism that will, first, take care about applying autoplay animation and, second, change the appearance of label to suggest change of the checkbox state between checked and unchecked. Let’s tackle the autoplay first.

We will create a chain composed of :checked state for the “slider__switchcheckbox followed by adjacent selector and class “slider” this followed by direct child selector and classslider__list”. Declaration block for this chain will contain animation-name set to the name of keyframes – “autoplay”, animation-duration to “10s” and animation-iteration-count to “infinite”. You can also use a shorthand “animation: autoplay 10s infinite” if you want.

.slider__switch:checked + .slider > .slider__list {
 animation-name: autoplay;
 /* This will change the time it takes to move to next slide */
 animation-duration: 10s;
 animation-iteration-count: infinite;
}

For the label, we will change its background color to dark greyish blue (#455a64) and, for its :after pseudo-class, left property to 2.75rem. This will move the switch on the other end of the label.

.slider__switch:checked + .slider + .slider__control > label {
 background: #455a64;
 &:after {left: 2.75rem;}
}

Complete SASS:

// Remy function to convert pixels to rems
@function remy($value) {
 @return ($value / 16px) * 1rem;
}

/**
 * Base
 */
body {
 font-size: 100%;
 background: #f1f1f1;
}
/* Fallback for hidden attribute */
hidden {display: none;}

/**
 * Keyframes for autoplay
 */
@keyframes autoplay {
 /* position of the first slide */
 0% {left: 0;}
 /* position of the second slide */
 25% {left: remy(-650px);}
 /* position of the third slide */
 50% {left: remy(-1300px);}
 /* position of the fourth slide */
 100% {left: remy(-1950px);}
}

/**
 * Slider
 */
.slider {
 position: relative;
 /* top margin is for purposes of demo */
 margin-top: 3rem;
 margin-right: auto;
 margin-left: auto;
 overflow: hidden;
 width: remy(650px);
 height: remy(420px);
 box-shadow: 0 4px 14px rgba(0,0,0,.25);
}
.slider__list {
 position: absolute;
 left: 0;
 width: remy(2600px);
}
.slider__slide {float: left;}

/**
 * Slider control
 */
.slider__control {
 margin-right: auto;
 margin-left: auto;
 width: remy(72px);
 font-family: sans-serif;
}
.slider__control label {
 position: relative;
 display: block;
 margin-top: 2rem;
 margin-bottom: 1rem;
 width: 4.5rem;
 height: 2rem;
 font-size: 1rem;
 font-weight: normal;
 line-height: 1.5;
 color: transparent;
 background: #ddd;
 border-radius: 2rem;
 cursor: pointer;
 transition: left 0.15s ease-out;
 &:before {
  content: "autoplay";
  position: absolute;
  top: 2.5rem;
  left: 0;
  color: #333;
  font-size: .95rem;
  font-weight: bold;
  text-transform: uppercase;
 }
 &:after {
  content: "";
  position: absolute;
  top: .25rem;
  left: .25rem;
  display: block;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: 2rem;
  background: #fff;
  transition: left 0.15s ease-out;
  transform: translate3d(0, 0, 0);
 }
}
.slider__switch:checked + .slider > .slider__list {
 animation-name: autoplay;
 /* This will change the time it takes to move to next slide */
 animation-duration: 10s;
 //animation-delay: 3.5s;
 animation-iteration-count: infinite;
}
.slider__switch:checked + .slider + .slider__control > label {
 background: #455a64;
 &:after {left: 2.75rem;}
}

CSS version:

/**
 * Base
 */
body {
 font-size: 100%;
 background: #f1f1f1;
}
/* Fallback for hidden attribute */
hidden {
 display: none;
}

/**
 * Keyframes for autoplay
 */
@-webkit-keyframes autoplay {
 /* position of the first slide */
 0% {
 left: 0;
 }
 /* position of the second slide */
 25% {
 left: -40.625rem;
 }
 /* position of the third slide */
 50% {
 left: -81.25rem;
 }
 /* position of the fourth slide */
 100% {
 left: -121.875rem;
 }
}
@keyframes autoplay {
 /* position of the first slide */
 0% {
 left: 0;
 }
 /* position of the second slide */
 25% {
 left: -40.625rem;
 }
 /* position of the third slide */
 50% {
 left: -81.25rem;
 }
 /* position of the fourth slide */
 100% {
 left: -121.875rem;
 }
}

/**
 * Slider
 */
.slider {
 position: relative;
 /* top margin is for purposes of demo */
 margin-top: 3rem;
 margin-right: auto;
 margin-left: auto;
 overflow: hidden;
 width: 40.625rem;
 height: 26.25rem;
 box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25);
}
.slider__list {
 position: absolute;
 left: 0;
 width: 162.5rem;
}
.slider__slide {
 float: left;
}

/**
 * Slider control
 */
.slider__control {
 margin-right: auto;
 margin-left: auto;
 width: 4.5rem;
 font-family: sans-serif;
}
.slider__control label {
 position: relative;
 display: block;
 margin-top: 2rem;
 margin-bottom: 1rem;
 width: 4.5rem;
 height: 2rem;
 font-size: 1rem;
 font-weight: normal;
 line-height: 1.5;
 color: transparent;
 background: #ddd;
 border-radius: 2rem;
 cursor: pointer;
 -webkit-transition: left 0.15s ease-out;
 transition: left 0.15s ease-out;
}
.slider__control label:before {
 content: "autoplay";
 position: absolute;
 top: 2.5rem;
 left: 0;
 color: #333;
 font-size: .95rem;
 font-weight: bold;
 text-transform: uppercase;
}
.slider__control label:after {
 content: "";
 position: absolute;
 top: .25rem;
 left: .25rem;
 display: block;
 width: 1.5rem;
 height: 1.5rem;
 border-radius: 2rem;
 background: #fff;
 -webkit-transition: left 0.15s ease-out;
 transition: left 0.15s ease-out;
 -webkit-transform: translate3d(0, 0, 0);
 transform: translate3d(0, 0, 0);
}
.slider__switch:checked + .slider > .slider__list {
 -webkit-animation-name: autoplay;
 animation-name: autoplay;
 /* This will change the time it takes to move to next slide */
 -webkit-animation-duration: 10s;
 animation-duration: 10s;
 -webkit-animation-iteration-count: infinite;
 animation-iteration-count: infinite;
}
.slider__switch:checked + .slider + .slider__control > label {
 background: #455a64;
}
.slider__switch:checked + .slider + .slider__control > label:after {
 left: 2.75rem;
}

Note About Support:

Since the slider in this tutorial is using newer CSS3 animations module, we should consider how it is supported by browsers. The good news is that the majority of modern browsers will work with CSS animations without any problem. Only users if IE 8 and 9 (probably masochists) will not be able to use it because for these two versions, there is no support for animations. For the rest of the users (90%), no more work is needed. If you need to make the slider work for these two versions, you can use feature testing detection library modernizr and create a JavaScript fallback.

Final Words

Here you have amazing autoplay slider created and powered only with pure CSS3! The best of it is that you no longer need to create more space for two separate forms. What’s more, it works great even without JavaScript. However, keep in mind while implementing this design that little issue with IE 8 and 9 mentioned above. Besides this there is nothing preventing you from taking the skills you learned today and put the skills you’ve learned into practice on your next project.

Thank you very much for your time. And, until next time, have a great day!

Did you like this article? Please subscribe.

Are you on social media? Let's connect! You can find me on Twitter and Dribbble.