Advertisement
  1. Web Design
  2. UX/UI
  3. Forms

How to Make Custom Accessible Checkboxes and Radio Buttons

Scroll to top
Read Time: 7 min

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:

1
<div class="wrapper">
2
  <input id="a11y-issue-1" name="a11y-issues" type="checkbox" value="no-issues">
3
  <label for="a11y-issue-1">There are no issues</label>
4
</div>

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.

1
.wrapper {
2
	position: relative;
3
}
4
5
.wrapper input {
6
	height: 40px;
7
	left: 0;
8
	opacity: 0;
9
	position: absolute;
10
	top: 0;
11
	width: 40px;
12
}

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:

1
.wrapper input + label::before {
2
	border: 2px solid;
3
	content: "";
4
	height: 40px;
5
	left: 0;
6
	position: absolute;
7
	top: 0;
8
	width: 40px;
9
}

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”:

1
.wrapper input + label::after {
2
	content: "";
3
	border: 4px solid;
4
	border-left: 0;
5
	border-top: 0;
6
	height: 20px;
7
	left: 14px;
8
	opacity: 0;
9
	position: absolute;
10
	top: 6px;
11
	transform: rotate(45deg);
12
	transition: opacity 0.2s ease-in-out;
13
	width: 12px;
14
}

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:

1
.wrapper input:checked + label::after {
2
	opacity: 1;
3
}

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:

1
<div class="wrapper">
2
	<input id="a11y-issue-1" name="a11y-issues" type="checkbox" value="no-focus-styles">
3
	<label for="a11y-issue-1">
4
		Focus styles are not present
5
		<svg aria-hidden="true" focusable="false">
6
			// Code for SVG
7
		</svg>
8
	</label>
9
</div>

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):

1
.wrapper input + label svg {
2
    border: 0;
3
    fill: blue;
4
    height: 20px;
5
    width: 20px;
6
    opacity: 0;
7
    position: absolute;
8
    left: 10px;
9
    top: 10px;
10
    transition: opacity 0.2s ease-in-out;
11
}

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:

1
.wrapper input:focus + label::before {
2
	box-shadow: 0 0 0 3px #ffbf47;
3
}

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:

1
.wrapper input:focus + label::before {
2
	box-shadow: 0 0 0 3px #ffbf47;
3
	outline: 3px solid transparent; /* For Windows high contrast mode. */
4
}

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:

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Web Design tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.