Making A Switch In CSS
Tags: css
Published: 2019-03-30 18:28:05

---
title: Making A Switch In CSS
published: true
description: How to style a checkbox so it looks like a switch
tags: CSS
---

# Let's switch it up!

For a recent project I thought I'd use Materialize CSS and after working with it, determined that, while it's a good library, it wasn't for my project.  However, I really liked the switch element they designed so I decided to remake it myself in CSS.

Doing so required I go into areas in CSS I'd never gone before, namely, pseudoelements.  By manipulating `::before` and `::after`, I can make a functional switch out of a checkbox.

Below is the final product.  It's a switch that toggles from off to on, and turns grey and unusable when disabled.

{% codepen https://codepen.io/jkuhl/pen/ZPRYOw %}

# Getting Started

We're going to begin with the humble checkbox and wrap it in a label, as we should so that when the user clicks on the label, it will toggle the checkbox

> For accessibility purposes you should always use labels in conjunction with form elements.

To ensure our CSS doesn't affect other checkboxes on our site, we'll give the label a class, and modify that class and its descendents as we continue.

```html
<label class="switch">
  Off
  <input type="checkbox">
  On
</label>
```

Let's set up the CSS:

```css
.switch input[type="checkbox"] {

}
```

The first thing we need to do is get rid of the default checkbox styling.  This took some research, but I found the solution, `-webkit-appearance: none;`.  Our checkbox will now disappear.

Then we need to determine the placement of our knob (the red or blue circle) and the little slider the knob "travels" on (the grey rectangle in the back).  These will be made with pseudo elements and they need to be moved _relative_ to the _position_ of the checkbox element.

That's a big hint, we will need to style the knob for absolute positioning, but absolute positioning, as a default will be based off the whole window.  We'll want `position: relative` on our checkbox because it is the parent element for the `::before` and `::after` pseudo elements.

Finally, we'll want some spacing around our switch, once it appears, so the "on" and "off" text doesn't smash right next to it, and we can do this with margin.

```css
.switch input[type="checkbox"] {
  -webkit-appearance: none;
  position: relative;
  margin: 0 1rem;
}
```

That sets up our checkbox.  But right now, we have nothing to see, so lets make the slider

# Mmmm Sliders

Nothing would please me more than a plate of mini burgers.  But this a CSS article, not a local small town grill with American food, country music and goofy sh*t on the walls.

![Shenanigans](https://media.giphy.com/media/2agCyh9S58i2I/giphy.gif)

In this context, the slider is just the track our knob travels on.  We'll make it out of the `::after` pseudo element.

But first, what is the `::after` element, and for that matter, the `::before` element?  Well, pseudo elements are elements that come with various tags that can handle various things.  I know that's a very generic and unhelpful response, but there are a lot of them.  Some, like `::first-line` can modify the first line of a paragraph.  There's an experimental one that lets you mess around with `::spell-check`.  The MDN has a lot of information on [pseudo elements](https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-elements)

`::before` and `::after` are elements found on most HTML elements and they are considered children of their element.  `::before` is the first child and `::after` is the last child.  For our purposes, it won't matter which one is the first or last child.  These can be used for a lot of purposes, even creating [mustaches](https://www.youtube.com/watch?v=Y0_FMCji3iE).

But we're going to keep it simple and use `::after` to create our slider.  It'll just be a regular rectangular grey box.

To target it, we simply need to append `::after` to a new css rule

```css
.switch input[type="checkbox"]::after {

}
```

There are five things we need to do to make it appear.  First, pseudo elements need some content.  Ours will be a simple empty string, so `content: ''` will suffice.  Next, it needs to have display property.  We'll make it `block`, because after some finagling, that's what I found works best here.  And it needs a background color, and I picked `grey`.

> Don't make the mistake I always do of setting the `color` property!  `color` is for text!  `background` is what we want to set!

Finally, we need height and width.  As it should be set within a body of text, having it based on the root em (rem) seems the most logical.  I set it to be 1 rem wide and 0.5 rem high.

```css
.switch input[type="checkbox"]::after {
  content: '';
  display: block;
  width: 1rem;
  height: 0.5rem;
  background-color: grey;
}
```

A wild grey rectangle appears!

#  Styling the Knob

Now for the knobs.  Here we have a knob that can be either right or left, but is always a circle, always has a shadow (to make it have some 3d effect) and always has the same content and size, so we want to write our CSS to be DRY, Don't Repeat Yourself.

We want to write a baseline for our `::before` pseudo element.

```css
.switch input[type="checkbox"]::before {

}
```

Again, we need to give it content, dimensions and a display property for it to show.

```css
.switch input[type="checkbox"]::before {
  content: '';
  display: block;
  width: 1rem;
  height: 1rem;
}
```

That will make a square for us.  We can't see it yet because there's no color though, so we'll want to make the rules for when the checkbox is checked and unchecked, since the color changes depending on that value.

Targeting a checked checkbox is simple, we can make a new rule that selects `:checked`.  Targeting an unchecked box is a little more tricky because there's no `:unchecked`.  We have to use the `:not()` pseudo selector, so we can make two new rules:

```css
.switch input[type="checkbox"]:checked::before {
}

.switch input[type="checkbox"]:not(:checked)::before {
}
```

The order is important here.  We want to target the `::before` pseudo-element of a checked input element of type checkbox that is descendent of an element of the switch class.  We don't want `:checked` _after_ `::before` because `::before` doesn't have a `checked` attribute.

If it is checked, I want it to be blue and moved to the left 10 pixels.  And if it is not checked, I want it to be red and moved to the right 10 pixels.

```css
.switch input[type="checkbox"]:checked::before {
  left: 10px;
  background-color: blue;
}

.switch input[type="checkbox"]:not(:checked)::before {
  right: 10px;
  background-color: red;
}
```

Because we're modifying `left` and `right` attributes, our baseline needs to be set for `position: absolute`:

```css
.switch input[type="checkbox"]::before {
  content: '';
  display: block;
  position: absolute;
  width: 1rem;
  height: 1rem;
}
```

Now we're almost there.  The knob is a bit uncentered on the track, to fix it, I found that setting the top position -0.25 put it where I wanted it:


```css
.switch input[type="checkbox"]::before {
  content: '';
  display: block;
  position: absolute;
  top: -0.25rem;
  width: 1rem;
  height: 1rem;
}
```

And finally, let's make it a circle with some shadow.  Circles are easy, just give it a border radius of 50%.  Shadows on the other hand, I never hand craft shadows, I found a good online tool for that.  I like to use [CSSMatic](https://www.cssmatic.com) for this.  They have a real-time box shadow tool that autogenerates the code for you.  With the border radius and values I liked, the final CSS for the baseline selector looks like this:

```css
.switch input[type="checkbox"]::before {
  content: '';
  display: block;
  position: absolute;
  top: -0.25rem;
  width: 1rem;
  height: 1rem;
  border-radius: 50%;
  -webkit-box-shadow: 2px 2px 5px 0px rgba(0,0,0,0.75);
  -moz-box-shadow: 2px 2px 5px 0px rgba(0,0,0,0.75);
  box-shadow: 2px 2px 5px 0px rgba(0,0,0,0.75);
}
```

And there you have it!  A functional switch!

# But wait, there's more!

No form element is complete without styles to target `:disabled.`  Our users need a visual cue to know this element can't be used at the moment.  For this, we can take our knob, our `::before` element, and change the color if the checkbox is disabled:

```css
.switch input[type="checkbox"]:disabled::before {
  background-color: darkgrey;
}
```

That will turn the knob grey if it is disabled.  Try adding 'disabled' to the `input` tag to test it out.

# And we're done here!

Here's the full code:

```html
<label class="switch">
  Off
  <input type="checkbox">
  On
</label>
```

```css
.switch input[type="checkbox"]:checked::before {
  left: 10px;
  background-color: blue;
}

.switch input[type="checkbox"]:not(:checked)::before {
  right: 10px;
  background-color: red;
}

.switch input[type="checkbox"]::before {
  content: '';
  display: block;
  position: absolute;
  top: -0.25rem;
  width: 1rem;
  height: 1rem;
  border-radius: 50%;
  -webkit-box-shadow: 2px 2px 5px 0px rgba(0,0,0,0.75);
  -moz-box-shadow: 2px 2px 5px 0px rgba(0,0,0,0.75);
  box-shadow: 2px 2px 5px 0px rgba(0,0,0,0.75);
}

.switch input[type="checkbox"]::after {
  content: '';
  display: block;
  width: 1rem;
  height: 0.5rem;
  background-color: grey;
}

.switch input[type="checkbox"] {
  -webkit-appearance: none;
  position: relative;
  margin: 0 1rem;
}

.switch input[type="checkbox"]:disabled::before {
  background-color: darkgrey;
}
```

This is a simple little switch that can add a little flavor to a site.  It works great for binary situations, if the user wants _this_ or the user wants _that_.

Feel free to use and modify this as you see fit.  Try adjusting size or colors.  Make it fancier, have fun with it.  And if you see any way this can be done better in CSS, let me know.

Happy coding