The invisible thread that guides keyboard users
Not everyone uses a mouse. Some people can’t. Some choose not to. Your site needs to work for all of them.
The first time you try navigating a website with just your keyboard, it’s eye-opening. You press Tab and… where did the focus go? Is that barely-visible dotted line supposed to help? And why did I just land on some random element in the middle of the page?
For keyboard users, focus is like a flashlight in a dark room. If the flashlight is dim, keeps disappearing, or jumps around randomly, navigating becomes impossible.
Focus States: Make Them Visible
Yes, the default focus outline can be ugly. No, you shouldn’t remove it. You should make it better.
/* Bad: Removing focus entirely */
*:focus {
outline: none;
}
/* Good: Custom focus styles that don't suck */
:focus-visible {
outline: 3px solid #000;
outline-offset: 2px;
}
/* Even better: Different styles for different inputs */
button:focus-visible {
outline: 3px solid #0066cc;
outline-offset: 2px;
}
input:focus-visible {
outline: 2px solid #0066cc;
box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.25);
}
Focus styles should:
- Be clearly visible
- Have sufficient contrast
- Work on any background
- Not be color alone (add thickness, offset, or shadow)
Skip Links: The Secret Shortcut
Imagine tabbing through 47 navigation links just to read the main content. Every. Single. Page.
It’s like being forced to walk through an entire shopping mall just to get to the one store you want to visit. Sure, you’ll get there eventually, but why should you have to?
Skip links fix this. They’re invisible until needed. A lifesaver when found.
<!-- Add this right after <body> -->
<a href="#main" class="skip-link">Skip to main content</a>
<!-- Your navigation here -->
<nav>...</nav>
<!-- Mark your main content -->
<main id="main">
<h1>The actual content people came for</h1>
</main>
/* Hide it visually but keep it keyboard accessible */
.skip-link {
position: absolute;
top: -40px;
left: 0;
background: #000;
color: #fff;
padding: 8px;
text-decoration: none;
z-index: 100;
}
.skip-link:focus {
top: 0;
}
Tab Order: Keep It Logical
Tab order should follow the visual flow of your page. Usually, you don’t need to mess with it.
<!-- Bad: Messing with tabindex unnecessarily -->
<button tabindex="3">First button</button>
<button tabindex="1">Second button</button>
<button tabindex="2">Third button</button>
<!-- Good: Let the DOM order handle it -->
<button>First button</button>
<button>Second button</button>
<button>Third button</button>
<!-- Only use tabindex when necessary -->
<div tabindex="0" role="button">
Only if you REALLY can't use a real button
</div>
<div tabindex="-1" id="error-message">
Remove from tab order but keep focusable via JavaScript
</div>
Rules for tabindex:
- tabindex=”0″: Adds to natural tab order
- tabindex=”-1″: Focusable by JavaScript only
- tabindex=”1″ or higher: Please don’t. Seriously.
Modal Focus Management
When a modal opens, don’t leave users lost in limbo. Here’s what should happen:
- Save where focus was
- Move focus to the modal
- Trap focus within the modal
- Return focus when closed
Think of it like a phone call. When someone calls you, your attention shifts completely to the conversation. When the call ends, you go back to whatever you were doing before. Modal focus should work the same way.
let previousFocus;
function openModal() {
previousFocus = document.activeElement;
const modal = document.getElementById('modal');
modal.showModal();
// Focus first interactive element or the modal itself
modal.focus();
}
function closeModal() {
const modal = document.getElementById('modal');
modal.close();
// Return focus to trigger element
previousFocus.focus();
}
<!-- An accessible modal using <dialog> -->
<button onclick="openModal()">Open settings</button>
<dialog id="modal" aria-labelledby="modal-title">
<h2 id="modal-title">Settings</h2>
<form method="dialog">
<!-- Modal content here -->
<button type="submit">Save changes</button>
<button type="button" onclick="closeModal()">Cancel</button>
</form>
</dialog>
→ Focus patterns that work:
- Erik Kroes: The Universal Focus State – One style to rule them all
- W3C: Using the dialog element – Modal patterns done right