Custom form elements with pure CSS
Make checkboxes into slider switches
This is a reprint of an article I wrote way back in 2015, but it's still relevant today.
One of the more useful CSS 3 selectors is the :checked pseduo-class. It has great support in all modern browsers, and can be used to make some really powerful styles. The best part is these are pure CSS with no javaScript needed.
Step 1: Simple custom checkboxes
Let's start by using a simple checkbox list on an email subscription page, and change the checkboxes to colored squares
I'm going to use a class of custform on the divs, which will let me set up base styles for the custom elements
<fieldset>
<legend>Subscriptions</legend>
<div class="custform"><input id="newsletter" type="checkbox" name="newsletter" value="newsletter"><label for="newsletter">Newsletter</label></div>
<div class="custform"><input id="insider" type="checkbox" name="insider" value="insider" checked="checked"><label for="insider">Insider</label></div>
<div class="custform"><input id="hotd" type="checkbox" name="hotd" value="hotd"><label for="hotd">Hack of the Day</label></div>
<div class="custform"><input id="qotd" type="checkbox" name="qotd" value="qotd"><label for="qotd">Quote of the Day</label></div>
</fieldset>
First thing to do is to hide the checkbox itself. I don't want to just use display:none, because that will also hide it from speaking and braille browsers. Instead, I'll use visibility:hidden. I also need the enclosing div to have positioning on it.
.custform {
position: relative;
display: inline-block;
white-space: pre;
}
.custform input[type='checkbox'] {
visibility: hidden
}
I also need positioning on the label. Now that the checkbox is hidden, the label is going to be where everything happens. Clicking on the label will check/uncheck the input, and we need to reflect those changes in our pseudo elements. Because the pseudo-elements are placed on the label, clicking on them also registers as a click on the label, which in turn registers as checking/unchecking the box. For now, I'm just going to use a square, and fill it with a solid color.
.custform input[type='checkbox'] + label {
position: relative;
margin-right:2em;
}
.custform input[type='checkbox'] + label::before {
content: '';
border: 1px solid #ccc;
width: 1.2em;
height: 1.2em;
position: absolute;
top: 0;
left: -1.5em;
border-radius: .2em;
cursor: pointer;
background: #fff;
}
And now the cool part. Since clicking on the label will check or uncheck the input, we can then test to see if the input has been checked, and change the appearance of the label, any element directly after the label, or any pseudo-elements on the label itself!
In this case, however, I'm simply changing the background– and border– color
.custform input[type='checkbox']:checked + label:before {
background-color:#149198;
border-color:#149198
}
The checkboxes should now look like:
Step 2: Make checkboxes into slider switches
We already have the mechanics in place to make the slider switches, all I need to do is style them. So that I can keep both the custom square checkboxes, and the checkboxes as sliders on the same page, I'll be adding a slidecheck class to the enclosing divs.
<fieldset>
<legend>Subscriptions</legend>
<div class="custform slidecheck"><input id="newsletter3" type="checkbox" name="newsletter3" value="newsletter3"><label for="newsletter3">Newsletter</label></div>
<div class="custform slidecheck"><input id="insider3" type="checkbox" name="insider3" value="insider3" checked="checked"><label for="insider3">Insider</label></div>
<div class="custform slidecheck"><input id="hotd3" type="checkbox" name="hotd3" value="hotd3"><label for="hotd3">Hack of the Day</label></div>
<div class="custform slidecheck"><input id="qotd3" type="checkbox" name="qotd3" value="qotd3"><label for="qotd3">Quote of the Day</label></div>
</fieldset>
Since the switch is going to display after the label, I need to reverse some of the styles from before.
.custform.slidecheck {
width: 100%;
max-width: 230px;
display: block;
margin-bottom: .5em;
}
.custform.slidecheck input[type='checkbox'] + label {
margin-left: -.75em;
margin-right: 0;
position: static
}
Notice that I've given the slidecheck div a max-width of 230px. I could avoid that by having the switch to the left of the label, like I have the plain checkboxes above. However, I don't think they look as good on the left. The compromise is that I need to have a width on the div, so that the switches are close enough to make sense.
All the measurements are in em units so that the slide switch will scale nicely depending on which font and font-size you use.
To help with the slider switch illusion, I'm also using CSS transitions for the movement and background color change.
.custform.slidecheck input[type='checkbox'] + label::before {
content: 'OFF';
font-size: .67em;
font-family: "Andale mono", monospace;
line-height: 2.083em;
text-align: right;
padding-right: .833em;
width: 5em;
height: 2.25em;
background-color: #fff;
position: absolute;
top: auto;
left: auto;
right: 0;
border-radius: 1.2em;
border: 1px solid #ccc;
color: #aaa;
transition: background-color 0.3s;
}
.custform.slidecheck input[type='checkbox'] + label::after {
content: '';
font-size: .67em;
width: 1.75em;
height: 1.75em;
border-radius: 1.2em;
background: #d9dbdc;
position: absolute;
top: .25em;
left: auto;
right: 3em;
opacity: 1;
transition: all 0.3s ease;
}
.custform.slidecheck input[type='checkbox']:checked + label:after {
right: .25em;
}
.custform.slidecheck input[type='checkbox']:checked + label:before {
content: 'ON';
text-indent: .833em;
text-align: left;
color: #fff;
font-weight: bold;
background-color:#149198;
border-color:#149198;
}
.custform.slidecheck input[type='checkbox']:checked + label:before {
background-color:#149198;
border-color:#149198
}
And here's the finished version:
Bonus: 3D-style slide buttons
It's easy to modify the buttons to make them 3D, if that would fit in better with the rest of your site design. Just add a box-shadow to the elements, like this:
.custform.slidecheck.threedee input[type='checkbox'] + label:before {
box-shadow: inset 0 .083em .083em rgba(0, 0, 0, 0.2);
}
.custform.slidecheck.threedee input[type='checkbox'] + label:after {
box-shadow: 0 .083em .083em rgba(0, 0, 0, 0.6);
height:1.667em;
width:1.667em;
}
And the final version:
Just like the previous version, everything is in em units, so it should scale just fine. Here is what it looks like with font-size: 70px
Conclusion
Using the :checked pseudo-class is extremely helpful, and I will be posting some more uses for it in the future.