How do you make a custom component keyboard accessible?
The short answer
A component is keyboard accessible when a user can reach it with Tab, operate it with the keys they expect, and see where focus is. Native elements like <button>, <a>, and <input> give you all of this for free. The work appears when you build a custom widget out of generic elements: then you have to make it focusable, handle the right keys, and describe its role and state so assistive technology understands it.
Start with the right element
The first rule is to not rebuild what the platform gives you. A <div> styled like a button is invisible to the keyboard:
<!-- Not focusable, no Enter or Space handling, no role --><div class="button" onclick="submit()">Submit</div><!-- Focusable, fires on Enter and Space, announced as a button --><button onclick="submit()">Submit</button>Reach for a real element whenever one exists. Only build a custom widget when there is no native equivalent.
Make custom elements focusable
If you must use a non interactive element, add it to the tab order with tabindex="0". Use tabindex="-1" for elements that should be focusable by script but not by Tab. Avoid positive tabindex values, which reorder the natural flow and cause confusion:
<div role="button" tabindex="0">Custom button</div>Handle the expected keys
Each widget role has keys users expect. A custom button must respond to both Enter and Space:
button.addEventListener('keydown', (event) => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); // stop Space from scrolling the page activate(); }});Other common expectations:
- a menu or list: Up and Down arrows move between items, Escape closes
- tabs: Left and Right arrows switch tabs
- a dialog: Escape closes it
Communicate role and state
Keyboard operability is only half of it. Assistive technology also needs to know what the element is and what state it is in:
<div role="button" tabindex="0" aria-pressed="false"> Mute</div>Update the state in script as it changes, for example toggling aria-pressed or aria-expanded, so a screen reader announces the new state.
Keep the focus indicator visible
Never remove the focus outline without replacing it. If the default ring does not fit the design, style a clear custom one:
.button:focus-visible { outline: 2px solid #6366f1; outline-offset: 2px;}:focus-visible shows the ring for keyboard users while keeping it hidden for mouse clicks, which is usually the behavior you want.
Interview Tip
The cleanest answer leads with "use the native element." Most keyboard accessibility comes free from <button>, <a>, and form controls. Frame the custom work as the three things you lose and have to rebuild: focusability with tabindex, the key handlers for that role, and the role and state through ARIA. Add that you never remove the focus indicator, and the answer is complete.
Why interviewers ask this
Custom dropdowns, toggles, and widgets are where keyboard support breaks most often. Interviewers want to see that your instinct is to use semantic elements first, that you know how to add focusability and the right key handlers when you cannot, and that you keep a visible focus indicator. It shows you treat keyboard users as a first class part of the UI rather than an afterthought.