CSS Tutorials

How To Make Stylish Checkboxes And Radio Buttons With CSS3

4.66/5 (59)

We're all familiar with HTML checkboxes and radio buttons. The most frustrating thing about them, is that there's no way for us to change the way they look. They look in a different way depending on your operating system and browser, and we can't style them using CSS.

This can be annoying for most designers, who want to make those elements appealing, so that they don't spoil the design. In this tutorial I'll show you how to make them look real cute with a simple trick. Let's dig in!

Be Strong. Hide Your Checkboxes

This may sound counterintuitive, but to make our checkboxes and radio buttons look great, we have to hide them and forget about them! Yes, there's no need for them to stick around anymore, so we just apply a simple rule:

.section input[type="radio"],
.section input[type="checkbox"]{
  display: none;

But Matthew, you might say, what are we going to do without them? No worries, we'll build our own checkboxes, with blackjack and… well, let's just get into it.

Radio Buttons

First, there's our mark-up:

<section id="first" class="section">
    <div class="container">
      <input type="radio" name="group1" id="radio-1">
      <label for="radio-1"><span class="radio">Coffee</span></label>
    <div class="container">
      <input type="radio" name="group1" id="radio-2">
      <label for="radio-2"><span class="radio">Tea</span></label>
    <div class="container">
      <input type="radio" name="group1" id="radio-3">
      <label for="radio-3"><span class="radio">Cappuccino</span></label>

We have a section, where we're going to contain three radio buttons. We'll cover checkboxes further in this tutorial, the principle is the same. Each input is wrapped into a div with a container class. Also, each input has a label with a span in it.


Since we've hidden the radio and checkbox inputs, the only way for us to access them would be by using a label tag. To work properly the label tag has to contain the for attribute with an ID of corresponding input. If an input has an ID radio-1, then the for attribute should also be radio-1.

You might be wondering why did I wrap the text inside each label into a span:

<div class="container">
      <input type="radio" name="group1" id="radio-1">
      <label for="radio-1"><span class="radio">Coffee</span></label>

Since we're going to style radio buttons with ::before and ::after pseudo elements, we need a parent element, based on which they can be positioned. In this case it will be our label:

.container label {
  position: relative;

Here is a set of styles which is shared among both checkboxes and radio buttons:

.container span::before,
.container span::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  margin: auto;

Top and bottom properties set to zero and combined with margin: auto; allow our elements to have a centered horizontal position.

Here's how the rest of the styles look like:

.container span.radio:hover {
  cursor: pointer;
.container span.radio::before {
  left: -52px;
  width: 45px;
  height: 25px;
  background-color: #A8AAC1;
  border-radius: 50px;
.container span.radio::after {
  left: -49px;
  width: 17px;
  height: 17px;
  border-radius: 10px;
  background-color: #6C788A;
  transition: left .25s, background-color .25s;
input[type="radio"]:checked + label span.radio::after {
  left: -27px;
  background-color: #EBFF43;

The most important part is the last set of rules, which basically does the whole trick. The :checked pseudo class allows us to make changes to elements when the input is checked. With a '+' selector we can choose the next sibling element, and target our span.radio, where we apply new rules to the ::after pseudo element. In this case we change its horizontal position and the color.

In order to make the switch smooth, we assign the transition of 0.25 seconds to the left property and background-color. Now when we click on the radio button, the switch is moving smoothly instead of jumping quickly.


If you need to create custom checkboxes, the method is the same. Take a look at the styles:

.container span.checkbox::before {
  width: 27px;
  height: 27px;
  background-color: #fff;
  left: -35px;
  box-sizing: border-box;
  border: 3px solid transparent;
  transition: border-color .2s;
.container span.checkbox:hover::before {
  border: 3px solid #F0FF76;
.container span.checkbox::after {
  content: '\f00c';
  font-family: 'FontAwesome';
  left: -31px;
  top: 2px;
  color: transparent;
  transition: color .2s;
input[type="checkbox"]:checked + label span.checkbox::after {
  color: #62AFFF;

The only difference is that we're using an icon from the FontAwesome family as our ::after pseudo element. By default it's transparent, but when the checkbox is checked, the icon becomes blue.


If you want to use a FontAwesome icon in your pseudo element, you have to include its code into the content property, and specify the font-family property as FontAwesome. For example:

content: '\f00c';
font-family: 'FontAwesome';

The code f00c is preceded by a backslash, which is required to escape the unicode character. The unicode can be found on the page of your chosen icon:

A FontAwesome icon page with a unicode character

Final Result

That's it. Now we've created a fully-functional and beautiful checkboxes and radio buttons, which you can tweak and use for your own projects. You can view the full source code in the CodePen demo:

If you find this tutorial useful, or have any questions, feel free to share your thoughts in the comments below. Thanks!

17 Responses

  1. Great post Matthew! What I find very helpful is that you add stylings step by step and explain what it does in between. I’ve learned something today 🙂

    1. You’re welcome, Jep! I’m glad you found my tutorial useful 🙂

  2. Wow, thank you so much for the sharing, the touch to flat design is incredible but I prefer no rounded borders, so I did a little change…

    1. Sure, that’s what it’s all about, I provide an example and you customize it to your needs 🙂 Glad you liked it!

  3. Hi Everyone,
    I am graphically illiterate (ie no idea how to do this stuff). However, I would like to send a friend an email (or document) with and ‘yes’ and ‘no’ check box but where it’s impossible to check the ‘no’ box. I think I’ve seen this on some joke thing ages ago – when trying to check the ‘no’ box it moves….

    Any ideas about how I can do this?
    Thank you

    1. LOL! Just use a static image, that would be the easiest. In jQuery javascript you would do something like this

      $(‘input[“type=checkbox”]’).bind(‘click’, function(e) {
      e.preventDefault(); // this ignores the default behavior of selecting the checkbox

    2. Email is a tricky medium for tweaking the elements, mainly because not every email client supports the newest CSS rules. Probably indeed the best solution would be to send a static image 🙂

  4. Great tutorial. Are you thinking of improving this for accessibility (i.e. keyboard navigation)?

    1. Hey Mike, I’m happy you like it! That’s a great idea, I’ll consider it for my future publications.

  5. how can we use the radio toggle design for checkboxes? I just want a yes and no slide toggle. Sure I will figure it out on my own soon, after scouring the web for an hour.

    1. Hi Justin!

      Thanks for finding my article useful, and sorry for my late response. Here’s what you need to do in order to achieve the effect you’re looking for:

      1. Locate the block with styles for radio buttons (it’s commented out as /* Radio buttons */) and replace each occurrence of “radio” with “checkbox”.
      2. Find the style section with checkboxes /* Check-boxes */ and remove it OR if you want to keep the styles, rename the checkbox class to “radio” and the effect will be replaced.

      Hope this helps!


  6. “Be Strong. Hide Your Checkboxes” and say good by to the keyboard.

    1. Yeah, that sucks.. Sometimes we have to choose the lesser of the two evils.

      1. If you use “opacity: 0” instead of “display: none”, the checkbox/radio is still technically there on the page. For bonus points, you could position it over the top of the simulated controls so that (in some cases) the user is still interacting with the original input.

  7. Hi, Thanks for the great ideas, really useful. Doesn’t this break most client side validation as the focus can’t be set to the input element as it’s not visible. With z-index instead of display none you could probably get round that.

    Thanks again for the tutorial!

  8. How would you get/set the value of a checkbox or catch the onChange event?

Leave a Reply

Your email address will not be published. Required fields are marked *