← void

aria

the spec says: "no aria is better than bad aria." most code ignores this.

Rule 1 of ARIA Use: If you can use a native HTML element with the semantics and behavior you require already built in, do so instead of repurposing an element and adding ARIA.

This is the first rule. It appears first because it is the most important and the most ignored.

When you write <button>, you get: focusability, keyboard activation (Enter and Space), an implicit role of button, announced type, click events from keyboard and pointer, and browser default styling. You get all of this for free. For zero bytes of JavaScript.

When you write <div onclick="...">, you get: a click event. Nothing else. Every capability you removed must be added back manually, in JavaScript, that you now have to maintain.

common mistakes

instead of
<div onclick="toggle()">Menu</div>
use
<button type="button">Menu</button>
0 keyboard nav. 0 focus. 0 screen reader role. click only.
button has role=button, focusable, activated by Enter and Space, announced to screen readers. it is all there. for free.
instead of
<span role="button" tabindex="0" aria-pressed="false" onclick="...">
use
<button type="button">
34 extra bytes of attributes to approximate what button does natively
button already has pressed state via CSS :active. no aria-pressed needed. the span with role=button is a button that forgot who it was.
instead of
<a href="#" onclick="navigate()">Go somewhere</a>
use
<a href="/somewhere">Go somewhere</a>
href="#" scrolls to top on click. onclick fires before navigation. fallback is broken.
anchors are for navigation. href is the destination. use it.
instead of
<div role="heading" aria-level="2">Section title</div>
use
<h2>Section title</h2>
2 ARIA attributes to replicate what h2 already is
h1–h6 have implicit heading roles and levels. the heading hierarchy is document structure. use headings.
instead of
<div role="list"><div role="listitem">...</div></div>
use
<ul><li>...</li></ul>
ARIA roles bolted to generic elements. no native list behavior.
ul/ol/li have implicit list and listitem roles. browsers expose them to accessibility trees without any attributes.
instead of
<input aria-required="true" aria-invalid="true">
use
<input required>
aria-required duplicates the required attribute. aria-invalid is set manually after validation.
required adds HTML validation, ARIA semantics, and browser UI. :invalid/:valid CSS pseudoclasses fire automatically. constraint validation API ships with every browser.
instead of
<div role="img" aria-label="Company logo">...</div>
use
<img src="logo.svg" alt="Company logo">
a div pretending to be an image. no native width/height. no lazy loading. no srcset.
img is an image. it has alt. it participates in layout metrics for CLS. it supports loading=lazy. use it.
instead of
<div role="dialog" aria-modal="true" aria-labelledby="...">
use
<dialog>
focus trap: not implemented. Escape key: not implemented. backdrop: add JS. top-layer: never.
dialog has showModal(). focus trap is automatic. Escape fires cancel event. backdrop is ::backdrop. top-layer means it renders above everything. the div role=dialog has none of this.

when aria is actually needed

ARIA exists for patterns HTML does not cover. Custom widgets with no native equivalent. Dynamic state that has no HTML expression. When you have genuinely exhausted the HTML spec, then reach for ARIA.

custom range slider
input type=range exists but may not match your visual design. if you build a custom one, use role=slider with aria-valuemin, aria-valuemax, aria-valuenow.
tab panel interface
no native tab element. role=tablist, role=tab, role=tabpanel with aria-selected and aria-controls is the correct pattern here.
combobox with custom options
datalist covers simple autocomplete. for complex filtered searches with custom rendering, role=combobox with aria-expanded and aria-activedescendant is appropriate.
live regions
aria-live=polite announces dynamic content changes. status updates, form feedback, loading states. the only way to communicate async changes to screen readers.
tree widget
no native collapsible tree element. role=tree with role=treeitem, aria-expanded, aria-level is the spec. complex. worth it if you need it.

the four rules

The W3C ARIA specification ships with four authoring practices.

Rule four: no ARIA is better than bad ARIA. A div with no ARIA is invisible to screen readers but does not break anything. A div with incorrect ARIA announces false information. Silence is better than lies.

estimated transfer: < 5kb — html + inline styles — js: 0 bytes