Poor Focus Management
Your keyboard users are trapped, lost, or invisible. It's like a revolving door that won't let you out — disorienting and infuriating.
What is this?
Picture a revolving door that spins you around endlessly and never lets you out. Or a room with invisible doors — you know there are exits, but you cannot see them. That is what poor focus management feels like for keyboard users. Focus management is how your website handles the highlighted outline that moves from element to element when someone presses Tab. When it is broken, users get trapped in modals they cannot escape, cannot see which element is selected, or tab through things in a nonsensical order. It makes your site genuinely unusable for anyone not using a mouse.
Why it matters
- For your visitors: Keyboard users include people with motor disabilities, power users who prefer the keyboard, and anyone with a broken trackpad. When focus is invisible, they cannot see where they are. When focus is trapped in a modal with no escape, they are literally stuck. When tab order is jumbled, they are jumping around the page like a scratched DVD. All of these make your site hostile to a significant portion of your audience.
- For your business: Focus issues disproportionately affect the exact users who are most likely to complain, leave negative reviews, or file accessibility lawsuits. In the US, web accessibility lawsuits have increased every year. Beyond legal risk, keyboard-inaccessible forms mean lost sign-ups, and focus traps mean lost customers who physically cannot complete your flow.
- The standard: WCAG 2.4.7 requires a visible focus indicator on all interactive elements. WCAG 2.4.3 requires a logical focus order. WCAG 2.1.2 prohibits keyboard traps — if keyboard focus can enter a component, it must be able to leave. These are Level A requirements — the minimum bar, not a nice-to-have.
<!-- Visible focus ring on interactive elements -->
<style>
:focus-visible {
outline: 2px solid #f59e0b;
outline-offset: 2px;
}
</style>
<!-- Modal that can be closed with Escape -->
<dialog id="my-modal">
<h2>Settings</h2>
<p>Your settings content...</p>
<button onclick="this.closest('dialog').close()">
Close
</button>
</dialog>/* The classic crime — removing focus outlines */
*:focus {
outline: none;
}<!-- Custom modal with no keyboard escape -->
<div class="modal" style="position: fixed; inset: 0;">
<div class="modal-content">
<h2>Settings</h2>
<!-- No close button, no Escape handler -->
<!-- Keyboard users are trapped forever -->
</div>
</div>How to fix it
React / Next.js
Step 1: Never remove focus outlines. Instead, use :focus-visible to show them only for keyboard users (not mouse clicks).
// src/app/globals.css
@layer base {
/* Show focus ring only for keyboard navigation */
:focus-visible {
outline: 2px solid oklch(0.72 0.18 75);
outline-offset: 2px;
border-radius: 2px;
}
/* Remove the default outline only for mouse users */
:focus:not(:focus-visible) {
outline: none;
}
}Step 2: Use the native <dialog> element for modals. It handles focus trapping and Escape to close for free — no library needed.
// Native dialog handles focus trapping + Escape key automatically
const dialogRef = useRef<HTMLDialogElement>(null);
// To open: dialogRef.current?.showModal()
// To close: dialogRef.current?.close()
<dialog ref={dialogRef} onClose={onClose} className="bg-white p-6 shadow-xl backdrop:bg-black/50">
{children}
<button onClick={onClose}>Close</button>
</dialog>;Step 3: Manage focus when content changes. When loading new content dynamically, move focus to it.
// After dynamically loading content, focus the heading
const headingRef = useRef<HTMLHeadingElement>(null);
useEffect(() => {
headingRef.current?.focus();
}, [activeTab]);
return (
<h2 ref={headingRef} tabIndex={-1}>
Tab Content
</h2>
);Plain HTML
<!-- 1. Never do this -->
<!-- *:focus { outline: none } -->
<!-- 2. Use focus-visible for smart focus rings -->
<style>
:focus-visible {
outline: 2px solid #f59e0b;
outline-offset: 2px;
}
</style>
<!-- 3. Use native <dialog> for modals -->
<button onclick="document.getElementById('settings').showModal()">Open Settings</button>
<dialog id="settings">
<h2>Settings</h2>
<p>Content here...</p>
<button onclick="this.closest('dialog').close()">Close</button>
</dialog>
<!-- 4. Use logical source order — avoid CSS order or tabindex hacks -->
<!-- Tab order follows DOM order. Rearrange your HTML, not your tabindex. -->The number one rule: never use tabindex values greater than 0. They override the natural tab order and create chaos. Use tabindex="0" to add elements to the natural flow and tabindex="-1" to make elements focusable via JavaScript only.
A cat always knows its escape routes. Your keyboard users deserve the same certainty. Trapping them is not just bad UX — it is a failure of basic hospitality.
How the cat scores this
The scanner checks three things. First, it looks for the infamous outline: none or outline: 0 on :focus (without a replacement style) — if you have removed focus indicators without adding :focus-visible replacements, that is an immediate flag. Second, it checks modals and dialogs for keyboard escape routes (Escape key handler or close button). Third, it examines tabindex attributes and flags any values greater than 0, which indicate manual tab order manipulation. Each issue is scored separately, and they compound.
Further reading
- WCAG 2.4.7: Focus Visible — the standard requiring visible focus indicators
- MDN: <dialog> element — the native HTML solution for accessible modals
- What Input? — a library to detect keyboard vs. mouse interaction for smart focus styles
Small Touch Targets
Your clickable elements are too small. Trying to tap them is like pressing elevator buttons wearing oven mitts — frustrating and full of misfires.
Missing Footer
Your site has no footer with contact or legal info. It's a business card with no phone number — visitors can't trust what they can't verify.