Building a custom checkbox component
The <howto-checkbox> element represents a boolean option in a form. Its most common use case is a dual-type checkbox that lets users toggle between checked and unchecked states.
When the element is first created, it attempts to self-apply the attributes role="checkbox" and tabindex="0". The role attribute lets assistive technology such as a screen reader announce the kind of control the user has encountered. The tabindex attribute adds the element to the tab order so it becomes keyboard-focusable and operable.
A checked state is indicated by a checked boolean attribute and a corresponding checked property set to true. The element also sets an aria-checked attribute to either "true" or "false", depending on the current state. Clicking the checkbox with a mouse or pressing the space bar toggles these states.
The component also supports a disabled state. If the disabled property is set to true or the associated attribute is applied, the element sets aria-disabled="true", removes the tabindex attribute, and returns focus to the document when the checkbox is currently the activeElement.
For the element to have an accessible name, it should be paired with a howto-label element.
Example usage
<style>
howto-checkbox {
vertical-align: middle;
}
howto-label {
vertical-align: middle;
display: inline-block;
font-weight: bold;
font-family: sans-serif;
font-size: 20px;
margin-left: 8px;
}
</style>
<howto-checkbox id="join-checkbox"></howto-checkbox>
<howto-label for="join-checkbox">Join Newsletter</howto-label>
Implementation details
The component's constructor runs whenever a new instance is created—whether by HTML parsing, document.createElement('howto-checkbox'), or new HowToCheckbox(). The constructor is a sensible place to build shadow DOM, but avoid touching attributes or light DOM children at this stage because they may not exist yet.
Cloning content from a <template> element outperforms innerHTML because it avoids the additional cost of HTML parsing. Defining key codes up front helps with keyboard event handling.
The connectedCallback() fires when the element is inserted into the DOM, and is where the initial role, tabindex, internal state, and event listeners should be set up.
Since a user can set a property on an element instance before the prototype is connected to the class, the _upgradeProperty() method inspects the instance for any properties that were assigned early and runs them through their class setters. Details for this pattern are in the documentation on lazy properties.
The disconnectedCallback() method fires when the element is removed from the DOM. This is a suitable place to release references and remove event listeners.
Attribute and property synchronization
Properties and their corresponding attributes should mirror one another. The property setter for checked handles truthy and falsy values and reflects those values to the state of the attribute. Full guidance is covered in the section on avoiding reentrancy.
The attributeChangedCallback() runs when attributes in the observedAttributes array are modified. It is well-suited for side effects like setting ARIA attributes.
Focus and disabled behavior
The tabindex attribute alone cannot fully remove focusability. An element with tabindex="-1" can still be focused by mouse or by calling focus(). To disable an element completely and keep it unfocusable, remove the tabindex attribute entirely.
When focus is on the element and the disabled state is applied, call the HTMLElement.blur() method to return focus to the document.
Keyboard and toggle logic
Shortcut combinations often used by assistive technology should not be intercepted. Other key presses should be ignored and allowed to fall through to the browser.
The _toggleChecked() method flips the current state through the checked setter. Since this action originates exclusively from a user interaction, it also dispatches a bubbling change event, which mimics the native behavior of <input type="checkbox">. The change event bubbles to be consistent with the native element's semantics.
Reference
- HowTo: Components on GitHub
- Checkbox Pattern in ARIA Authoring Practices 1.1
- What can ARIA Do?
- Using tabindex



