Custom controls need explicit keyboard support
Aliases: custom widget keyboard · ARIA widget keyboard · authored focus
What it is
Browsers and operating systems wire keyboard behaviour only to native controls: a button is tabbable and fires on Space or Enter; a select changes value with arrows. When a div, span, canvas or custom component impersonates those controls, the look can match; the keyboard behaviour does not arrive for free. That is explicit keyboard support for custom controls: role, focus and key handling have to be authored. Style and supplemental attributes will not do the job.
Putting a role name on an element does not make it answer to arrow keys. Supplemental attributes change how assistive technology names it; they do not add key behaviour the element never had.
Why it happens
Native keyboard behaviour lives in the user agent: reachability, activation keys, arrows inside composite widgets — a platform contract. A custom control cuts that contract. If the author syncs appearance and a click listener only, the pointer path is open and the keyboard path is dead: the node may be absent from the tab sequence, or present while Enter does nothing.
Composites have a second layer. Tab should enter and leave the widget as a whole; options inside move with arrows — roving tabindex and kin. Skip the inner keymap and users can “reach” the control but not choose inside it: reachable on paper, inert in use. Menus, tabs, trees, grids and date pickers fail here most often, because their keyboard contracts are much longer than “press once”.
Studying it
Paired test: the same interaction built native and custom. Walk select, change value, cancel from the keyboard. Record swallowed keys, whether focus enters the interior, whether Escape exits.
Environments: keyboard-only on desktop; an external keyboard or Switch Access on mobile, because a custom control that is easy to tap often fails completely once a keyboard is attached.
Automation can flag “focusable without a role”. It cannot flag “has a role but no arrows”. That takes actual key presses.
Where it stops holding
When a native element already ships the behaviour, do not wrap it in a custom skin — that deletes a keyboard contract that existed. Canvas games or visualisations may use an application-level focus model, but that model still has to implement reach, activate and leave, and must not swallow every browser shortcut. Announcing a name to a screen reader without handling keys can be “hearable” for some blind users and still unusable for someone who simply cannot use a mouse.
Applying it
- If a native
button,a,inputorselectwill do, do not fake it with adiv. - When custom is unavoidable, ship together: focusability, a visible focus indicator, activation keys, and the arrows / Escape that role expects. A role name is not completion.
- For composites, use roving tabindex or an equivalent so Tab stops once and arrows move inside.
- How to check: open a custom dropdown or date picker from the keyboard, change a value, cancel. If only clicking works, explicit support is still missing.