Significa Blog
Published in

Significa Blog

CSS Only Slider

Today I’d like to guide you on how to create a responsive card slider using CSS and HTML only — not a single JavaScript line! On this tutorial we’ll make use of radio buttons to achieve a fully-working CSS only slider.

You can check the demo here:

And access the source code here:


  • No dependencies
  • Works on all browsers
  • Works without Javascript
  • Very performative


  • Not the greatest solution to use with CMS
  • Not very DRY

This is a step-by-step tutorial for anyone with a reasonable amount of knowledge in HTML & SCSS. If you don’t want to do it step by step, scroll faster! Plus, I’ll be using BEM– if you’re not familiar with how BEM should be written, take a look at this:

Ok, ready to go! First of all, let’s take a look at the design:

Conceptual aside: Radio buttons

Before starting with the actual tutorial, let me tell you a bit about radio buttons’ features and how to use them properly. If you already know how to handle them, feel free to skip this part.

First of all, this is how an input type radio basic markup looks like:

<input type=“radio”>

A very important feature about radio buttons that we’ll be leveraging during this tutorial is the fact that only one can be checked at the time.
However, in order to make that happen, all of them require the same name:

<input type=“radio” name=“slider”>

Another relevant feature about radio button is that you can define which one is checked directly on the HTML. This will be very useful later on.

<input type=“radio” name=“slider” checked>

A cool fact is that you don’t actually need to click on the radio button itself to make it checked, you might as well click on it’s label.
And in order to get that working, you need this:

<label for=“”>This is a clickable label</label>

In order for the label to check a specific radio, you need to identify each radio button, this way the label knows which one it is referring to.
For this tutorial, we’ll use ID’s but you may use classes as well — classes work with radio buttons but not with checkboxes.

<input type=“radio” name=“slider” id=“slider-1”>

And then we’ll make the label refer for it:

<label for=“slider-1”>This is clickable</label>

That’s all we’ll need to know regarding radio buttons for now!

1. The Markup

Step 1 — Wrappers

Lets start by having a parent section which we’ll call slider. And then lets set up a div inside slider which we can call slider__holder.
The inner one will wrap the slider itself and the outer one will hold the inner, plus the titling content.

Step 2 — Radio buttons

Done that, let’s markup the radio buttons. We got to put them as a slider__holder sibling so we can easily refer to it later on.
We’ll need 5 of them, so:

Don’t forget to change the ID’s number on each radio button.
Plus, I want the middle one to be the visible one on page load, that’s why I defined it as checked.

Step 3 — Card as labels

We want all cards to be clickable, so each one of them will be a label for each one the 5 radios.
Mind that each label for=”slide-X” has to refer to each radio button, so don’t forget to change the number when you copy and paste.

You might have noticed that each slide has a modifier slider__item — n. We’ll be using it as soon as we need to refer to each slider__item.

Step 4 — Cards content

For the content of each card, I’ll leave it up to you. I’ll do mine but it doesn’t influence the slider’s functionality in any way, so this is up to your creative designer skills.

Step 5 — Navigation bullets

Besides giving an important visual clue of the current card selected, the bullets below the cards should be clickable to help the navigation between the slides.
Which is the same as saying that each bullet has to be a label for the radios as well.

Also, lets make bullets as a new element rather than have it as a slider element:

Once again we’re using bullets__item — n modifier since we’ll be referring to each one of them separately in the future.

Just place them under <div class=“slider__holder”></div> as a sibling.

Done that, we’re done with the markup.

You might have noticed that slider also has a section class.
That’s because I wanted to add a section title and description to it and make it as a separate element. It doesn’t have any influence in the functionality whatsoever and you’re free to ignore it. All the styles will be available in the source code.

Here’s the whole snippet:

2. The Functionality

Before anything else, I think we should make sure the labels are in fact checking the radio buttons.

The best option would be for us write some text inside the Cards, inside the bullet’s label tag and click on them. If the checked input changes, then it’s ok. If not, something might be wrong.

I did the following for debugging:

As I told you before, I won’t make any reference to each slide content and design, I rather focus on the functionality. However, for this tutorial sake, I’ll use my design and it’s styles as an example.

Step 1 — Active Slide

First of all, we need to set each card default style. Feel free to write your own styles as long as you include a transform: scale(0.85) among them.

Then we need to define how it will look when a certain slide is active. And that’s when the fun part starts.

So, when #slide-1 is checked we want .slider__item — 1 to be 100% scaled, so we do the following:

Explaining what just happended:
First: we referred to .slider__item — 1 (line 7);
Second: we told it to scale(1) when #slide-1 is checked (line 11 and 12);

In current language it means that when #slide-1 is checked, it’s sibling’s (~) .slider__holder son .slider__item — 1 will be 100% scaled.

Basically when you click on Card 1 it will get 100% scaled.

Then we must replicate it for the remainder cards. So when we click on Card 2 it will get 100% scaled and the sames for cards 3, 4 and 5.

At this point, when you click on a card it will get 100% scaled and all the other ones will get smaller — 85% scaled.

The same will happen if you could click on the bullets since they are labels as well.

Step 2— Non Active Slides

Now, we’ll have to define each slide position for when a certain slide is active. In the following case, for example, when #slide-1 is checked/active, .slider__item — 2 is 0.85 scaled and translated 100px.

And in the following one, #slide-1 is checked/active, .slider__item — 3 is 0.65 scaled, translated 210px and with z-index: 0.

And in the following one, #slide-1 is checked/active, .slider__item — 4 and .slider__item — 5 are 0.65 scaled, translated 210px, with 0 opacity and with z-index: -1.

And in the following one… there is no following one! Imagine doing this for all the active slides — defining each card position when a certain slide is active. It would be plain crazy!

Specially because there’s a pattern as you can see in the image below:

At certain active cards, some of the remainder ones behave the same way. For example, when card 1 is active, cards 4 and 5 assume the same position — behind card 3.

Step 3 — Loop it!

To tackled this kind of madness, lets use SCSS loops.
The following code does exactly the same but saves 90% of trouble!

Take a look:

Let me explain:

  • We loop through each slide
  • Inside each loop iteration, we loop again all slides, validating if:
    - The current slide is 3 or more positions to the right
    - is 2 positions to the right
    - is 1 position to the right
    - is the current slide
    - is 1 position to the left
    - is 2 positions to the left
    - is 3 or more positions to the left

This way, we can tackle all the possibilities and give them style accordingly.
Take a second look at the following image for further understanding:

Step 4— Bullets

Now, we have to have a loop for the bullets as well:

This is a much simpler loop since you just have to define the active one.
Don’t forget to style it.


We just built a card slider that works on any browser and any device, highly performative (using GPU transitions) without the need of a single line of Javascript.

The same logic can be applied to build image sliders, tabbed content, and many more.

We hope everything was easy to follow and if you have any questions or comments, drop us a line in the commenting section!

If you liked it, please press 💚. It will let us know you want us to keep on writing more stuff like this.


Update: we’ve seen some comments regarding the pertinence of this tutorial and we just want to make clear that javascript isn’t, by any mean, a bad choice to achieve something like this. In fact, javascript probably IS the way to go. This tutorial is intended to stretch the boundaries of CSS a bit and learn some cool things like Sass loops while we’re at it :)




Significa is a design-led agency focused on product development: we take on products from inception to launch, from business model to people’s pocket, from wireframe to continuous deployment.

Recommended from Medium

#16 Blameless postmortems at ASOS

Canaby App End of Summer Update

Spacemesh 2020 Update

IoT Operating Systems.

IoT Operating Systems.

How to create a Bitbucket-hosted Git repository for Agilitest projects | Agilitest blog

An Introduction to Database Sharding

How To Build An Index Using Custom Code | Index One

LocalHackDay: Build, a journey

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store


Making products meaningful @

More from Medium

Vuetify use CSS Grid classes

How to get items side-by-side

HTML in Minute

Project 1 And A New CSS Framework

Screenshot of DaisyUI link HTML