Pattern Markup: Restyled Checkbox
<!-- restyled input --> <div class="s-cb"> <input type="checkbox" id="s_input_1" checked> <label for="s_input_1"> Option 1 </label> </div>
Pattern Markup: Visually Hidden Checkbox
<!-- visually hidden --> <div class="c-cb"> <input type="checkbox" id="c_input_1" checked> <label for="c_input_1"> Option 1 </label> </div>
When customizing any form control, it must remain accessible to users. This includes those that navigate the web with their keyboard, along with those using assistive technologies (AT). Relying on HTML's native checkbox control will help ensure that all the necessary keyboard functionality and information exposed to browsers' accessibility trees (and thus then to AT) remains intact.
What follows are breakdowns of the two methods of creating custom styled checkboxes, as per the markup examples shown previously.
Restyled checkbox details
The most important thing to note about directly styling checkbox controls is that their inherent appearance needs to be reset by the CSS property
appearance: none. Use the
-webkit- prefix for this property if you need to target browsers where the unprefixed property is not supported.
Note that Internet Explorer and pre-Chromium Edge do not support this prefixed property, so their direct styling of checkboxes will be limited. These browsers do support a
::-ms-check selector, which can be used instead for legacy styling.
For more information on directly restyling checkboxes, please read One last time: custom styling radio buttons and checkboxes.
Visually hidden checkbox details
Previously, this had been the most robust method to create custom styles for checkboxes while still relying on the native HTML checkbox control - rather than using ARIA to create a fully custom checkbox. This technique is recommended only if you are in a situation where directly styling the native checkbox control is not a robust option for you (i.e., needing to support IE11 while also not being able to live with the fact that designs should be allowed to degrade when using outdated technology).
For the visually hidden checkboxes, the primary purpose of the wrapping element (
div with the
.c-cb class) is to act as a styling hook. The element uses
position: relative; to keep absolute positioned pseudo elements within the bounds of the wrapping element, mitigating potential quirks that could result from another ancestor element's position property.
The form control's
::after pseudo elements are used to create a visual "checkbox" that can be styled as you see fit. The example on this page merely recreates a recognizable, but custom, checkbox design.
The appearance of the custom checkbox is contingent on current state of the native visually hidden checkbox.
:focus states, along with a potential
disabled state all serve as hooks to visually change the styling of the custom checkbox.
Ensure proper styling for all states and situations
Regardless of which restyling method you choose (directly styling the native checkbox, or using a visually hidden checkbox) you will need to consider all the various states (checked, unchecked, indeterminate, focus) as well as media query styling adjustments (high contrast, dark mode, etc.) that restyling these controls will require. Note that restyling the checkbox directly will make some of these tasks easier (e.g., Windows High Contrast will be more likely to color the checkbox as expected when changing themes. But creating a faux custom checkbox will require additional effort to ensure the proper colors theme colors are respected).
Affects on Screen Reader Announcements?
With either styling method, the native checkbox remains in the DOM, and is the focusable element that will be interacted with by all users. These styling methods do not alter the way a screen reader will announce the checkboxes when focused by the Tab key.
Specifically for the visually hidden checkbox, people might think to hide it with a standard "visually-hidden" or "sr-only" CSS ruleset. These popular rulesets often include
position: absolute and squish the native
input down to a 1px by 1px invisible dot on the screen. Using this standard method to visually hide the checkbox will retain its keyboard accessibility, but impose screen reader quirks that should be avoided.
For instance, with NVDA 2018.3.2 and Firefox 62 to 64, using
position: absolute to remove checkboxes from the normal DOM flow creates a separate focus stop when navigating with the virtual cursor. This means that where using the down arrow key to navigate through the document would typically highlight (virtually focus) a native checkbox once, it instead highlights it twice.
This double virtual focus issue doesn't occur with unstyled checkboxes with Firefox + NVDA. This
position: absolute behavior also does not occur with Chrome + NVDA.
A user can hit the up arrow or down arrow keys to navigate between checkboxes and have their accessible names announced. However, in Firefox + NVDA, when navigating with the down arrow key NVDA will announce "checkbox [check or unchecked]". A second press of down arrow will announce "clickable [accessible name of the checkbox]". Note this separation of the form control from its accessible name does not occur when navigating by X or F keys, nor when using the Tab key to navigate the controls.
Along with the NVDA +
position: absolute announcement quirk, using standard visually hidden CSS to hide native checkboxes off screen, or as a single pixel in the document, will mean that some screen reader users will likely be unable to locate a checkbox.
For instance, people searching by touch, or using a mouse while a screen reader announces what is being hovered (NVDA Mouse setting, "Report role when mouse enters object" turned on, or when using ZoomText Fusion and mouse hovering a custom checkbox).
To mitigate a checkbox role not being announced to these people, the checkboxes have been positioned on top of their pseudo element styled counterparts (
position: relative; with a
z-index). The native checkbox remains visually hidden, but is announces as if it were not (as expected).
VoiceOver on macOS 10.13.6 + Safari 12.0.2
When re-checking the checkboxes after the latest releases of macOS and Safari, if navigating by Tab key or VO cursor, checkboxes will double announce their label. For instance:
Option 1. Option 1.. This only occurs if the checkbox precedes its
label. If the checkbox comes after the
label, or within it, in the DOM order, this behavior will not occur. This behavior does not occur when using Chrome + VoiceOver. (Bug filed: 45506356)
For additional information about creating minimal markup patterns for radio button and checkboxes, you should check out: