Semantic HTML is accessibility
The first rule of ARIA is: don't. Native elements are doing more for you than you think.
Here's a small secret of accessibility: most of the spec is satisfied not by adding things, but by not removing things. The browser does an enormous amount of work for you when you use the right HTML element. Use the wrong one — say, a <div> styled to look like a button — and you have to recreate every one of those features by hand. Most people don't.
Semantic HTML is HTML where each element matches its meaning: <button> for a button, <nav> for navigation, <h2> for a section heading, <label> for a form label.
The first rule of ARIA
There's a famous line in the ARIA spec: "No ARIA is better than bad ARIA." The W3C even gives this its own catchy name: the first rule of ARIA use — if you can use a native HTML element with the role and behavior you want, do that, and don't redefine it with ARIA.
Why? Because ARIA only describes things to assistive tech. It doesn't add behavior. role="button" on a <div> makes a screen reader call it a button. It does not make Enter activate it. It does not put it in the tab order. It does not make Space trigger a click. You have to wire all of that yourself with JavaScript, and most people will do it imperfectly.
A real <button> does all of that automatically. So does <a href>. So does <input>.
ARIA is a fire extinguisher: useful when you actually need it, completely unnecessary if you didn't start the fire.
What you get for free with native elements
Compare a real button to a fake one:
<button type="button">Open menu</button>
<div class="btn" onclick="open()">Open menu</div>
The real <button> ships with all of these, no extra code:
- It's announced as "button, Open menu" by every screen reader.
- It's in the tab order — Tab reaches it.
- Enter and Space activate it.
- It has a focus ring you can see.
- It supports the
disabledattribute, which removes it from the tab order and announces "dimmed" or similar. - It submits forms (when
typeis omitted, in a form) or doesn't (type="button"). - It works with form validation, autofocus, and form-associated APIs.
The fake <div> ships with one thing: a click handler. To match the real button you'd need to add tabindex="0", role="button", an Enter/Space keydown listener, a focus style, an aria-disabled story, and a workaround for forms. You will get one of these wrong.
<div>. Which of these will you not get back, even if you add role="button" and tabindex="0"?Landmarks and structure
Screen-reader users navigate by landmarks — large named regions of the page. Modern HTML gives you them as elements:
<header>— the banner at the top of the page (when it's a top-level header).<nav>— a chunk of navigation links.<main>— the primary content. Exactly one per page.<aside>— sidebars and complementary content.<footer>— the footer (when at the top level).<section>and<article>— generic regions; give them anaria-labelor contained heading to make them landmarks.
A user can pull up a list of all landmarks and jump straight to "main" — if you used the element. If your page is a stack of <div class="header">, <div class="content">, <div class="footer">, that user has nothing to jump to and has to wade through the navigation every time.
Headings tell the story
Headings (<h1> through <h6>) are not for making text bigger — they're an outline of the page. Screen readers offer a "list of headings" shortcut that's the equivalent of a table of contents.
The rule of thumb: one <h1> per page, and don't skip levels (don't jump from <h2> to <h4>). You can style headings to look any size you like; just keep the ranks correct.
Choosing a heading because it "looks right at this size" is the most common reason heading outlines break. Pick the rank for meaning, then style with CSS.
Forms with real labels
Every form input needs a label. The bulletproof pattern:
<label for="email">Email address</label> <input id="email" name="email" type="email" required>
That for / id pairing does three things at once: it tells the screen reader the input's name, it makes clicking the label focus the input (handy on small phones), and it lets browsers do autofill correctly.
Things that are not a real label:
- Placeholder text. Disappears when typing, often low contrast, often missed by autofill.
- A
<span>next to the input. The screen reader doesn't know they're related. - An
aria-labelwhen a visible label would have worked. Always prefer a visible label.
<input placeholder="Email"> with no <label>. The visual designer says "the placeholder is enough." What's the strongest case against?Why div soup is the villain
Div soup is a page where almost everything is a <div> or <span>, with class names doing all the meaning: class="button", class="link", class="heading", class="navigation". It looks fine. It clicks fine. It is, accessibility-wise, a wasteland.
A screen-reader user lands on a div-soup page, asks for a list of headings, gets nothing. Asks for a list of landmarks, gets nothing. Tries to tab through, hits unreachable controls. They leave.
The fix isn't a giant refactor — it's a habit. Whenever you reach for <div>, ask:
- Is this a button?
<button>. - Is this navigation?
<nav>. - Is this a section title?
<h2>(or whatever level fits). - Is this a list of things?
<ul><li>. - Is this the page's main content?
<main>.
You'll write the same CSS, you'll keep the same design — and you'll get a working accessibility tree for free.
<div class="nav"><div class="link">Home</div>...</div>. The cheapest, highest-impact upgrade is:<button> over <div role="button">?Native HTML is your accessibility trust fund. Spend it. Next we'll look at the visual layer — color and contrast.