Make Every Interactive Thing Reachable
Focus & Keyboard
Keyboard users navigate with Tab. Every interactive element needs a visible focus indicator and a logical tab order.
What you'll learn
- Style `:focus-visible` properly
- Set tab order with `tabindex`
- Test keyboard navigation
Some users navigate entirely with the keyboard — Tab to move between interactive elements, Enter / Space to activate. Every interactive element needs a visible focus indicator and a logical tab order.
:focus-visible — Smart Focus Rings
The browser’s default focus ring is ugly but ESSENTIAL for
keyboard users. Removing it (outline: none) without a
replacement breaks accessibility.
The modern approach uses :focus-visible to show the ring only
when focus came from the keyboard:
button:focus { outline: none; } /* drop the default */
button:focus-visible {
outline: 2px solid blue;
outline-offset: 2px;
} Click with a mouse → no ring. Tab to it → ring appears. Best of both worlds.
What’s Focusable
By default, these elements are focusable:
<a href>— links with href<button><input>,<select>,<textarea><iframe>,<audio controls>,<video controls>- Anything with
tabindex(see below)
A <div> is NOT focusable — use a real element.
tabindex
| Value | Effect |
|---|---|
tabindex="0" | Focusable, in tab order |
tabindex="-1" | Programmatically focusable (.focus()) but skipped by Tab |
tabindex="1"+ | Positive — DON’T USE. Reorders tab flow, almost always wrong |
For a custom control that needs focus, tabindex="0". For a
container that should accept programmatic focus (a modal, error
summary) but not normal tab order, tabindex="-1".
Tab Order
The default tab order is DOM order. Visual order (with flex’s
order or grid item placement) doesn’t affect tab order. If they
diverge, keyboard users get a confusing experience.
The fix: write your HTML in the intended tab order. CSS can reorder visually, but you usually shouldn’t.
Skip Links
<a href="#main" class="skip-link">Skip to main content</a> .skip-link {
position: absolute;
left: -9999px;
}
.skip-link:focus {
left: 1rem;
top: 1rem;
} Hidden until focused with keyboard. Lets keyboard users jump past the header.
Modal Focus Management
When a modal opens:
- Move focus into it
- Trap focus inside while open (Tab cycles within)
- On close, return focus to the trigger
This is JS-heavy. Modern dialog patterns or <dialog> element
handle it for you.
Test It
The simplest accessibility test: unplug the mouse. Tab through the whole page.
- Can you reach every interactive element?
- Can you see where focus is?
- Does Enter / Space activate buttons / links?
- Can you escape from modals?
If any answer is no, fix it.
Up Next
Respecting users who don’t want motion.
Reduced Motion →