Keyboard accessibility (WCAG 2.1 — principle 2: Operable) is critical for users who cannot use a mouse: people with motor disabilities, power users, and anyone using a keyboard-driven interface or switch device. Here are the most common failures and how to fix them.
:focus { outline: none } to remove the browser's default blue ring. This makes it impossible to tell where keyboard focus is.
/* BAD — removes focus visibility entirely */
* { outline: none; }
:focus { outline: none; }
:focus-visible to show a custom focus ring only for keyboard navigation (not on mouse click).
/* Remove outline on mouse click, but show it for keyboard */
:focus { outline: none; }
:focus-visible {
outline: 3px solid #2563eb;
outline-offset: 3px;
border-radius: 4px;
}
Note: modals should trap focus within themselves while open — that is correct behavior. The failure is when focus cannot be escaped at all.
See how 321 websites scored →
View the 2026 Report// Trap focus inside modal
function trapFocus(modal) {
const focusable = modal.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const first = focusable[0];
const last = focusable[focusable.length - 1];
modal.addEventListener('keydown', (e) => {
if (e.key === 'Tab') {
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first.focus();
}
}
if (e.key === 'Escape') closeModal();
});
}
function openModal(modal) {
modal.removeAttribute('hidden');
trapFocus(modal);
modal.querySelector('[autofocus], button, [tabindex]').focus();
}
function closeModal() {
modal.setAttribute('hidden', '');
triggerButton.focus(); // return focus to trigger
}
<div> elements with click handlers, but has no keyboard support.
<!-- BAD: non-keyboard-accessible -->
<div class="tab" onclick="selectTab(1)">Overview</div>
<!-- GOOD: full keyboard and ARIA support -->
<div role="tablist" aria-label="Product tabs">
<button role="tab"
aria-selected="true"
aria-controls="panel-overview"
id="tab-overview">Overview</button>
<button role="tab"
aria-selected="false"
aria-controls="panel-features"
id="tab-features"
tabindex="-1">Features</button>
</div>
<div role="tabpanel" id="panel-overview" aria-labelledby="tab-overview">
...content...
</div>
// Arrow key navigation for tab widgets
tablist.addEventListener('keydown', (e) => {
const tabs = [...tablist.querySelectorAll('[role="tab"]')];
const idx = tabs.indexOf(document.activeElement);
if (e.key === 'ArrowRight') {
tabs[(idx + 1) % tabs.length].focus();
} else if (e.key === 'ArrowLeft') {
tabs[(idx - 1 + tabs.length) % tabs.length].focus();
}
});
tabindex values (e.g. tabindex="3") creates a global tab sequence that overrides document order and is confusing to keyboard users.
/* BAD — positive tabindex values break natural order */ <button tabindex="3">First visually but third in DOM</button> <button tabindex="1">Third visually but first in tab order</button>
tabindex="0" (adds to natural order) and tabindex="-1" (removes from tab order, focusable via JS). Fix the DOM order to match visual order.
<!-- Good: DOM order matches visual order, no positive tabindex --> <button>First</button> <button>Second</button> <button>Third</button> <!-- OK: programmatically focusable but not in tab order --> <div tabindex="-1" id="error-message">Error: invalid email</div>
Without a skip link, keyboard users must Tab through every navigation item on every page before reaching main content.
<!-- First element in <body> --> <a href="#main-content" class="skip-link">Skip to main content</a> <!-- Later in the page --> <main id="main-content">...</main>
/* Visually hidden until focused */
.skip-link {
position: absolute;
top: -100px;
left: 0;
background: #2563eb;
color: #fff;
padding: 8px 16px;
z-index: 1000;
font-weight: 700;
transition: top 0.1s;
}
.skip-link:focus {
top: 0;
}
Accessalyze detects missing skip links, focus issues, and interactive elements that fail keyboard navigation automatically.
Run a Free Accessibility Scan →See real website accessibility scores: Browse 244+ free accessibility audits →
Try it yourself
Enter your website URL to get a free accessibility score.