Cover image for Creating a CSS-Only Toggle Switch

Creating a CSS-Only Toggle Switch

dcodeyt profile image Dom (dcode) ・3 min read

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>

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;

Next, we can hide the actual input field:

.toggle__input {
    display: none;

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;

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;

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));

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 😁

Posted on by:

dcodeyt profile

Dom (dcode)


Web Developer from Australia that runs the YouTube Channel 'dcode'!


markdown guide