DEV Community

Cover image for Creating a CSS-Only Toggle Switch
Dom (dcode)
Dom (dcode)

Posted on

Creating a CSS-Only Toggle Switch

In today's post I wanted to share a technique which I use to create pure CSS toggle switches. This is very easy to implement, and is a stylish replacement to regular checkbox HTML inputs.

Video Tutorial

If you prefer this tutorial in the form of a video, check it out on my YouTube channel, dcode:

What makes this CSS-only?

In order to make this solution CSS-only we're going to be using traditional <input type="checkbox"> elements but they're going to be hidden through CSS.

Our CSS will then make use of the :checked pseudo-class and the general sibling combinator (~) to dynamically apply either a "on" or "off" style to our switch.

Writing the HTML

Let's start with the HTML. For this, we're going to be wrapping everything inside a <label> element. This will allow us to click anywhere within the wrapper and it will toggle the state of our hidden checkbox.

<label class="toggle" for="myToggle">
    <input class="toggle__input" name="" type="checkbox" id="myToggle">
    <div class="toggle__fill"></div>
Enter fullscreen mode Exit fullscreen mode

Ensure the for attribute of the <label> matches up with the id of the input field, as shown above.

As you can see, we have the regular input field and we have the fill element. The fill element represents everything visible, including the circle slider itself.

Moving onto the CSS

The first thing to do is to define a few variables to control the dimensions of the toggle switch - this allows us to change the "width" in one place, and everything will be sized based on this number.

While we're here, let's display the .toggle as inline-block so it flows easier and ensure the correct cursor is displayed:

.toggle {
    --width: 40px;
    --height: calc(var(--width) / 2);
    --border-radius: calc(var(--height) / 2);

    display: inline-block;
    cursor: pointer;
Enter fullscreen mode Exit fullscreen mode

Next, we can hide the actual input field:

.toggle__input {
    display: none;
Enter fullscreen mode Exit fullscreen mode

Styling the switch fill

Now we're done with the wrapper, let's move onto the switch itself. For this, we can use the variables defined above and apply some basic styles:

.toggle__fill {
    position: relative;
    width: var(--width);
    height: var(--height);
    border-radius: var(--border-radius);
    background: #dddddd;
    transition: background 0.2s;
Enter fullscreen mode Exit fullscreen mode

Notice the transition - this is in place to ensure we get a smooth animation as the background color changes from grey to green (as we'll see shortly).

Styling the switch circle (or slider)

For the circle, we can make use of the ::after pseudo-element in CSS - let's create it like this:

.toggle__fill::after {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    height: var(--height);
    width: var(--height);
    background: #ffffff;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.25);
    border-radius: var(--border-radius);
    transition: transform 0.2s;
Enter fullscreen mode Exit fullscreen mode

Most of these are self-explanatory but as shown we're using position: absolute to position the circle in the top left corner before setting a width and height via defined variables.

Getting the toggle to work

As mentioned earlier, the toggle is going to work by using the :checked pseudo-class and the general sibling combinator.

Let's define a couple of rules below, which will be activated when the <input> is checked:

.toggle__input:checked ~ .toggle__fill {
    background: #009578;

.toggle__input:checked ~ .toggle__fill::after {
    transform: translateX(var(--height));
Enter fullscreen mode Exit fullscreen mode

As we can see, translateX is used here to push the circle to the right side. In order for this to line up, it must be pushed the same amount as the height of the container.

And that's it! The toggle switch should be fully functional. If anyone else has suggestions for improvement or different styles, please leave them in the replies below šŸ˜

Top comments (2)

jcoonrod profile image
jcoonrod • Edited

This is a GREAT tutorial - I really appreciate it when people make tutorials without unnecessary bloat that makes it harder to learn. Here are two ways to make it even simpler. (1) You do not need "for " in your label or id= in your input when the label surrounds the content. (2) You do not need 3 classes. You can select your css on label[role=toggle], input[role=toggle] and div[role=toggle]. which makes using the pattern MUCH simpler in the html. In fact, you could just set the role for the label and then select the other two items as its siblings. Another suggestion - in tutorials just use named colors instead of hex colors. "Green" and "grey" communicate much more clearly.

djdingle247 profile image

I have a question about the JavaScript rabble sort you did on YouTube. I was wondering if you could help. Iā€™m following you on Twitter @djdingle247 . Can you reach out there?