A pure CSS toggle in 5 min!
The problem
Recently I needed a nice toggle for a web app I was working on and I decided to do one myself. I wanted it to be simple and possibly to work without Javascript. The more obvious choice was an HTML checkbox, able to natively store his status of "checked" or "not checked" without the need for data-attributes.
...but checkboxes are ugly. Well, not ugly but too simple and they didn't fit the design I had in mind.
I needed to "skin" the checkbox still mantaining its features. What I had in mind was something like iOS7 toggles, maybe not that flat but with a similar look.
So let's start building our custom CSS only[1] toggle!
The HTML
To achieve the result we need three things:
- The checkbox itself
- A container where the toggle could have slided
- A sliding toggle
...which in HTML translates to...
<div class="toggle">
<input name="toggle" type="checkbox" class="toggle__checkbox"/>
<div class="toggle__outer">
<span class="toggle__inner"></span>
</div>
</div>
Pretty simple structure. A generic toggle element containing the checkbox and the checkbox skin.
Now we need to skin that thing. First thing first:
Styling the container
First we should choose the palette. I'm quite bad with that task so I often use some tool to help me out in that choice. Coolors is one of my favourite, if you want to give it a try! I ended up pretty basic, with an almost white toggle with a grayish background which will lighten up in green when checked.
Once happy with our palette we can store those colors in a couple of #SASS variables.
$color--inactive: #aeb9ba;
$color--active: #5dfdcb;
$color--toggle: #f4faff;
The toggle element should specify the size of the widget and should declare its position as relative to let us easily place the toggle container and the toggle itself:
.toggle {
position: relative;
width: 48px;
height: 24px;
}
Good! ...but we have not yet put in the main actor of this story, so let's add the checkbox style. We want the checkbox to be as tall and as wide as it's container so that wherever we click it will react to our clicks,
.toggle {
position: relative;
width: 48px;
height: 24px;
& .toggle__checkbox {
width: 100%;
height: 100%;
margin: 0;
position: absolute;
}
}
Still a boring checkbox, but we're about to make it nicer! First by styling the outer toggle, aka the toggle container:
.toggle {
position: relative;
width: 48px;
height: 24px;
& .toggle__checkbox {
width: 100%;
height: 100%;
margin: 0;
position: absolute;
z-index: 100;
+ .toggle__outer {
position: absolute;
width: 100%;
height: 100%;
border-radius: 24px;
background-color: $color--inactive;
border-bottom: 1px solid rgba($color--toggle, 0.5);
box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.5);
transition: background-color 0.25s ease-in-out;
transition-delay: 0.15s;
pointer-events: none;
What's happening here? Well... first I want to position the container exactly in the same space I positioned the checkbox, so I use the position: absolute
property and give to it 100% of width and height of it's container. Then I set a border radius to make it round and a box shadow with the inset
value to make it look like it is cutting an hole in the space around. I then prepare the toggle container to smoothly change color when clicked. Notice that I gave a little delay to the animation, to let it start when the toggle has almost reached the end of it's run. Finally I set the property pointer-events
to none
so that clicking on the container won't actually stop on the container but rather get through it and reach the checkbox hidden below.
Now let's add the toggle itself! It's just a simple disc, the only trick is the positioning. it must start on the left of it's container and move to the far right when the toggle is checked. Here's the SCSS:
> .toggle__inner {
position: absolute;
top: 4px;
left: 3px;
width: 18px;
height: 18px;
border-radius: 100%;
transform: translate3d(0, 0, 0);
transition: all 0.25s ease-in-out;
background-color: $color--toggle;
pointer-events: none;
}
At this point the toggle itself should be easy to understand. I set it's position
to absoluteagain and vertically positioned it in the middle of the container. Giving it a
border-radius: 100%` and making it as tall as it's width I can be pretty sure it will be a perfect circle and setting the position of the 3D translation I can prepare it to move.
Let's add the animation then. We will take advantage of the checked
attribute that is built-in on every HTML checkbox to change the color of the container and the position of the toggle. The code is as simple as that:
.toggle__checkbox {
...
&:checked {
+ .toggle__outer {
background-color: $color--active;
> .toggle__inner {
left: calc(100% - 20px);
}
}
}
Done! We can now enjoy the smooth animation and a much nicer toggle than the native checkbox! If you want to try the finished code here's a link to my Codepen demo.
Let me know if you like these kind of tips and if my english was good enough (sorry, I'm quite bad at it!). If there's something you found difficult or unclear I'll be happy to explain it better!
Actually, I'll write the style using SCSS syntax, but the final result will be just HTML+CSS ↩