JavaScript by Example

Focus and ARIA

Move focus after dynamic DOM changes and use ARIA labels or descriptions only when native HTML does not say enough.

Focus tells keyboard and screen reader users where they are. ARIA adds names, descriptions, and state when native HTML is not enough.

When the focused element is removed from the DOM, focus often falls back to <body>. The user loses their place. After deleting a list item, move focus to the next useful control.

function deleteItem(deleteButton) {
  const item = deleteButton.closest("li");
  const nextItem = item.nextElementSibling || item.previousElementSibling;
  const nextDeleteButton = nextItem?.querySelector("button");
  const addButton = document.querySelector("#add-task");
 
  item.remove();
  (nextDeleteButton || addButton).focus();
}
<button id="add-task" type="button">Add task</button>
<ul>
  <li>Task one <button onclick="deleteItem(this)">Delete</button></li>
  <li>Task two <button onclick="deleteItem(this)">Delete</button></li>
</ul>

Icon-only controls have no visible text for screen readers to announce. aria-label gives a short name. aria-describedby points to separate help or error text.

<button type="button" aria-label="Close dialog">
  <svg aria-hidden="true" focusable="false"><!-- X icon SVG --></svg>
</button>
 
<label for="username">Username</label>
<input id="username" type="text" aria-describedby="username-hint" />
<p id="username-hint">3-20 characters.</p>
<p id="username-error" aria-live="polite"></p>
function showError(message) {
  usernameError.textContent = message;
  username.setAttribute("aria-describedby", "username-hint username-error");
  username.setAttribute("aria-invalid", "true");
}

In production

Preserve existing descriptions when adding an error. Otherwise the field can lose its normal hint as soon as validation fails. Incorrect ARIA can make the accessibility tree worse than plain HTML, so start with native elements.

Enjoyed this? Get more software craft delivered to your inbox.