Focus, Keyboard, and Live Regions
Prerequisites: ARIA roles, states, and properties
tabindex
| Value | Effect |
|---|---|
tabindex="0" |
Makes an element focusable, in normal document order |
tabindex="-1" |
Focusable by script (element.focus()), but skipped by Tab |
positive (tabindex="3") |
Jumps the queue — don't use it, it breaks the natural order |
Tab order follows the source order, so write the HTML in the order people should move through it — don't rearrange it visually with CSS in a way that confuses keyboard users.
<h1 id="results-heading" tabindex="-1">12 results</h1>
After loading new content with JavaScript, move focus to its heading with
document.getElementById("results-heading").focus(), so keyboard and
screen-reader users know where they are.
inert
The inert attribute makes a whole subtree unfocusable, unclickable,
and hidden from assistive technology — for example, the page behind a
custom modal or an off-screen menu:
<main inert>
<h1>Page content</h1>
<a href="/more">This link can't be reached while inert</a>
</main>
<div class="menu-panel">
<a href="/">Home</a>
</div>
<script>
const link = document.querySelector("main a");
link.focus();
console.log("focused:", document.activeElement === link);
document.querySelector("main").inert = false;
link.focus();
console.log("focused after removing inert:", document.activeElement === link);
</script>
focused: false
focused after removing inert: true
inert is supported in all current major browsers (Chrome and Edge
102, Firefox 112, Safari 15.5).
Live regions
When content changes without a page load — a "Saved" message, a cart count, search results — screen readers don't notice unless you tell them. A live region announces changes to its contents:
<p role="status">3 items in your cart</p>
<div role="alert">Payment failed: your card was declined.</div>
<div aria-live="polite" aria-atomic="true" id="search-count"></div>
role="status"(same asaria-live="polite") — announced when the screen reader is idle. Use it for most updates.role="alert"(aria-live="assertive") — interrupts. Only for urgent, important messages.aria-atomic="true"— read the whole region, not just the part that changed.
The region must already exist in the page before its content changes; adding a new element that already contains text is often not announced. Put an empty region in the HTML and fill it later.
Visible focus
Browsers draw a focus ring around focused elements. Never remove it
without replacing it (outline: none with nothing else is a common and
serious accessibility bug). CSS's :focus-visible lets you style the
ring for keyboard users only.
Practice
Add a polite live region for a form's "Draft saved at 10:42" message, so it can be updated by script.
Show solution
<p id="save-status" role="status"></p>
<script>
document.getElementById("save-status").textContent = "Draft saved at 10:42";
</script>