Native-First & ARIA
ARIA gives names, roles, and state to things the browser cannot infer on its own. But native HTML elements already carry their role and keyboard behavior built in, so the first rule of ARIA is: reach for a native element first, and use ARIA only for what HTML cannot express.
What ARIA is
ARIA (Accessible Rich Internet Applications) is a set of HTML attributes that modify what the browser communicates to assistive technology through the accessibility tree. There are three kinds: roles (role="dialog"), properties (aria-label="Close"), and states (aria-expanded="true"). ARIA does not add keyboard behavior, visual styles, or DOM structure. It only changes what AT hears. If you add role="button" to a <div>, the AT hears "button" but Tab still skips the element and Enter still does nothing. You have added the label without the substance.
The W3C First Rule of ARIA is: if you can use a native HTML element or attribute with the required semantics and behavior already built in, do so. A <button> is focusable with Tab, activatable with Enter and Space, and communicates role="button" automatically. A <div role="button"> needs tabindex="0", a keydown handler for Enter and Space, an onclick handler, and every ARIA state wired up manually. Native HTML buys you all of that for free.
Why it matters
WCAG 4.1.2 (Name, Role, Value, Level A) requires that every user interface component exposes its name, its role, and any state or property that changes over time. Native HTML satisfies this automatically for interactive elements. ARIA lets you satisfy it for custom widget patterns that have no native equivalent: a tab panel, an autocomplete listbox, a tree view, a custom toggle button that is not a <button> for layout reasons.
Getting ARIA wrong is worse than using no ARIA at all. Incorrect roles confuse AT users who navigate by landmark or widget type. Stale state attributes (an aria-expanded that never updates from false to true) give false information. An aria-hidden="true" on a focused element removes it from the AT view while keyboard users can still reach it, creating a ghost interaction. The accessibility tree tells you what AT actually hears and is the ground truth for auditing ARIA.
How to apply each ARIA category
**Roles** fill widget gaps. Use role="tab" + role="tabpanel" for tab interfaces, role="dialog" for a custom modal (paired with aria-modal="true" and focus trapping), role="alert" for urgent messages, role="tooltip" for hover descriptions. Do not override native roles: <button role="link"> is contradictory.
**Properties** name and describe. Use aria-label for controls with no visible text label (an icon-only close button: aria-label="Close"). Prefer aria-labelledby when a visible heading already names the region, because the label stays in sync with visible text automatically. Use aria-describedby for supplemental hint text (a password requirement note tied to a password input). Properties do not change during interaction.
**States** reflect live conditions. Add aria-expanded="false" to an accordion trigger button and flip it to true with JavaScript when the panel opens. Add aria-pressed to a toggle button. Add aria-checked to a custom checkbox. If you do not update the state with JavaScript on every interaction, AT users see a permanently static state that does not match reality.
For dynamic content announcements, use role="status" (equivalent to aria-live="polite") for non-urgent updates (form saved, item added to cart). Use role="alert" (equivalent to aria-live="assertive") only for errors or urgent messages. Assertive live regions interrupt whatever AT is currently reading, so overusing them is disruptive.
Try it
See what a native <button> provides for free versus what a <div role="button"> still needs, and watch the three ARIA attribute categories in action.
Native element vs ARIA role alone
<button>Click me</button>
- +In tab orderfree
- +Enter / Space activatesfree
- +Focus ring (browser)free
- +role = buttonfree
- +Disabled statefree
Zero extra attributes needed. Works with keyboard and AT out of the box.
<div role="button">Click me</div>
- !In tab orderadd manually
- !Enter / Space activatesadd manually
- !Focus ring (browser)add manually
- +role = buttonfrom role
- !Disabled stateadd manually
Needs tabindex="0", keydown handler for Enter + Space, and manual state updates.
The three ARIA attribute categories
Roles
Widget patterns HTML has no element for
Properties
Stable names and descriptions
States
Change with user interaction
Live regions: polite vs assertive
Use when
Non-urgent updates
Behavior
Waits until AT finishes the current sentence before announcing
AT announces:
"3 results found"
After a search completes
Use when
Errors and urgent messages only
Behavior
Interrupts whatever AT is currently reading immediately
AT announces:
"Error: email is required"
After a failed form submit
Caution: assertive live regions interrupt AT mid-sentence. Reserve role="alert" for genuine errors and time-sensitive messages only.
Check yourself
Pick an answer to lock it in, then read why. Getting one wrong is part of how it sticks.
Remember this
- Use native HTML elements first.
<button>gives keyboard support, focus, androle="button"for free. <div role="button">requirestabindex="0", Enter/Spacekeydownhandlers, and every state update: all manual.- Prefer
aria-labelledbyoveraria-labelwhen a visible text element already names the widget. - Never put
aria-hidden="true"on a focusable element: AT hides it but keyboard users can still reach it. role="status"= polite live region;role="alert"= assertive (interrupts AT immediately, use only for errors).
Done with this concept?
Mark it complete to track your progress. No login needed.