Skip to content
AgileLogics
6 min read

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 as aria-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>

Related developer tools