Costa Rica

NEOART

Styling checkbox and radio input, the fun way.

Today I am going to show you how to style a checkbox and a radio input in a cross-browser compatible way without using any additional element.

This method applies only to modern browsers, it should work on Edge and recent versions of Firefox, Chrome and Opera; it also works on Safari iOS 11 but I have no way of testing previous versions so let me know how it goes.

These are your standard checkbox and radio in their 4 possible states (unchecked, checked, disabled, checked disabled).


The first thing we are going to do is to remove all default styles and make it look like an empty element, add some background and border so we can actually see it, and add height and width.

input[type=checkbox],
input[type=radio] {
 -moz-appearance:     none;
 -webkit-appearance:  none;
  background:         #fff;
  background-repeat:  no-repeat, no-repeat, repeat;
  border:             1px solid #707071;
  border-radius:      0;
  cursor:             default;
  height:             19px;
  margin:             0 5px 5px 0;
  vertical-align:     top;
  width:              19px;
}

The border-radius is needed for Safari otherwise the elements will still have rounded borders; this is how they look now:


I am going to use the styles from FLOD player for this tutorial, lets add some color and see how they look.

input[type=checkbox] {
  background-image:       linear-gradient(#fff, #fff),
                          linear-gradient(#707071, #707071),
                          linear-gradient(#f7f7f8, #ddddde);
  background-position:    4px 4px, 3px 3px, 0 0;
  background-size:        4px 9px, 6px 11px, 100% 100%;
}

input[type=radio] {
  background-image:       linear-gradient(#fff, #fff),
                          linear-gradient(#707071, #707071),
                          linear-gradient(#f7f7f8, #ddddde);
  background-position:    8px 8px, 9px 9px, 0 0;
  background-size:        0 0, 0 0, 100% 100%;
}

How it works:
CSS gradients are applied as background images in layers so the bottom layer (that would be the last gradient in the list) will look like this the middle layer is the border for the checkmark and it looks like this and the top layer (I'm using orange instead of white) will add the fill of the checkmark like this

If we put everything together the final result will be this

The CSS background has several properties that we need to make this work, background-image is used to set the layer of gradients that we need; background-repeat decides if the gradient is to be repeated and on which axis; background-position set the start position for the gradient and background-size set the dimensions of the gradient.

For example on the first layer of our background (the topmost one) we have this properties set:

background-image:    linear-gradient(#fff, #fff);
background-position: 4px 4px;
background-repeat:   no-repeat;
background-size:     4px 9px;

Well, now they look all the same so we need to add some styling to the checked state to make them look different (notice the pseudo-class in the css selector).

input[type=checkbox]:checked {
  background-image:       linear-gradient(#fff, #fff),
                          linear-gradient(#2a5b96, #2a5b96),
                          linear-gradient(#bedcfa, #9bc8f5);
  background-position:    9px 4px, 8px 3px, 0 0;
  border-color:           #2a5b96;
}

input[type=radio]:checked {
  background-image:       linear-gradient(#fff, #fff),
                          linear-gradient(#2a5b96, #2a5b96),
                          linear-gradient(#bedcfa, #9bc8f5);
  background-position:    4px 4px, 3px 3px, 0 0;
  background-size:        9px 9px, 11px 11px, 100% 100%;
  border-color:           #2a5b96;
}

Much better, now they are fully functional but we are still missing the disabled state and the focus indicator is not the same in all the browsers.

input[type=checkbox]:disabled,
input[type=radio]:disabled {
  background-color:       #f0f0f1;
  background-image:       linear-gradient(#fff, #fff),
                          linear-gradient(#ddddde, #ddddde);
  border-color:           #ddddde;
}

input[type=checkbox]:focus,
input[type=radio]:focus {
  outline:                2px solid #ffcc00;
}

This is all we need but we can add some transitions to make it look nicer when you click on it.

We cannot use transitions on the gradients because they are treated like images but we can play around with the background position and size.

input[type=checkbox] {
 -webkit-transition:      background-position 0.1s;
  transition:             background-position 0.1s;
}

input[type=radio] {
 -webkit-transition:      background-position 0.1s,
                          background-size 0.1s;
  transition:             background-position 0.1s,
                          background-size 0.1s;
}

Now it would be nice if you can have a label that changes text with the different states without using javascript?

input[type=checkbox] + span[data-unchecked]::before,
input[type=radio]    + span[data-unchecked]::before {
  content:                attr(data-unchecked);
}

input[type=checkbox] + span[data-checked]::before,
input[type=radio]    + span[data-checked]::before {
  content:                attr(data-checked);
}

How it works:
we are going to add a span element right after each input, like this:

<input type="checkbox">
<span data-checked="On" data-unchecked="Off">

the span element has 2 attributes data-checked and data-unchecked, they represent the text to be displayed for each state. The CSS selector

input[type=checkbox] + span[data-?]::before

will target the span element and create a pseudo-element (::before) inside it with the content taken from the right attribute.

Remember that you can also use radial gradients and border-radius to create your input style, just use your imagination.

This is it for today, you can download the example code right below; If you have any question or you would like a tutorial about something specific just let me know in the comments or you can find my e-mail in the contact page.

Download Example Code