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>
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>
When customizing any form control, it must remain accessible to assistive technology and keyboard users. To keep this custom style accessible, a visually hidden checkbox, or restyling the checkbox directly are the cornerstones of maintaining the accessibility of this form control.
Visually hidden checkbox details
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 utilizes
position: relative; to keep absolute positioned pseudo elements within the bounds of the wrapping element, and not affected by another potential parent element's position property.
The form control's
:after pseudo elements are used to create a visual "checkbox" element that can be styled via CSS. This example merely recreates a recognizable, but custom, checkbox design.
The appearance of the custom checkbox is determined by the current state of the native checkbox.
:focus states, along with a potential
disabled state all serve as hooks to visually change the styling of the custom checkbox.
Directly styled checkbox details
The most important thing to note about directly styling checkbox controls is that their inherent appearance needs to be reset by vendor prefixed
appearance: none. Since Internet Explorer and Edge do not support this prefixed property, their direct styling of checkboxes will be limited. The Microsoft browsers do support a
::-ms-check selector, which can provide some help in styling for these browsers.
Affects on Screen Reader Announcements?
Since the native checkbox remains in the DOM, and is used as the focusable element that controls the state of the custom visualization, there are no alterations to how a screen reader announces styled 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 in Firefox + NVDA. This
position: absolute behavior also does not occur in 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 bug will not occur. This bug 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 Under-Engineered Custom Radio Buttons and Checkboxen, by Adrian Roselli.