Skip to content

Instantly share code, notes, and snippets.

@samikeijonen
Last active October 21, 2018 20:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save samikeijonen/619a7395cec96dcb8ac46188502af19d to your computer and use it in GitHub Desktop.
Save samikeijonen/619a7395cec96dcb8ac46188502af19d to your computer and use it in GitHub Desktop.

Styling checkbox and radio button in accessible way

Form elements like checkbox and radio button look different in different browser and operating system combinations. Therefore designers and developers have been styling them to look consistent, and even better.

In the same time we need to keep checkbox and radio button accessible to assistive technology (AT) and keyboard users. In this article I summarize what does it mean.

Custom checkbox styles

Here is the end result for custom checkbox in Github. Markup for one checkbox looks like this:

<div class="checkbox-radio-button-wrapper checkbox-radio-button-wrapper--styled">
  <input id="a11y-issue-11" name="a11y-issues-1" type="checkbox" value="no-issues">
  <label for="a11y-issue-11">There are no issues</label>
</div>

<div> wrapper is added for the custom styles but other than that the markup is the same as semantic form markup. Magic happens when we visually hide <input type="checkbox"> using CSS rule opacity: 0.

.checkbox-radio-button-wrapper--styled {
	position: relative;
}

.checkbox-radio-button-wrapper--styled input {
	height: 40px;
	left: 0;
	opacity: 0;
	position: absolute;
	top: 0;
	width: 40px;
}

It's important that we do not hide checkbox using display: none rule. It would hide checkbox completely from AT users and we would loose keyboard interactions.

The wrapping <div> have position: relative CSS rule. That helps us to position checkbox and label ::before and ::after pseudo elements where we want using position: absolute.

Visual checkbox using label::before and label::after

We are still missing visual custom checkbox. First we use label::before element to add border of the checkbox:

.checkbox-radio-button-wrapper--styled input + label::before {
	border: 2px solid;
	content: "";
	height: 40px;
	left: 0;
	position: absolute;
	top: 0;
	width: 40px;
}

I have used 2px solid border and inherit color but you could have different border color if needed. Note that we position also this absolute the same way as our "empty" checkbox. And with same height and width to match the styles.

At this point our checkboxes looks like this:

https://www.dropbox.com/s/3dst1q9rdl9jf40/checkbox-styles.png?dl=0

(Alt text: Custom checkbox styles with 2px border.)

Next step is using label::after pseudo element to style the "check":

.checkbox-radio-button-wrapper--styled input + label::after {
	content: "";
	border: 4px solid;
	border-left: 0;
	border-top: 0;
	height: 20px;
	left: 14px;
	opacity: 0;
	position: absolute;
	top: 6px;
	transform: rotate(45deg);
	transition: opacity 0.2s ease-in-out;
	width: 12px;
}

We create the check using element where we have four pixel border for bottom and right. Then we rotate it 45 degrees, and it looks like our custom check. Also in here you could use different border color to match your design.

https://www.dropbox.com/s/pm7yhkp3yh6enrh/check.png?dl=0

(Alt text: Custom check styles with 4px border bottom and right. When rotating this 45 degrees it looks like a check.)

Note that we're still hiding the check visually using opacity: 0.

Show custom check using :checked pseudo selector

:checked pseudo selector represent checkbox when it's toggled to "on" state. We use this to change opacity of our custom check:

.checkbox-radio-button-wrapper--styled input:checked + label::after {
	opacity: 1;
}

Check using SVG

We can also use custom SVG icon for our check. SVG would be inside the label:

<div class="checkbox-radio-button-wrapper checkbox-radio-button-wrapper--styled">
	<input id="a11y-issue-2222" name="a11y-issues-3" type="checkbox" value="no-focus-styles">
	<label for="a11y-issue-2222">
		Focus styles are not present
		<svg aria-hidden="true" focusable="false">
			// Code for SVG
		</svg>
	</label>
</div>

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

Focus styles

My example checkbox styles are very similar than in GOV.UK design system for form elements. Focus styles are as important as in any focusable element:

.checkbox-radio-button-wrapper--styled input:focus + label::before {
	box-shadow: 0 0 0 3px #ffbf47;
}

We are using box-shadow for focus styles because that respect round borders as we can see in radio button.

https://www.dropbox.com/s/yjxqwyavjpqhmt9/focus-styles-checkbox.png?dl=0

(Alt text: Focus styles for checkbox: yellow border around checkbox.)

https://www.dropbox.com/s/sqtw9t5xhbpzjff/focus-styles-radio-button.png?dl=0

(Alt text: Focus styles for radio box: yellow round border around radio button.)

Focus styles for Windows High Contrast Mode

Windows High Contrast Mode removes box-shadow rules, that's why we add transparent outline styles:

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

Transparent outline looks like an extra border in high contrast mode.

https://www.dropbox.com/s/zqwc2cqb4bboshz/focus-styles-high-contrast-mode.png?dl=0

(Alt text: Transparent outline shows as border in Windows High Contrast Mode.)

Custom radio button styles

Pretty much all the styles and logic are the same in radio button as in checkbox. Here is the end result for custom radio button in Github.

Only difference is using border-radius and styling checked state a little bit differently. You could certainly use SVG icon in here also. I'll leave that as a homework for you:)

Testing

Testing is important part when tinkering native HTML elements. My tests are far from perfect but this is how I tested:

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

In all my tests the custom checkbox and radio button behaved in the same way as native elements.

Test list is missing for example voice recognition software like Dragon, or switch devices.

Conclusion and references

Hopefully this article gave you examples how to create custom styles for checkbox and radio button, and still maintain the build in accessibility.

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment