The Old Story

Back in the days, HTML components like checkbox were pretty tricky to style the way we wanted to. Often these components stud out of the overall page design which wasn't so good for the user experience.

Not to mention that each browser had (and still has) its own appearance for the control thus contributing to the difference in the look and feel across various browsers.

To overcome these situations developers used to do a lot of hacking like hiding the input, creating images and icons for the checkmark and adding a bunch of JavaScript code to handle the checking/unchecking. If you ask me, this is not that pretty and it seems like a lot of work to achieve something simple.

The New Story

The times described above are long gone and we are closer than ever to the universal way of styling the checkboxes in a manner when they will look and feel the same across all browsers, especially with the news that the Microsoft is building a chromium-based browser. You can read about it here.

The new story, from my perspective, is that we can style the checkbox without hiding it and without adding SVG images and JavaScript code. This can be done by using the following instead:

  1. The CSS appearance property
  2. The HTML check mark symbol (✓)

Appearance Property

The appearance property is used to display an element using a platform-native styling based on the users operating system's theme.

This property supports many values, but the one interesting for us is the value none. Basically, we want to remove all the native styling and apply the custom one. In the end, our checkbox will have nice looking colors and transitions and the most important thing, it will look and feel the same in all major browsers.

Example usage:

.my-class {
   -webkit-appearance: value;
   -moz-appearance: value;
   /* -o-appearance: value; - Not required since the new version of Opera uses - 
      webkit prefix for this property, but we've added it nevertheless just to be aware 
      of it 
    */
   appearance: value;
}

Ok, let's dive into the code.

HTML

Our HTML markup is pretty straight-forward. We have a label wrapping our input and a span to hold the text within. It looks like this:

<label class="checkbox">
    <input type="checkbox" />
    <span>Check Me</span>
</label>

Nothing too fancy here. We used a wrapper element to make it easier to align the inner items vertically. This is done with the flexbox layout which we will see in the CSS section.

CSS

The CSS styling looks like this:

.checkbox {
    display: inline-flex;
    cursor: pointer;
    position: relative;
}

.checkbox > span {
    color: #34495E;
    padding: 0.5rem 0.25rem;
}

.checkbox > input {
    height: 25px;
    width: 25px;
    -webkit-appearance: none;
    -moz-appearance: none;
    -o-appearance: none;
    appearance: none;
    border: 1px solid #34495E;
    border-radius: 4px;
    outline: none;
    transition-duration: 0.3s;
    background-color: #41B883;
    cursor: pointer;
  }

.checkbox > input:checked {
    border: 1px solid #41B883;
    background-color: #34495E;
}

.checkbox > input:checked + span::before {
    content: '\2713';
    display: block;
    text-align: center;
    color: #41B883;
    position: absolute;
    left: 0.7rem;
    top: 0.2rem;
}

.checkbox > input:active {
    border: 2px solid #34495E;
}

If you think this is still a lot of CSS let me remind you that we do not need the flexbox layout or the transitions in order to implement this styling. This is added to make it more elegant. If we remove the extra CSS all we need to do is to remove the default styling by setting the appearance to none, add borders and coloring and set the HTML symbol.

Let us break down the important parts to back up the statement above. The first step is to use the appearance property and remove the default styling:

...

-webkit-appearance: none;
-moz-appearance: none;
-o-appearance: none;
appearance: none;

...

Hopefully, this property will become a standard soon and we would be able to use it without the browser specific prefixes.

Next, we need to provide our custom borders and backgrounds:

...

border: 1px solid #34495E;
border-radius: 4px;
outline: none;
background-color: #41B883;
cursor: pointer;

...

And last, we will use the ::before pseudo-class to style the HTML symbol. The CSS below will display the HTML symbol nicely colored and positioned after we check the input field.

...

content: '\2713';
display: block;
text-align: center;
color: #41B883;
position: absolute;
left: 0.7rem;
top: 0.2rem;

...

And that is it! It is really that simple. No more JavaScript overkill to achieve these styles in order to match the checkbox design with the rest of the page. We can safely achieve it with the CSS provided here.

Here is a fiddle to play around with the code:

Further Reading

If you're interested in CSS variables, check out this or this post published on my blog.

See the official docs of the appearance property.