Accessibility foundations in markup

Native elements before ARIA, accessible names, label and alt patterns, landmarks, live regions and focus order.

Native elements before ARIA

Assistive technology does not see your class names or your CSS. It sees a tree of roles, names and states, built from the elements you chose and the attributes you set. Choosing the right element is therefore the cheapest accessibility work there is.

<!-- no role, no keyboard support, announced as plain text -->
<div class="btn" onclick="saveDraft()">Save draft</div>

<!-- role, name, focus and keyboard activation come for free -->
<button type="button" class="btn">Save draft</button>

A real <button> is focusable, fires on Enter and Space, exposes its disabled state, and participates in forms correctly. Rebuilding that on a div takes a dozen attributes, a keydown handler, and usually still misses something.

⚠️
The first rule of ARIA is: do not use ARIA when a native element already fits. A wrong role is worse than no role, because it tells the screen reader to expect behaviour the element does not have.

Every control needs a name

The accessible name is what a screen reader announces when focus lands on a control. If nothing supplies one, the user hears "button" or "link" with no indication of what it does — and voice control cannot target it either.

ElementWhere its name comes from
<button>Its text content, or aria-label for icon-only controls
<a>Its text content — link text must describe the destination
<img>alt, or empty alt="" when decorative
<input>A <label for>, a wrapping label, or aria-label
<iframe>The title attribute
<fieldset>Its <legend>, which labels the whole group
Icon inside a controlHide the graphic, name the parent control
<label for="search">Search courses</label>
<input id="search" type="search" name="q">

<button type="button" aria-label="Close dialog">
  <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
    <path d="M6 6l12 12M18 6L6 18"/>
  </svg>
</button>

Landmarks, live regions and focus order

Landmarks are the coarsest navigation aid on a page: with them, a screen reader user can jump straight to the main content or list every navigation block instead of tabbing through a menu of twenty links.

  • Exactly one <main>; give each repeated <nav> a distinct aria-label.
  • Keep DOM order and visual order identical — CSS reordering breaks tab sequence for everyone using a keyboard.
  • Never use positive tabindex; it hijacks the natural order. tabindex="0" belongs only on custom widgets.
  • Offer a skip link as the first focusable element so repeated navigation can be bypassed.
  • Live regions must exist in the DOM before the update arrives; they announce changes, not existing text.
<a class="skip-link" href="#content">Skip to content</a>

<main id="content">
  <h1>Your courses</h1>
</main>

<p role="status" aria-live="polite">Cart updated: 3 items</p>

Use role="status" for polite announcements such as saved confirmations, and reserve aria-live="assertive" for errors that genuinely must interrupt. Then test with a real screen reader — VoiceOver on macOS, NVDA on Windows — and try to complete one task without looking at the screen.

FAQ

Should I add tabindex="0" to make things focusable?
Only for custom widgets you have built from scratch. If something needs focus and activation, it almost always wants to be a <button> or <a> instead, which are focusable already.
aria-label or visually hidden text?
For a short string, aria-label is fine and simple. Visually hidden text is translatable by page-level tools, supports markup, and is easier to keep in sync — prefer it for anything longer than a few words.

Interactive elements without JavaScript The document head: metadata, favicons and social cards

Last refreshed 2026-09-18.