Skip to main content

Command Palette

Search for a command to run...

ARIA & Advanced Semantics

Published
3 min read
ARIA & Advanced Semantics
T
Hello World! I'm Tito. I write about designing and building real-world software — frontend, backend, infrastructure, system design, and data.

ARIA = Accessible Rich Internet Applications

ARIA exists because HTML wasn’t designed for modern UIs. We had to build custom widgets (modals, tabs, comboboxes, etc.) and screen readers need to understand those widgets.

You do not need ARIA for buttons, links, forms, inputs, labels etc. HTML already solves this. You do need it for custom widgets like tabs, accordions, modals and other custom widgets. Even then, instead of building yours from scratch, lean into libraries and/or frameworks that have already done the work for you e.g. MUI, Hero UI.

ARIA is a semantic patch layer that modifies the accessibility tree only, not the DOM behavior.

Example:

<div role="button">Save</div>

In the DOM, this is a generic DIV. On the accessibility tree, it is a button, so the screen reader says button. However, it still doesn’t behave like a button. You have to implement the behavior yourself.

ARIA Categories

ARIA also has 3 types:

Roles: what an element is. Includes the following;

role="button"
role="dialog"
role="tab"
role="alert"

A div with a button role is still worse than <button> even if it has keyboard handling, states and focus styles

States: describes an element’s current condition

aria-expanded="true"
aria-checked="false"
aria-disabled="true"

Properties: describe extra relationships or information

aria-label
aria-describedby
aria-controls
aria-labelledby

Every interactive element must have an accessible name or else they are invisible to screen readers.

According to priority, screen readers use:

  1. aria-labelledby

  2. aria-label

  3. <label>

  4. inner text

  5. alt

You can see how ARIA overrides everything. This matters because let’s say we have a native button like in the snippet below:

<button aria-label="Delete">
    Delete user
</button>

Screen reader says: “Delete”, completely ignoring the inner text.


Some important ARIA types include:

  • aria-hidden: This removes elements from the accessibility tree. It’s great for decorative icons like icon buttons, and duplicate visuals.

    Look out for focusable elements. They should not be hidden.

      <button aria-label="Delete item">
      <TrashIcon aria-hidden="true" />
      </button>
    
  • aria-expanded: this communicates over a boolean open/closed state. The screen reader reads “collapsed” / “expanded”. It’s great for accordions and dropdowns

      <button
        aria-expanded={open}
        aria-controls="section1"
      >
      Details
      </button>
    
      <div id="section1" hidden={!open}>
    
  • aria-controls: this links controller to controlled element. It adds relationship context. It can be found on the trigger of a dropdown menu

      <button aria-controls="menu" />
      <ul id="menu" />
    
  • aria-describedby: this, too, provides extra context.

      <input aria-describedby="hint" />
      <p id="hint">Must be 8 characters</p>
    

    The screen reader reads “Password edit, must be 8 characters”. It assists with hints, errors and help text.

    A form error example:

      <input
        aria-invalid={!!error}
        aria-describedby="email-error"
      />
    
      {error && (
          <p id="email-error">{error}</p>
      )}
    
  • aria-live: for dynamic async updates. Useful for toast notifications like form success, and async results. Modes include polite (wait) and assertive (interrupt)

      <div aria-live="polite">{message}</div>
    
  • aria-label & aria-labelledby: describes the element, providing information on it or it’s relationships

      <button aria-label="Close" />
    

    aria-label is good for icon-only buttons. Maybe not so much on long or dynamic content.

    aria-labelledby references real text. This is preferred. It is reusable, translatable, and stays synced.

      <h2 id="title">BillingSettings</h2>
      <section aria-labelledby="title">
    

This article is part of a larger accessibility series:

  • Part 1 : How accessibility actually works - from the accessibility tree to semantic HTML and keyboard interaction

  • Part 2 (this article): ARIA & advanced semantics

  • Part 3: How to Test Accessibility & Some Common Interview Questions

You can read this start to finish, or jump to the sections most relevant to you.

If this was helpful, feel free to leave a like ❤️

More from this blog

Engineering with Tito

29 posts

Hello World! I am Bolatito. I am a full stack software engineer. I am obsessed with building great products and currently, I am specializing in building products for the web.