Tristate Toggle Switch with Pure CSS
Tags: html, css, challenge, programming
Published: 2021-06-16 07:22:14

If it hadn't been for the challenge I was asked to do as a junior frontend developer, I wouldn't have learned how to implement a **tri-state toggle switch** with **pure CSS**.

The challenge was launched on [frontendmentor.io](https://www.frontendmentor.io/challenges/calculator-app-9lteq5N29) which is a basic calculator app with _three themes_ available to switch among.

Well, the truth is you can't style core HTML elements like radio buttons directly. However, there is a trick to do so.

Prior to the styling, we need to make some changes in our HTML document.
_Instead_ of having 3 unwrapped input elements,
```html 
<input type='radio' />
<input type='radio' />
<input type='radio' />
```
we're going to wrap them in a label tag separately and then add a span tag of a specific class.

Here's how our code will look like:

```html
<div class="theme-toggle">
  <label class="custom-radio-button">
    <input id="first" name="toggle-state" type="radio" checked />
    <span class="checkmark"></span>
  </label>

  <label class="custom-radio-button">
    <input id="second" name="toggle-state" type="radio" />
    <span class="checkmark"></span>
  </label>

  <label class="custom-radio-button">
    <input id="third" name="toggle-state" type="radio" />
    <span class="checkmark"></span>
  </label>
</div>
```
Wrapping the label around input controls, makes it much easier to click the button.

I'll add some minor styles on wrapper class `.theme-toggle`:

```css
.theme-toggle {
   display: flex;
   justify-content: center;
   align-items: flex-start;
}
```

Let's start off by styling labels:
```css
.custom-radio-button {
   width: 20px;
   height: 20px;
   border: 2px solid #444;
   border-radius: 50%;
   display: flex;
   justify-content: center;
   align-items: center;
}
```
Now the thing is to **hide** those circular radio button themselves; no worries as we have label tags, remember?
**They still make the whole area clickable**, so:
```css
.custom-radio-button input {
   display: none;
}
```
A quick reminder:
The **span** element is typically used to wrap a specific piece of content to give it an additional hook so you can use to add style. Without any style attributes, **span** has no effect at all.

Since we cannot apply width and height to inline elements, we change span's display property to `inline-block`.

```css
.custom-radio-button .checkmark {
   width: calc(100% - 6px);
   height: calc(100% - 6px);
   background-color: hsl(6, 63%, 50%);
   border-radius: 50%;
   display: inline-block;
   opacity: 0;
   transition: opacity 0.3s ease;
}
```
You may wonder what the use of `width` and `height` is; that is to simply fill the label area with a round shape.

And the `opacity: 0` will make the inputs completely transparent.

All we have to do is to target the _adjacent element_ which is the _checkmark_, when the input is `checked`:
```css
.custom-radio-button input:checked + .checkmark {
   opacity: 1;
   display: inline-block;
}
```
This last style will apply on the first `input` element when the page loads because we've set `checked` on it in our HTML.

That's all we need in order to have a custom radio button.
 
You can also check the whole project out on my [github.com](https://github.com/SanazBHMN/calculator-app)