How to change checkbox color in CSS and SVG
This is the second article in a series of articles comparing some commonly-used CSS techniques to their SVG counterparts, showing the pros and cons of each, in an attempt to help you make a better decision as to which of these two to choose when solving common design problems on the web.
Custom Checkbox Styles Using CSS
In the previous post, we talked about creating textured text effects using both CSS and SVG, and came to the conclusion that SVG is currently more capable and powerful than CSS is in doing that. In this post, we’ll tackle custom HTML form styling—specifically checkbox and radio inputs.
Form elements are particularly not the easiest elements to style with CSS, as you might already know. But they’re also not particularly hard to style either.
To overcome the limitations brought by the fact that we have no style selectors for styling the innards of checkbox and radio inputs in CSS, developers have long used one popular way to style these inputs: using images. There are two ways to do that in CSS…
Using an Image Sprite
The most flexible way to style a checkbox (or radio button) in CSS is by using an image sprite that contains the images representing the styled checkbox in its two states: checked and unchecked.
I will assume we have both checkboxes and radio inputs in one form, so it is useful to use one image sprite that contains the styles for the checkboxes and the radio buttons as well. Such a sprite would look something like this:
The area surrounding the images is transparent. The size of each of the above inputs is equal to their real size on the page. So, if you want the checkboxes to be displayed at 25px by 25px on the page, make sure the image representing them inside the sprite has that same size.
The next step is to set up the checkboxes on the page so that the above sprite can be used.
The markup looks like this:
Click me
Make sure the label is tied to the checkbox using the for
attribute. The “inside of the label will be used as the “box” to which we will apply the sprite. This is not always necessary—you can apply the sprite sheet to the label instead of adding a new element, but I prefer this approach as a way to indicate the checkbox alternative and because our sprite here is horizontal. Sometimes, depending on where you want to apply the sprite, you might need to use a vertical one where the icons inside of it are stacked on top of each other.
Now, to the styling.
First, the default checkbox is hidden, and the label is styled such that you get a pointer cursor when you hover over it indicating that it is clickable:
input[type="checkbox"] {
display: none;
}
label {
cursor: pointer;
color: #555;
}
Next, the span
is given the dimensions we want the checkbox to have. In this demo, I am using checkboxes each of which is 45px by 45px. We will also apply a background to the span
using the image sprite we have.
input[type="checkbox"] + label span {
display: inline-block;
vertical-align: middle;
width: 45px;
height: 45px;
background: url(path/to/checkbox-radio-sprite.png) 0px center no-repeat;
}
Note: Ideally, you will want to use em
s to determine the dimensions of the span so that it scales with the text as its size changes, but I am using pixels here for demonstration purposes.
With these styles applied, the span will use the image sprite as a background, but only the first unchecked checkbox image will appear inside of the boundaries of the span
. (The 0px
position of the background sprite makes sure of that.) Think of this as the span
being a small window and only a portion of the sprite could be visible inside of that window at a time, because the sprite is four times as wide as the window, so only one portion of it “fits” inside of the window at a time.
Here is a GIF showing how the offset value you use for the background affects its position inside of the span’s boundaries.
Next, we’re going to use the :checked
pseudo-class to select the label span and style it when the checkbox is in the checked state:
input[type="checkbox"]:checked + label span {
background: url(path/to/checkbox-radio-sprite.png) -48px center no-repeat;
}
So what has changed? The only thing we needed to change was the position of the background image. We offset the image such that the second checkbox shows inside the background of the span
. We made sure that happens by using the distance from the second checkbox image to the edge of the sprite. The checked checkbox image is at 48px from the left edge of the sprite. So, to make sure it aligns with the left edge of the span, we use that value as a value for the background image.
Following the same logic and steps, to display the image of the unchecked radio button, we would need the position of that image inside the sprite, and use it as an offset for the background of the span.
So, to style the radio input, the code would look like this:
input[type="radio"] + label span {
background: url(path/to/checkbox-radio-sprite.png) -105px center no-repeat;
}
input[type="radio"]:checked + label span {
background: url(path/to/checkbox-radio-sprite.png) -153px center no-repeat;
}
And the following is the live demo for the above code:
See the Pen Styling checkboxes with CSS by Sara Soueidan (@SaraSoueidan) on CodePen.
You can also style the checkboxes using a similar technique, but instead of using a PNG image sprite, you would use CSS gradient images…
Using CSS Gradients
Instead of applying a PNG image to the span, you can style the span so that it looks like a checkbox or radio button, using CSS linear or radial gradients as backgrounds. I prefer this technique over the previous one, but it is limited in that the background can only be a solid color or a gradient. So, if you want a more sophisticated or textured checkbox style, this technique won’t do it without an external image.
input[type="checkbox"] + label span,
input[type="radio"] + label span {
display: inline-block;
vertical-align: middle;
width: 45px;
height: 45px;
border: 2px solid #888;
border-radius: 10px;
background: radial-gradient(#eee, #aaa);
}
/* we want the radio buttons to be circular */
input[type="radio"] + label span {
border-radius: 50%;
}
These styles will style the span so that it looks like an unchecked checkbox or radio button. But for the checked states, we will need to use a pseudo-element (or any child element for the matter) on the checkbox to add the checkmark. We want to add a checkmark when it is checked. To do that, we can use a pseudo-element on the span
, and specify the content of that pseudo-element to be a checkmark like so:
input[type="checkbox"]:checked + label span::before {
content: "✓";
color: deepPink;
text-align: center;
font-size: 40px;
}
This will add a checkmark to the span when the checkbox is checked. Pretty neat, right? As for the radio button, we only need to change the background color in the checked state:
input[type="radio"]:checked + label span {
background-image: radial-gradient(#FF5ABA, deepPink);
}
You can take this further and use a pseudo-element on the radio span as well and style it anyway you want to indicate a checked radio button. And this is the live demo for the above code:
See the Pen Styling checkboxes with CSS – Gradients by Sara Soueidan (@SaraSoueidan) on CodePen.
As you can see, this technique is clearer and shorter than the previous one, but it is less flexible with the style options.
SVG, on the other hand, provides us with a completely different way of indicating checked states…
How SVG Line Drawing Works
Instead of abruptly jumping between states, SVG provides us with a more natural and friendlier way—so to speak—to express a change in state. Since an SVG is an image, we could certainly use it to style a checkbox just like we did in the previous section with CSS: instead of referencing a PNG image, you would reference a couple of SVG images, each of which would describe a state. Heck, you don’t even have to do that; you could use just one SVG image and shift its content to show different states (practically the same way the PNG sprite works) by changing the value of its viewBox
attribute. But that’s s topic for another article.
However, we want to take advantage of SVG’s interactive nature to add more life to the experience, leveraging animations to create a smoother state transition that works similar to the way checkboxes work when we check them in real life, on real paper. Let’s see how we can do that.
For starters, we don’t need more than one SVG. One SVG is enough to achieve this effect. We do, however, need to hide the default checkbox like we did in the previous section, so that we can show the SVG image in its stead.
input[type="checkbox"] {
display: none;
}
Then, in the markup, instead of using a “that we would apply an image to, we will be directly using an image—the SVG itself, by placing it inline inside the label: (We’ll explain the code as we go)
Click me
The “element inside of the SVG looks like a hand drawn scribble (see image below). What we want to do is make it look like this scribble is drawn into the checkbox when the label is clicked, similar to the way someone would check a checkbox on paper using a pen by drawing the scribble in it. The following GIF shows the effect in action:
So our goal is to animate the path inside the svg
so that it appears as if it is drawing itself. In order to do that, we need to use the SVG line drawing technique pioneered by Jake Archibald.
How SVG Line Drawing Works
Jake wrote about the line drawing technique in an article over on his blog two years ago. And Chris Coyier wrote a step-by-step explanation of the technique over at CSS-Tricks too, so I recommend you read those two articles for more visual examples. For the sake of brevity, we will go over how it works in brief, using the excellent interactive demo from Jake’s article:
- The stroke of an element in SVG is similar to what a border is to an element in HTML. (See the
stroke
attribute in the above SVG code.) - The stroke of a path is usually the line that determines the path’s shape. In the case of our scribble, it is the path itself.
- The stroke can, however, be dashed, just like borders can be dashed in CSS/HTML.
- In SVG, you can also specify the length of the dash and the length of the gaps separating these dashes using the
stroke-dasharray
attribute. It takes two space-separated values, one specifies the length of the dash and the other specifies the length of the gap. If you specify only one value, both the dashes and the gaps will have the same length. - A dash length can be the same as the entire path length.
-
http://blogs.adobe.com/creativecloud/files/2015/08/dash-path-length.gif - A path’s dash can have an offset, which sets the position of that dash — and subsequently its respective gap — along the path.The following image shows the path with multiple dashes, and showing how the offset changes their position so that some of them are pulled outside the boundaries of the path, while others take their place and are also shifted from one position to the other:
-
http://blogs.adobe.com/creativecloud/files/2015/08/dashes-offset.gif - By changing the offset of the dash to also be equal to the length of the path, the dash is kind of “pulled back” outside the boundaries of the path. Notice how the dash moves in the following image. It is not becoming shorter—it is only being “pulled” out of the path length, so only the gap (which also has the same length as the path) is now showing (hence the empty space).
-
http://blogs.adobe.com/creativecloud/files/2015/08/dash-offset.gif - Now, in order to simulate a line drawing effect, the dash offset is animated so that the dash comes back into view as shown visually in this image:
http://blogs.adobe.com/creativecloud/files/2015/08/drawing-dash.gif
Notice how the value of thestroke-dashoffset
changes from the full length (988 in Jake’s example) to zero (0) as the line animates its way into view.
This is how line drawing works. The path’s stroke is dashed such that the length of the dash is equal to the path itself, then the offset is set to the value of the length too, and then the offset is animated back to zero. Pretty great, right?
Now, because of the way the line drawing effect works, instead of having a stroke-dashoffset
value of zero, the SVG path
in the previous code snippet got a dash offset value equal to the path’s length which is 270.
The path is then animated by setting the stroke-dashoffset
to zero in CSS when the checkbox is checked. We also need to make sure we add a transition so that the effect works as expected and the path does not just jump from one state to the other abruptly:
label svg path {
transition: stroke-dashoffset .4s linear;
}
input[type="checkbox"]:checked \~ label svg path {
stroke-dashoffset: 0;
}
The following is the pen showing the animated checkbox style in action:
See the Pen Styling checkboxes with SVG by Sara Soueidan (@SaraSoueidan) on CodePen.
Radio inputs can be styled the exact same way, but rounding the SVG borders instead of keeping them angled. Indeed, Manoela Ilic has created a set of animated checkboxes and radio buttons that use the same principle explained in this section. Here is a preview of those examples:
You can check the animated checkboxes out here.
The only drawback to this technique is that it does not work in Internet Explorer because IE does not support CSS animations on SVG attributes. This has changed in MS Edge, but older versions of IE still don’t support it. So you need to either fall back to a non-animated version OR use JavaScript to animate the path.
You will often find yourself requiring JavaScript to create line drawing effects in SVG anyway, because, in most cases, you may not know the exact length of the path(s) you are animating. A DOM method exists that allows you to retrieve the value of the SVG path’s length, and then you can use the value to animate the dash offset. The following is a (modified/simplified) snippet from Jake’s article:
var path = document.querySelector('svg path');
path.getTotalLength(); // gets the length of the path
// Set up the starting positions
path.style.strokeDasharray = length + ' ' + length;
path.style.strokeDashoffset = length;
// Define our transition
path.style.transition = path.style.WebkitTransition =
'stroke-dashoffset 2s ease-in-out';
// Go!
path.style.strokeDashoffset = '0';
You can find the complete snippet and the interactive demo in Jake’s article, so make sure to check it out.
Conclusion
There is no better and best amongst these techniques, if you ask me. Each one has its pros and cons. Both CSS techniques are good, the SVG technique adds a nice and familiar interaction resulting in a nicer experience, but that is not to say that the UX of the CSS techniques is bad.
The flexibility of SVG and the many, many creative options you get when you use it is certainly a huge advantage. Plus, you can always get the best of both worlds by using an SVG image sprite instead of a PNG image sprite, if you want. SVGs do look much better, after all.
Choose the technique that suits you based on your design requirements and browser support (SVG starts at IE9 and above + all other modern browsers). I hope this article gave you a clear overview of the options you have, and that you found it useful.
Thank you for reading.