Creating a CSS-Only Toggle Switch
Tags: css, html, webdev
Published: 2020-07-27 09:31:05

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**:
{% youtube wQVkA6cOBiI %}

### 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.

```html
<label class="toggle" for="myToggle">
    <input class="toggle__input" name="" type="checkbox" id="myToggle">
    <div class="toggle__fill"></div>
</label>
```

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:

```css
.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:
```css
.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:

```css
.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](https://developer.mozilla.org/en-US/docs/Web/CSS/::after) in CSS - let's create it like this:

```css
.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](https://developer.mozilla.org/en-US/docs/Web/CSS/:checked) and the [general sibling combinator](https://developer.mozilla.org/en-US/docs/Web/CSS/General_sibling_combinator).

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

```css
.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 😁