Difficulty: Intermediate
ARIA (Accessible Rich Internet Applications) is a set of HTML attributes defined by the W3C that provide additional semantic information to assistive technologies. When native HTML elements cannot fully convey the purpose, state, or relationship of UI components, ARIA fills the gap. The ARIA specification defines roles (what an element is), properties (characteristics of the element), and states (current conditions that may change).
The three most common ARIA labeling attributes are aria-label, aria-labelledby, and aria-describedby. The aria-label attribute provides an accessible name directly as a string - use it when there is no visible text label for an element. The aria-labelledby attribute references the id of another element whose text content should serve as the accessible name - it is stronger than aria-label and should be preferred when a visible label exists. The aria-describedby attribute links to an element that provides additional descriptive information, such as help text or format requirements. Unlike aria-labelledby, it does not replace the accessible name but supplements it.
ARIA roles override or supplement the implicit role of an HTML element. Landmark roles like role="banner", role="navigation", role="main", and role="contentinfo" define regions of a page. Widget roles like role="dialog", role="tablist", role="tab", and role="tabpanel" describe interactive components. Document structure roles like role="heading", role="list", and role="listitem" describe content organization. Always prefer native HTML elements (like <nav> instead of <div role="navigation">) - ARIA should only be used when HTML alone cannot express the semantics you need.
ARIA live regions allow assistive technologies to announce dynamic content changes without requiring the user to navigate to that part of the page. Setting aria-live="polite" on a container means changes will be announced when the user is idle. Using aria-live="assertive" interrupts the current announcement to deliver the update immediately - use this sparingly for urgent notifications like errors. The role="alert" implicitly sets aria-live="assertive", while role="status" implies aria-live="polite".
The first rule of ARIA is often stated as: do not use ARIA if you can use a native HTML element or attribute with the semantics and behavior you require. Native HTML elements come with built-in keyboard behavior, focus management, and implicit roles. Adding ARIA to a <div> does not give it keyboard interactivity or focus - you must implement all of that yourself. Misusing ARIA is worse than not using it at all because it can create confusing or contradictory information for assistive technology users.
<!-- aria-label: Inline accessible name (no visible label) -->
<button aria-label="Close dialog">
<svg viewBox="0 0 24 24"><path d="M6 6L18 18M6 18L18 6" /></svg>
</button>
<!-- aria-labelledby: References a visible label -->
<h2 id="billing-title">Billing Address</h2>
<form aria-labelledby="billing-title">
<label for="street">Street</label>
<input id="street" type="text" />
</form>
<!-- aria-describedby: Supplementary description -->
<label for="username">Username</label>
<input
id="username"
type="text"
aria-describedby="username-help username-error"
/>
<span id="username-help">3-20 characters, letters and numbers only.</span>
<span id="username-error" role="alert">Username is already taken.</span>
aria-label provides an accessible name when no visible text exists. aria-labelledby points to existing visible text to use as the name. aria-describedby adds supplemental info read after the name and role. You can reference multiple IDs with space-separated values.
<!-- Prefer native HTML5 elements over ARIA roles -->
<!-- Native (preferred) -->
<header>Site Header</header>
<nav>Navigation</nav>
<main>Main Content</main>
<aside>Sidebar</aside>
<footer>Site Footer</footer>
<!-- ARIA equivalent (use only when native elements are not possible) -->
<div role="banner">Site Header</div>
<div role="navigation">Navigation</div>
<div role="main">Main Content</div>
<div role="complementary">Sidebar</div>
<div role="contentinfo">Site Footer</div>
<!-- Multiple navs need labels to distinguish them -->
<nav aria-label="Main navigation">
<ul><li><a href="/">Home</a></li></ul>
</nav>
<nav aria-label="Footer navigation">
<ul><li><a href="/privacy">Privacy</a></li></ul>
</nav>
HTML5 semantic elements carry implicit ARIA roles: <header> is 'banner', <nav> is 'navigation', <main> is 'main'. When you have multiple elements of the same type (e.g., two <nav> elements), use aria-label to differentiate them for screen reader users.
<!-- Polite: Announces when user is idle -->
<div aria-live="polite" id="search-results">
<!-- Content updated via JavaScript -->
</div>
<!-- Assertive: Interrupts current announcement -->
<div aria-live="assertive" id="error-message">
<!-- Urgent errors appear here -->
</div>
<!-- role="alert" implies aria-live="assertive" -->
<div role="alert" id="form-error">
Please fix the errors before submitting.
</div>
<!-- role="status" implies aria-live="polite" -->
<div role="status" id="save-status">
All changes saved.
</div>
<!-- JavaScript to update live region -->
<script>
function updateResults(count) {
document.getElementById('search-results').textContent =
`Found ${count} results`;
// Screen reader announces: "Found 15 results"
}
</script>
Live regions tell screen readers to watch for content changes and announce them. Use 'polite' for non-urgent updates (search results, save status) and 'assertive' for urgent ones (error messages). The element must exist in the DOM before content is added for the announcement to work.
<!-- Accessible tab interface using ARIA roles -->
<div role="tablist" aria-label="Account settings">
<button
role="tab"
id="tab-profile"
aria-selected="true"
aria-controls="panel-profile"
>
Profile
</button>
<button
role="tab"
id="tab-security"
aria-selected="false"
aria-controls="panel-security"
tabindex="-1"
>
Security
</button>
<button
role="tab"
id="tab-billing"
aria-selected="false"
aria-controls="panel-billing"
tabindex="-1"
>
Billing
</button>
</div>
<div
role="tabpanel"
id="panel-profile"
aria-labelledby="tab-profile"
>
<h3>Profile Settings</h3>
<p>Edit your profile information here.</p>
</div>
<div
role="tabpanel"
id="panel-security"
aria-labelledby="tab-security"
hidden
>
<h3>Security Settings</h3>
<p>Manage your password and two-factor authentication.</p>
</div>
Tab interfaces require specific ARIA roles (tablist, tab, tabpanel) and states (aria-selected, aria-controls). Only the active tab should be in the tab order (other tabs get tabindex='-1'). Arrow keys navigate between tabs, not Tab key. Each panel is labeled by its corresponding tab via aria-labelledby.
aria-label, aria-labelledby, aria-describedby, ARIA Roles, Live Regions