ARIA & Advanced Semantics

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:
aria-labelledbyaria-label<label>inner text
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-labelis good for icon-only buttons. Maybe not so much on long or dynamic content.aria-labelledbyreferences 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 ❤️



