Keyboard Navigation and Focus
Make every interactive thing reachable and operable without a mouse.
A huge share of users — power users, people with motor disabilities, anyone whose mouse just died — drive the web from the keyboard. If a control can be clicked but not tabbed to, it's broken for them.
Tab order and tabindex
- Native interactive elements (
<a>,<button>,<input>) are focusable automatically — another reason to prefer them. tabindex="0"adds a custom element into the natural tab order.tabindex="-1"makes an element focusable by script (.focus()) but skips it in tabbing.- Avoid positive
tabindexvalues liketabindex="5"— they hijack the order and become a maintenance nightmare.
Never kill the focus ring
The visible outline on a focused element is how keyboard users know where they are. Removing it with outline: none and nothing in its place is one of the most common accessibility crimes on the web. If you dislike the default ring, style a nicer one with :focus-visible — don't delete it.
button:focus-visible {
outline: 3px solid #005fcc;
outline-offset: 2px;
}Example
When to use it
- A developer ensures all interactive elements can be reached with Tab and activated with Enter or Space without a mouse.
- A custom modal uses tabindex="-1" on the dialog and JavaScript focus management to trap focus inside while it is open.
- A complex widget uses roving tabindex (tabindex="0" on one item, tabindex="-1" on others) to allow arrow-key navigation within a group.
More examples
Focusable elements and tabindex
Links, buttons, and inputs are focusable by default; tabindex="0" adds a div to the tab order with a role.
<!-- Naturally focusable elements -->
<a href="/courses">Courses</a>
<button>Submit</button>
<input type="text">
<!-- Making a div keyboard-focusable -->
<div tabindex="0" role="button"
onkeydown="if(event.key==='Enter'||event.key===' '){this.click()}">
Custom button
</div>Skip link to bypass navigation
A visually hidden skip link becomes visible on focus, letting keyboard users bypass repeated navigation fast.
<a href="#main" class="skip-link"
style="position:absolute;left:-999px"
onfocus="this.style.left='4px'">
Skip to main content
</a>
<header>...<nav>...</nav></header>
<main id="main">
<h1>Page Content</h1>
</main>Focus trap inside a dialog
Trapping Tab and Shift+Tab inside an open modal prevents focus from escaping to background content.
<dialog id="dlg">
<h2>Confirm</h2>
<button id="cancel">Cancel</button>
<button id="ok">OK</button>
</dialog>
<script>
const dlg = document.getElementById("dlg");
dlg.showModal();
dlg.addEventListener("keydown", e => {
if (e.key !== "Tab") return;
const focusable = [...dlg.querySelectorAll("button")];
const first = focusable[0], last = focusable.at(-1);
if (e.shiftKey && document.activeElement === first) {
e.preventDefault(); last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault(); first.focus();
}
});
</script>
Discussion