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' />
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>
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;
}
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;
}
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;
}
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;
}
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;
}
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)
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/
The mistake's been corrected. Thanks for noticing :)
The demo's pretty great btw.