UXR SEO Analyzer documentation

Detailed guide

Semantic HTML Implementation Guide

View contents

Introduction

Semantic HTML means choosing elements that communicate what the content is, not just how it looks. When a developer writes <nav> instead of <div class="navigation">, they are passing on structural information that browsers, screen readers and search engines interpret automatically. That distinction underpins WCAG 2.2 success criteria 1.3.1 (Info and Relationships) and 4.1.2 (Name, Role, Value), both Level A — the accessibility floor.

The practical impact is large. WebAIM's data puts 96.3% of home pages as carrying automatically detectable accessibility errors, and a sizeable share of those come from using the wrong HTML element. When the semantic structure is right, screen readers can generate a navigation outline on their own, keyboard users find the landmarks they expect, and search engines understand the content hierarchy without falling back on heuristics.

This guide covers implementation patterns proven on real projects, working from overall document structure down to specific components such as forms and tables.

Why semantic HTML matters beyond compliance

Many development teams treat semantic HTML as an accessibility box to tick. In practice its benefits reach three areas that matter:

Accessibility: Screen readers such as JAWS, NVDA and VoiceOver build a landmark map from semantic elements. A blind user can jump straight to <main>, list every <nav>, or walk the <h2> headings as an outline. Without semantic HTML, none of that exists.

SEO: Google uses semantic elements to understand page structure. Headings <h1> through <h6> signal the topical hierarchy, <article> identifies self-contained content, and <nav> delimits navigation blocks. That feeds directly into how content is indexed and presented in results.

Maintainability: A document built from <header>, <main>, <aside> and <footer> reads and maintains far better than one built from nested <div> layers. New team members grasp the structure without reading the CSS classes first.

Document structure: the base template

Every accessible HTML document starts with a landmark structure that organises the page's main regions. Landmarks are the reference points assistive technology uses to build a quick-navigation map.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Page Title - Site Name</title>
</head>
<body>
  <a href="#main-content" class="skip-link">Skip to main content</a>

  <header class="site-header">
    <a href="/" class="logo">
      <img src="/logo.svg" alt="Company Name">
    </a>
    <nav aria-label="Main navigation">
      <ul>
        <li><a href="/">Home</a></li>
        <li><a href="/services">Services</a></li>
        <li><a href="/contact">Contact</a></li>
      </ul>
    </nav>
  </header>

  <main id="main-content" tabindex="-1">
    <article>
      <header>
        <h1>Article Title</h1>
        <p class="meta">
          Published <time datetime="2024-12-16">16 December 2024</time>
        </p>
      </header>
      <section aria-labelledby="intro-heading">
        <h2 id="intro-heading">Introduction</h2>
        <p>Introductory content...</p>
      </section>
    </article>
  </main>

  <aside aria-label="Related content">
    <h2>Related Articles</h2>
    <ul>
      <li><a href="/article-1">Related Article 1</a></li>
    </ul>
  </aside>

  <footer class="site-footer">
    <nav aria-label="Footer navigation">
      <ul>
        <li><a href="/privacy">Privacy Policy</a></li>
        <li><a href="/terms">Terms of Service</a></li>
      </ul>
    </nav>
    <p>&copy; 2024 Company Name.</p>
  </footer>
</body>
</html>

Three patterns in this template do the work. First, the skip link lets keyboard users jump straight to the content without walking the whole navigation. Second, each <nav> carries a different aria-label, because when several landmarks of the same type exist, the label is what tells them apart for the screen reader. Third, <main> carries tabindex="-1" so it can take focus when the user activates the skip link.

Landmark regions: when to use each one

HTML5 landmark elements map directly onto ARIA roles. The general rule is to use the native HTML element where one exists, and reach for the ARIA role only when there is no semantic alternative.

HTML5 element Equivalent ARIA role When to use it
<header> (page level) banner Global site header with logo and navigation
<nav> navigation Any block of navigation links
<main> main The page's main content (one per page)
<aside> complementary Tangentially related content
<footer> (page level) contentinfo Global footer with copyright and legal links
<section> region (when labelled) Thematic grouping of content
<search> search Search form or functionality

The most common mistake is landmark overuse. Wrapping every paragraph in a <section> with an aria-label floods the screen reader's navigation map and makes orientation harder, not easier. A landmark should stand for a meaningful region of the page, not an arbitrary subdivision of the text.

<!-- Wrong: too many landmarks -->
<main>
  <section aria-label="Paragraph 1"><p>...</p></section>
  <section aria-label="Paragraph 2"><p>...</p></section>
</main>

<!-- Right: only meaningful landmarks -->
<main>
  <h1>Page Title</h1>
  <p>Introduction...</p>
  <section aria-labelledby="features">
    <h2 id="features">Features</h2>
    <!-- Substantial section content -->
  </section>
</main>

When you use aria-labelledby, point it at the id of the section's heading. That ties the label to visible text, keeping what the user sees and what the screen reader announces in step.

Heading hierarchy: the content skeleton

Headings are the main mechanism screen reader users have for navigating long documents. An NVDA user can press H to jump between headings, or list every <h2> on the page as an outline. If the hierarchy skips a level — <h2> to <h4> with no <h3> in between — the user loses the sense of how sections relate.

Three rules cover it: use one <h1> per page as the main title, keep the hierarchy descending without skipping levels, and make every heading descriptive on its own.

<h1>Web Accessibility Guide</h1>

  <h2>WCAG Guidelines</h2>
    <h3>Perceivable</h3>
    <h3>Operable</h3>
    <h3>Understandable</h3>
    <h3>Robust</h3>

  <h2>Implementation Strategies</h2>
    <h3>Semantic HTML</h3>
      <h4>Document Structure</h4>
      <h4>Content Elements</h4>
    <h3>ARIA When Necessary</h3>

Sometimes a section needs a heading for accessibility that should not appear in the design. The visually-hidden technique keeps the heading available to screen readers without touching the layout:

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

This class is preferable to display: none or visibility: hidden, which hide the content from screen readers too.

Semantic lists: navigation and content

HTML offers three kinds of list, each with different semantics that screen readers announce differently. Unordered lists (<ul>) say the items are a group with no particular sequence, ordered lists (<ol>) signal a meaningful sequence, and description lists (<dl>) establish term-definition relationships.

The most frequent case is navigation, where links are structured as an unordered list inside a <nav>. When a screen reader meets that structure it announces "navigation, list of 4 items", which gives the user immediate context about how many destinations are available.

<nav aria-label="Main">
  <ul>
    <li><a href="/">Home</a></li>
    <li><a href="/about">About</a></li>
    <li><a href="/services">Services</a></li>
    <li><a href="/contact">Contact</a></li>
  </ul>
</nav>

Description lists are particularly useful for glossaries, metadata and technical specifications:

<dl>
  <dt>WCAG</dt>
  <dd>Web Content Accessibility Guidelines, an international standard maintained by the W3C.</dd>

  <dt>ARIA</dt>
  <dd>Accessible Rich Internet Applications, a specification for enhanced semantics in dynamic components.</dd>
</dl>

Data tables: structure that communicates relationships

HTML tables are for tabular data only, never for layout. An accessible table needs three things: a <caption> describing its purpose, <th> with a scope attribute to identify row and column headers, and logical grouping with <thead> and <tbody>.

<table>
  <caption>Monthly Sales Report</caption>
  <thead>
    <tr>
      <th scope="col">Month</th>
      <th scope="col">Revenue</th>
      <th scope="col">Growth</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">January</th>
      <td>$10,000</td>
      <td>+5%</td>
    </tr>
    <tr>
      <th scope="row">February</th>
      <td>$12,000</td>
      <td>+20%</td>
    </tr>
  </tbody>
</table>

The scope attribute is the critical part. Without it a screen reader cannot associate a data cell with its header, which turns the table into a flat sequence of values with no context.

Accessible forms: labels, grouping and validation

Forms are where semantic HTML has the largest effect on the actual experience. A field with no associated <label> is a direct obstacle for anyone using a screen reader or voice navigation.

The full pattern includes <fieldset> and <legend> to group related fields, a <label> tied by for/id, and aria-describedby for supplementary instructions:

<form action="/submit" method="post">
  <fieldset>
    <legend>Personal Information</legend>
    <div class="form-group">
      <label for="name">Full Name <span aria-hidden="true">*</span></label>
      <input type="text" id="name" name="name" required
             aria-describedby="name-hint" autocomplete="name">
      <p id="name-hint" class="hint">Enter your name as it appears on your ID</p>
    </div>
  </fieldset>
  <button type="submit">Submit Form</button>
</form>

Note that the required-field asterisk carries aria-hidden="true". The required attribute on the input already tells the screen reader the field is mandatory; the visual asterisk is redundant for assistive technology and could confuse if read out as "asterisk".

The rule is direct: buttons (<button>) perform actions within the page — opening a modal, submitting a form, triggering a function — while links (<a>) navigate to a different resource: another page, a downloadable file, a section of the document.

<!-- Button: performs an action -->
<button type="button" onclick="openModal()">Open Settings</button>

<!-- Link: navigates to a resource -->
<a href="/settings">Go to Settings</a>

The most common error is a link with href="#" and an onclick that performs an action. It confuses screen readers, which announce "link" where it should be "button"; it breaks keyboard navigation, since Enter on a link navigates rather than acts; and it strips native features such as opening in a new tab.

Automated testing of semantic structure

Automated tests can check that semantic elements are present and correctly nested on every deploy, which is what stops regressions when the markup changes.

import { test, expect } from '@playwright/test';

test('the page has a correct landmark structure', async ({ page }) => {
  await page.goto('/');
  await expect(page.locator('header')).toBeVisible();
  await expect(page.locator('main')).toBeVisible();
  await expect(page.locator('footer')).toBeVisible();
  const mainCount = await page.locator('main').count();
  expect(mainCount).toBe(1);
});

test('the heading hierarchy does not skip levels', async ({ page }) => {
  await page.goto('/');
  const headings = await page.locator('h1, h2, h3, h4, h5, h6').all();
  let lastLevel = 0;
  for (const heading of headings) {
    const tagName = await heading.evaluate(el => el.tagName);
    const level = parseInt(tagName[1]);
    if (lastLevel > 0 && level > lastLevel + 1) {
      const text = await heading.textContent();
      throw new Error(`Level skip: "${text}" is h${level} after h${lastLevel}`);
    }
    lastLevel = level;
  }
});

These tests complement manual screen reader audits, which are still needed to judge what navigating by landmarks and headings actually feels like.

Checklist

  • [ ] The page has appropriate landmark regions (header, nav, main, footer)
  • [ ] Only one <main> element per page
  • [ ] Only one <h1> per page, describing the main content
  • [ ] Heading hierarchy with no skipped levels
  • [ ] Navigation built with <nav> and lists (<ul> or <ol>)
  • [ ] Data tables with <caption>, <th> and the scope attribute
  • [ ] Forms with <label>, and <fieldset> / <legend> where appropriate
  • [ ] Buttons are <button> and links are <a>, according to function
  • [ ] Multiple landmarks of the same type carry distinguishing aria-labels
  • [ ] Automated tests verify structure and hierarchy

Frequently asked questions

Do I need to add ARIA roles to semantic HTML5 elements?

No. HTML5 elements such as <nav>, <main> and <header> carry implicit ARIA roles that modern browsers expose to the accessibility tree automatically. Adding role="navigation" to a <nav> is redundant. Explicit ARIA roles are only needed when a generic element (<div>, <span>) is used for a specific purpose — which is itself worth avoiding when a semantic equivalent exists.

Can I use more than one <h1> on a page?

The HTML specification technically allows multiple <h1> elements, but the practice recommended by the W3C and most accessibility practitioners is one <h1> per page, reflecting its main title. Screen readers let users jump straight to the <h1>, and with several of them the user cannot tell which is the page's real title.

How do I handle dynamic components such as modals and dropdowns?

Dynamic components with no native HTML equivalent need ARIA. A modal needs role="dialog", aria-modal="true" and focus management. A dropdown uses aria-expanded and aria-haspopup. The W3C rule is clear: reach for native HTML first — <details> and <summary> for accordions, for instance — and fall back to ARIA only when the component does not exist in HTML.

Does semantic HTML affect page performance?

Semantic HTML has no negative performance impact. If anything it produces lighter documents than <div>-based alternatives carrying multiple classes and ARIA attributes, since native elements bring their semantics built in. Browsers also optimise the processing of known native elements.


References

  1. W3C - WCAG 2.2 SC 1.3.1 Info and Relationships
  2. W3C - WCAG 2.2 SC 4.1.2 Name, Role, Value
  3. W3C - ARIA Landmarks Example
  4. MDN - HTML elements reference
  5. WebAIM - Semantic Structure

Related articles

Related version

Introduction

Semantic HTML

Semantic HTML means using HTML elements according to their intended purpose rather than their visual appearance

Category hub

Hub

WCAG 2.2 Compliance Hub

Web accessibility ensures that websites and applications can be used by everyone, including people with disabilities

Last updated: