Semantic HTML
Status: Complete
Category: Accessibility
Default enforcement: Soft
Author: PushBackLog team
Tags
- Topic: accessibility, quality
- Skillset: frontend
- Technology: HTML
- Stage: execution, review
Summary
Semantic HTML means using HTML elements for their intended purpose, not just their default visual appearance. Using the correct element — <nav>, <main>, <article>, <button> — communicates structure and intent to screen readers, search engines, and other tools that consume the DOM.
Rationale
HTML elements carry meaning, not just styling
Every HTML element has a default role, name, and set of expected behaviours communicated to the browser and its accessibility tree. A <button> is announced as a button by screen readers, is focusable by default, and fires on both click and Enter/Space. A <div> is announced as nothing, is not focusable, and responds only to pointer events.
When developers use <div onclick="..."> instead of <button>, they’re discarding all of that built-in accessibility behaviour and must manually reconstruct it with ARIA, tabindex, and JavaScript. The ARIA specification describes this as a last resort: “No ARIA is better than bad ARIA.”
Semantic HTML creates navigable structure
Screen reader users navigate pages by structure, not by visual layout:
- Jump to the next heading (
<h1>–<h6>) - Jump to the page
<main>to skip navigation - Jump between
<section>and<article>elements - Navigate a list of links within a
<nav>
A page built entirely from <div> and <span> elements is a flat, undifferentiated block of text to a screen reader. Heading hierarchy and sectioning elements are the page’s skeleton.
SEO and maintainability benefit too
Search engines use semantic elements to understand page structure: <h1> signals the primary topic; <article> signals standalone content; <main> signals the primary content area. Correct semantics improve crawability. Engineers reading the code also benefit: <nav> communicates intent; <div class="nav-wrapper"> requires reading the class to understand purpose.
Guidance
Common semantic elements and when to use them
| Element | Use for | NOT for |
|---|---|---|
<header> | Site header or section header (logo, nav, h1) | Arbitrary containers |
<nav> | Primary or secondary navigation lists | Any list of links |
<main> | The page’s primary content (one per page) | Repeated across pages |
<article> | Self-contained content that makes sense independently | Layout containers |
<section> | Thematically-grouped content with a heading | Dividing layout sections without semantic purpose |
<aside> | Content tangentially related to surrounding content | Pop-ups or modals |
<footer> | Site footer or section footer | Generic bottom-of-box areas |
<button> | Any clickable action that doesn’t navigate | Navigation (use <a> instead) |
<a> | Navigation to another URL or page anchor | Clickable actions without a URL |
<label> | Labelling every form input | Styling text near inputs |
Heading hierarchy
<!-- Wrong: headings chosen for size, not hierarchy -->
<h1>PushBackLog</h1>
<h3>Latest Sprints</h3> <!-- Skips h2 -->
<h5>Sprint 12</h5> <!-- Two levels skipped -->
<!-- Correct: hierarchy follows document structure -->
<h1>PushBackLog</h1>
<h2>Latest Sprints</h2>
<h3>Sprint 12</h3>
<h3>Sprint 11</h3>
<h2>Best Practices</h2>
If the visual size of a heading level is wrong, fix it with CSS, not by choosing the wrong heading level.
Interactive elements
<!-- Wrong: requires manual ARIA, tabindex, keyboard handling -->
<div
onclick="handleDelete()"
class="btn-delete"
role="button"
tabindex="0"
onkeypress="if(event.key === 'Enter') handleDelete()"
>
Delete
</div>
<!-- Correct: all of the above is built in -->
<button type="button" onclick="handleDelete()">Delete</button>
Examples
Page skeleton with semantic elements
<body>
<header>
<a href="/"><img src="/logo.svg" alt="PushBackLog" /></a>
<nav aria-label="Main navigation">
<ul>
<li><a href="/library">Library</a></li>
<li><a href="/sprints">Sprints</a></li>
</ul>
</nav>
</header>
<main>
<h1>Best Practices Library</h1>
<section>
<h2>SOLID Principles</h2>
<article>
<h3>Single Responsibility</h3>
<p>Every module should have one reason to change.</p>
</article>
</section>
</main>
<footer>
<p>© 2025 PushBackLog</p>
</footer>
</body>
Anti-patterns
1. <div onclick> instead of <button>
The most common. A <div> with an onclick handler is not keyboard-focusable, not announced as a button by screen readers, and fires no events on keyboard activation. Use <button type="button"> for actions; use <a href> for navigation.
2. Skipping heading levels for stylistic reasons
Going from <h1> to <h3> destroys the logical outline that screen reader users navigate by. Visual size is a styling concern; use CSS to resize headings.
3. Using <table> for layout
Layout tables were an HTML 4 workaround. They are announced as tables by screen readers, with meaningless “cell” navigation. Use CSS Grid or Flexbox for layout.
4. Nesting interactive elements
<!-- Invalid: button inside anchor -->
<a href="/checkout"><button>Checkout</button></a>
<!-- Screen readers and browsers handle this inconsistently -->
Interactive elements should not be nested. A link that contains a button is ambiguous.
Related practices
Part of the PushBackLog Best Practices Library. Suggest improvements →