1. Web Design
  2. Accessibility

How to Make Custom Accessible Checkboxes and Radio Buttons

Read Time:6 minsLanguages:

Form elements like checkboxes and radio buttons look different depending on the user’s browser and operating system. For this reason designers and developers have long been styling their own checkboxes and radio buttons, aiming for consistency no matter the browser or OS.

This is perfectly fine, but at the same time we need to make sure our checkboxes and radio buttons remain accessible to assistive technology (AT) and keyboard users. In this tutorial I’ll explain what this means and how we can do things correctly in a few different ways.

A11y From the Beginning

This tutorial is part of Web Accessibility: the Complete Learning Guide, where we’ve collected a range of tutorials, articles, courses, and ebooks, to help you understand web accessibility from the beginning.

Browser Default Styles

Let’s begin by looking at how your browser renders checkboxes by default. As mentioned, what you see here will depend on your browser and operating system. 

You’ll notice you can use your mouse to switch the checkboxes on and off, plus you can use your keyboard (jump through using TAB and toggle using SPACE, though this might alter depending on your settings).

1. Styling Custom Checkboxes

Time to build our own. We’re going to visually “hide” the default checkboxes, placing custom-built versions over the top. The first thing we’ll need to look at is the markup.

The HTML Markup

Markup for a single checkbox looks like this:

We use a <div> wrapper to help with custom styles, but other than that the HTML here is standard semantic form markup. The magic happens when we visually hide our <input type="checkbox"> using the CSS rule opacity: 0.

This hides our checkbox visually, allowing us to go ahead and create our own. It’s important that we don’t hide it using display: none because this would hide the checkbox from both browser and assistive technology (AT) users, and we would also lose keyboard interactions.

You’ll notice that, even though we’re hiding it, we give the checkbox a height and width of 40px. This gives us a clear, functional target area to place under our fabricated checkbox.

The wrapping <div> has a position: relative CSS rule. This helps us to position the checkbox and label ::before and ::after pseudo elements using position: absolute.

Add Visual Checkbox Using Pseudo Elements 

We are still missing a visual checkbox. To solve this we first use a label::before element to add a border:

I have used a 2px solid border and inherited color, but you can use a different border color if you wish. Note that we position and size this in the same way as our transparent checkbox.

With some extra margins for the labels to give us some spacing, this is what our checkboxes look like at this point:

Custom checkbox styles with 2px borderCustom checkbox styles with 2px borderCustom checkbox styles with 2px border
Custom checkbox styles with 2px border.

The next step is to use the label::after pseudo element to style the “check”:

We create the check using an element which we give a four pixel border for bottom and right. Then we rotate it 45 degrees: bingo! A custom check. You could also use different border colors to match your design.

Custom check styles with 4px border bottom and right When rotating this 45 degrees it looks like a checkCustom check styles with 4px border bottom and right When rotating this 45 degrees it looks like a checkCustom check styles with 4px border bottom and right When rotating this 45 degrees it looks like a check
Pseudo element styled with 4px border bottom and right. When rotated 45 degrees it looks like a check

At this stage you won’t be able to see anything; we’re still hiding the check visually using opacity: 0. Let’s remedy that!

Reveal Custom Check Using :checked Pseudo Selector

The :checked pseudo selector targets a checkbox when it’s toggled to the “on” state. We can use this to change the opacity of our custom check:

With that done, this is what we have:

Note: there’s one other thing we need to include in this, and that’s “focus styles”. We’ll discuss them in the next demo.

2. Custom Checkbox Using Inline SVG

Let’s take things up a level. Instead of a pseudo element we could use a custom SVG icon for our check. To do so, we would place the SVG inside the label:

In most cases SVG is just decorative, so aria-hidden="true" hides it from AT devices.

Add SVG Styles

We need to apply a couple of styles to the SVG element so that it’s positioned and sized properly. We can also use the fill property to change its color (blue in this case):

Remember Focus State Styles

Inspiration for my example checkbox styles was taken from those found in GOV.UK’s design system for form elements (a brilliant resource, go and take a look). The focus styles are as important as in any focusable element:

We use a box-shadow for focus styles because it will respect rounded borders, which we will also use for radio buttons later on

Focus styles for checkbox yellow border around checkboxFocus styles for checkbox yellow border around checkboxFocus styles for checkbox yellow border around checkbox
Focus styles for checkbox: yellow border

Add Focus Styles for Windows “High Contrast Mode”

Windows High Contrast Mode removes box-shadow rules, so for this reason we also need to add transparent outline styles:

This transparent outline appears as an extra border in high contrast mode.

Transparent outline shows as border in Windows High Contrast ModeTransparent outline shows as border in Windows High Contrast ModeTransparent outline shows as border in Windows High Contrast Mode
Transparent outline appears as an extra border in Windows High Contrast Mode

3. SVG as Pseudo Element Background

Besides using SVG code inline, we could also recreate a version of the first custom checkboxes we made, using SVG as a background for the pseudo element instead of building our own “check” with borders. We’ve covered all the techniques you need to know for this, so here’s the demo for you to dissect:

4. Custom Radio Button Styles

Pretty much all the styles and logic we’ve used for our checkboxes so far are the same for radio buttons. Take a look at the demo and see for yourself (to navigate with the keyboard, use the Arrow Keys):

The only apparent difference is our using border-radius and styling the :checked state a little differently. You could certainly use an SVG icon in this case too–I’ll leave that as homework for you! Show us your results in the comments section.

5. Testing for Accessibility

Testing is important part of the process when tinkering with native HTML elements. My tests are far from perfect but this is how I did things for this tutorial:

  • Keyboard test in all modern browsers, and IE11.
  • Voiceover using Safari.
  • NVDA Screenreader using Firefox.
  • Talkback using an Android device.
  • Color blind conditions using Sim Daltonism software.
  • High Contrast Mode in Windows.

Arguably this list is missing voice recognition software like Dragon, or switch devices. But in all my tests the custom checkboxes and radio buttons behaved in the same way as native elements.

Conclusion and References

Hopefully this tutorial has given you an understanding of how to create custom styles for checkboxes and radio buttons, whilst still building for accessibility.

I highly recommend learning more about custom form elements from these resources:

Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.