Back to Accessibility
Accessibility
easy
mid

Why does using semantic HTML improve accessibility?

Use elements for their meaning, not appearance: <header>, <nav>, <main>, <article>, <section>, <aside>, <footer> for structure; <button> for clickable actions; <a> for navigation; <label> tied to inputs by id. Semantic HTML gives screen readers landmarks to navigate by, gives keyboard users native focus/activation, and gives search engines structure. Pair with ARIA only when no semantic element fits — and prefer 'no ARIA' over 'wrong ARIA.'

7 min read·~5 min to think through

Semantic HTML is the foundation of accessibility. Get it right and you inherit keyboard support, screen reader announcements, and SEO benefits for free. Get it wrong and you spend the next sprint patching it with ARIA.

The structural elements

html
<body>
  <header>
    <nav aria-label="Primary"><ul>…</ul></nav>
  </header>
  <main>
    <article>
      <h1>Title</h1>
      <section>
        <h2>Section</h2>

      </section>
    </article>
    <aside>Related</aside>
  </main>
  <footer>…</footer>
</body>

Each of these creates a landmark screen readers can jump between (VoiceOver: VO+U → Landmarks). <div> does not.

Interactive elements

NeedRight elementWrong (common mistake)
Click triggers an action<button><div onClick>
Navigates to a URL<a href><button onClick={navigate}>
Form input + label<label for> + <input id>label sibling, no association
Group of radio choices<fieldset><legend>unstyled divs
Expandable disclosure<details><summary>div + JS toggle
Modal dialog<dialog>div + role="dialog"

<button> gives you: Tab focus, Enter/Space activation, focus ring, disabled state, form submission semantics. <div onClick> gives you none of these — you have to reimplement them all, badly.

Heading hierarchy

One <h1> per page. Don't skip levels (<h1><h3>). Screen-reader users navigate by heading (VO+U → Headings), and a broken hierarchy is like a table of contents with missing chapters.

Forms

html
<label for="email">Email</label>
<input id="email" type="email" required aria-describedby="email-help">
<p id="email-help">We'll never share it.</p>

The for/id link is non-negotiable. Without it, clicking the label doesn't focus the input, and screen readers announce "edit text" with no context.

Images

html
<img src="chart.png" alt="Sales grew 30% Q3 to Q4">      <!-- meaningful -->
<img src="divider.svg" alt="">                            <!-- decorative -->

Empty alt="" for decorative images. Missing alt entirely makes screen readers read the filename.

ARIA — only when no semantic element fits

The first rule of ARIA: don't use ARIA. <button aria-label="Close"> is fine. <div role="button" aria-label="Close" tabindex="0" onClick={…} onKeyDown={…}> is a smell — that's reimplementing <button>.

ARIA is for things HTML doesn't have: tabs, comboboxes, live regions (aria-live="polite" for toast announcements), expanded/collapsed state on a disclosure trigger (aria-expanded).

Color and focus

  • Visible focus indicator on every focusable element — don't outline: none without a replacement.
  • Color contrast ≥ 4.5:1 for text, 3:1 for UI components (WCAG AA).
  • Never convey meaning by color alone — pair with an icon or text.

Testing

  • Tab through the page; can you reach and operate every interactive element?
  • Run axe-core or Lighthouse → Accessibility — catches ~30% of WCAG issues.
  • Test with a screen reader (VoiceOver Cmd+F5 on Mac, NVDA on Windows) at least once per feature.

Follow-up questions

  • What's the difference between <section> and <article>?
  • When would you use aria-live='polite' vs 'assertive'?
  • How do you handle a custom dropdown accessibly?
  • What's the role of the lang attribute on <html>?

Common mistakes

  • Using <div onClick> instead of <button>, missing all native keyboard and AT support.
  • Skipping heading levels (h1 → h3) for visual sizing — that's what CSS is for.
  • outline: none on focus with no replacement — invisible focus, fails WCAG.
  • Aria-label on elements that already have visible text (overrides it for screen readers).
  • Using <label>Click me</label><input> without a for/id link.
  • alt='image of …' (screen readers already announce 'image'), or missing alt entirely.

Performance considerations

  • Semantic HTML has no runtime cost vs <div> soup — same bytes, same render. The win is in maintainability and zero-cost accessibility. ARIA attributes are cheap; the cost of bad ARIA is correctness, not perf.

Edge cases

  • Icon-only buttons need aria-label (or visually-hidden text) so screen readers announce a name.
  • Decorative images need alt='' (empty), not missing alt.
  • Dynamic content (e.g. error messages appearing after submit) need aria-live regions to be announced.
  • Modal dialogs need focus trap, return focus to opener on close, and Escape to close.
  • Reduced-motion users (@media (prefers-reduced-motion)) — skip animations.

Real-world examples

  • GitHub's UI is largely semantic — try VoiceOver landmark navigation, you can jump straight to the file list, sidebar, or footer.
  • Reddit's old design used <article> for each post — screen reader users could jump post-to-post with one shortcut.
  • Reach UI, Radix UI, React Aria — component libraries built around semantic-first, ARIA-when-needed.

Senior engineer discussion

Seniors should know the 'first rule of ARIA' (don't), the landmark model, the difference between semantic structure and visual styling, and how to audit (axe, manual keyboard test, screen reader smoke test). Bonus: knowing that WAI-ARIA Authoring Practices are patterns, not specs — and that production-grade implementations (Radix, React Aria) handle the dozens of edge cases hand-rolled versions miss.

Related questions