DEV Community

loading...
Cover image for Tristate Toggle Switch with Pure CSS

Tristate Toggle Switch with Pure CSS

Sanaz Bahmani
This is Sanaz, a junior frontend developer.
Updated on ・2 min read

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 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,

<input type='radio' />
<input type='radio' />
<input type='radio' />
Enter fullscreen mode Exit fullscreen mode

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:

<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>
Enter fullscreen mode Exit fullscreen mode

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:

.theme-toggle {
   display: flex;
   justify-content: center;
   align-items: flex-start;
}
Enter fullscreen mode Exit fullscreen mode

Let's start off by styling labels:

.custom-radio-button {
   width: 20px;
   height: 20px;
   border: 2px solid #444;
   border-radius: 50%;
   display: flex;
   justify-content: center;
   align-items: center;
}
Enter fullscreen mode Exit fullscreen mode

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:

.custom-radio-button input {
   display: none;
}
Enter fullscreen mode Exit fullscreen mode

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.

.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;
}
Enter fullscreen mode Exit fullscreen mode

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:

.custom-radio-btn input:checked + .checkmark {
   opacity: 1;
   display: inline-block;
}
Enter fullscreen mode Exit fullscreen mode

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

Discussion (0)