Semantic HTML is the cheapest accessibility you will ever ship
The cheapest accessibility you will ever ship is choosing the right HTML element.
Before a single ARIA attribute, before any JavaScript, the browser gives native
elements a pile of behaviour for free: a <button> is focusable, fires on Enter
and Space, announces itself as a button to a screen reader, and participates in
forms. A <div> styled to look like a button has none of that, and getting it
back means reimplementing the browser by hand — and getting every detail right.
The first rule of ARIA is literally “don’t use ARIA if a native element already
does the job.” Semantic HTML is that native element.
The div button re-implements the browser, badly
Here is what a clickable <div> actually costs once you make it accessible. Every
line is behaviour a real button already had:
// a div pretending to be a button — and still not quite one
<div
role="button" // tell AT it's a button
tabIndex={0} // make it focusable
onClick={handleClick}
onKeyDown={(e) => { // re-implement Enter AND Space
if (e.key === "Enter" || e.key === " ") { e.preventDefault(); handleClick(); }
}}
>Save</div>
Compared to the thing it is imitating:
<button onClick={handleClick}>Save</button> // all of the above, for free
The <button> is fewer characters, cannot forget the Space key, and stays correct
when the platform changes its conventions.
Structure is semantic too
Semantics is not only interactive elements. Using <nav>, <main>, <header>,
<h1>–<h6> in order, <ul> for lists, and <table> for tabular data gives
screen-reader users a map — they can jump between landmarks and headings instead
of reading linearly. A page built from <div>s is one undifferentiated wall to
assistive tech:
<header>…</header>
<nav aria-label="Primary">…</nav>
<main>
<h1>Page title</h1>
<section><h2>Section</h2> …</section> <!-- headings a reader can jump between -->
</main>
ARIA is the patch, not the plan
None of this means ARIA is bad — it is essential for the widgets HTML has no
element for (a combobox, a tab set, a tree). But ARIA adds semantics on top of
HTML; it does not add behaviour. role="button" tells a screen reader “this is a
button” and does nothing to make Enter work — you still write that yourself. So the
order is: reach for the semantic element first, and use ARIA only to fill the gaps
it genuinely cannot cover, on custom widgets, with the keyboard behaviour
hand-built to match. Every native element you use is behaviour you did not have to
write, test, and maintain. The loading-button-atom and accessible-combobox
exercises sit on opposite ends of this: the button should just be a <button>,
while the combobox is the real case where ARIA and hand-built keyboard support are
unavoidable.