DEV Community

Cover image for Tristate Toggle Switch with Pure CSS
Sanaz Bahmani
Sanaz Bahmani

Posted on • Updated on

Tristate Toggle Switch with Pure CSS

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

Top comments (2)

Collapse
 
nop1984 profile image
Mykola Dolynskyi • Edited

mistake in code
.custom-radio-btn input:checked + .checkmark
should be
.custom-radio-button input:checked + .checkmark

UPD: made demo + added some JS for label below jsfiddle.net/nop1984/r54mejq6/

Collapse
 
sanaz profile image
Sanaz Bahmani • Edited

The mistake's been corrected. Thanks for noticing :)
The demo's pretty great btw.