Skip to main content

Why semantic HTML matters

We always want to prefer native implementations over custom ones if we can help it. Semantic HTML is the foundation of web accessibility.
Semantic HTML provides meaning and structure to your content, making it understandable for both humans and assistive technologies.

Benefits of semantic HTML

  • Better accessibility - Screen readers and other assistive technologies rely on semantic HTML to understand page structure
  • Improved SEO - Search engines better understand your content hierarchy and meaning
  • Easier maintenance - Code is more readable and self-documenting
  • Built-in functionality - Native elements come with keyboard support, focus management, and expected behaviors
  • Better performance - Native elements are optimized by browsers
  • Reduced code - Less JavaScript and ARIA needed when using proper HTML
Using generic <div> and <span> elements for everything removes crucial semantic information that assistive technologies depend on.

Landmark elements

We always want to make sure we are using proper landmark elements where applicable. Landmark elements define the major sections of your page and help users understand and navigate the overall structure.

Common landmark elements

Contains introductory content, typically including navigation, logos, and site-level actions.
<header>
  <nav>
    <a href="/">Home</a>
    <a href="/introduction">Introduction</a>
  </nav>
</header>
Implicit ARIA role: banner (when not nested in <article> or <section>)
Contains the primary content of the page. There should be only one <main> element per page.
<main id="main-content">
  <h1>Page Title</h1>
  <p>Main content goes here...</p>
</main>
ARIA role: main
The <main> element should not be nested inside <article>, <aside>, <footer>, <header>, or <nav>.
Represents a standalone section of content that would make sense in a document outline.
<section aria-labelledby="features-heading">
  <h2 id="features-heading">Features</h2>
  <p>Our product features include...</p>
</section>
ARIA role: region (when labeled with aria-labelledby or aria-label)
Always include a heading in your <section> elements and use aria-labelledby to connect them.
Represents a self-contained piece of content that could be independently distributed or reused.
<article>
  <h2>Blog Post Title</h2>
  <p>Published on <time datetime="2026-03-04">March 4, 2026</time></p>
  <p>Article content...</p>
</article>
ARIA role: articleGood use cases: blog posts, news articles, forum posts, product cards
Contains content tangentially related to the main content, like sidebars or callouts.
<aside>
  <h3>Related Articles</h3>
  <ul>
    <li><a href="/article-1">Article 1</a></li>
    <li><a href="/article-2">Article 2</a></li>
  </ul>
</aside>
ARIA role: complementary

Example from the course

Take a look at the HTML structure of the Web A11y for Devs introduction page:
<body class="min-h-dvh">
  <header class="sticky top-0 z-10 py-3">
    <a href="#main-content" class="skip-link">Skip to content</a>

    <nav class="container mx-auto flex items-center justify-between gap-4">
      <h1>
        <a href="/">
          <img src="logo.jpg" alt="TestDevLab" />
          Web A11y for Devs
        </a>
      </h1>

      <ul>
        <li><a href="./introduction" aria-current="page">Introduction</a></li>
      </ul>
    </nav>
  </header>

  <main id="main-content" tabindex="-1">
    <section aria-labelledby="screen-readers">
      <h2 id="screen-readers">Screen readers</h2>
      <!-- Content here -->
    </section>

    <section aria-labelledby="reduced-motion">
      <h3 id="reduced-motion">Animation & Reduced motion</h3>
      <!-- Content here -->
    </section>
  </main>

  <footer class="py-3 text-center font-semibold">
    <p>Web A11y for Devs, <span id="current-year"></span></p>
  </footer>
</body>
Notice the usage of landmark elements: <header>, <nav>, <main>, <section>, and <footer>. This structure helps screen reader users navigate efficiently.

Heading hierarchy

Proper heading structure is essential for accessibility and SEO.

The rules

1

Start with h1

Every page should have exactly one <h1> element that describes the main content of the page.
<h1>Web A11y for Devs</h1>
2

Follow sequential order

Don’t skip heading levels. After <h1>, use <h2>, then <h3>, and so on.
<!-- GOOD -->
<h1>Main Title</h1>
<h2>Section</h2>
<h3>Subsection</h3>

<!-- BAD - skips h2 -->
<h1>Main Title</h1>
<h3>Subsection</h3>
3

Use headings for structure, not styling

Choose heading levels based on content hierarchy, not appearance. Use CSS for styling.
/* Style h2 to look smaller if needed */
h2.small {
  font-size: 1.2rem;
}
4

Create a logical outline

Your headings should form a table of contents that makes sense when read in isolation.
Screen reader users often navigate by headings. A poor heading structure makes your content difficult or impossible to navigate efficiently.

Example heading structure

<h1>Web Accessibility Guide</h1>

<h2>Screen Readers</h2>
<h3>What are screen readers</h3>
<h3>How to download</h3>
<h3>Commands & Cheat sheets</h3>

<h2>Common Pitfalls</h2>
<h3>Reduced Motion</h3>
<h3>Focus Indicators</h3>
<h3>Custom Buttons</h3>

<h2>Best Practices</h2>
<h3>Testing strategies</h3>
<h3>Team collaboration</h3>

List structures

Lists provide structure and improve navigation for screen reader users.

When to use lists

Use semantic list elements for:
  • Navigation menus
  • Sets of related items
  • Sequential instructions
  • Table of contents
  • Tag collections
  • File listings

List types

Use <ul> for lists where the order doesn’t matter:
<ul>
  <li>NVDA</li>
  <li>JAWS</li>
  <li>VoiceOver</li>
</ul>
Screen readers announce the number of items in a list, helping users understand the scope of content.

Semantic elements vs generic divs

Knowing when to use semantic elements versus generic <div> or <span> tags is crucial.

When to use semantic elements

Use semantic elements when the content has inherent meaning or structure:
  • Buttons - <button> instead of <div onclick="...">
  • Links - <a href="..."> instead of <span onclick="...">
  • Navigation - <nav> instead of <div class="nav">
  • Main content - <main> instead of <div id="main">
  • Articles - <article> instead of <div class="article">
  • Forms - <form>, <input>, <label>, <fieldset> instead of custom elements
  • Tables - <table>, <thead>, <tbody>, <th>, <td> for tabular data

When divs and spans are okay

<div> and <span> are appropriate for styling and layout purposes when no semantic element fits:
<!-- OK: div for layout wrapper -->
<div class="container">
  <!-- OK: span for inline styling -->
  <p>This is <span class="highlight">important</span> text.</p>
</div>

<!-- OK: divs for CSS grid/flexbox layout -->
<div class="grid">
  <div class="grid-item">Item 1</div>
  <div class="grid-item">Item 2</div>
</div>

Native buttons vs custom buttons

A native button gives us all necessary and accessible features - out of the box:

Native button benefits

  • Proper semantic role: “button”
  • Keyboard controls (Enter & Space keys)
  • Clean focus management
  • Form integration
  • Disabled state support

Custom div problems

  • No semantic role
  • No keyboard support
  • No focus by default
  • Requires extensive ARIA
  • More code to maintain

The right way: native button

<button type="button" onclick="handleClick()">
  Click me
</button>

The wrong way: div button

<!-- BAD: Requires extensive work to make accessible -->
<div 
  class="button" 
  onclick="handleClick()"
  role="button"
  tabindex="0"
  onkeydown="handleKeyPress(event)"
>
  Click me
</div>
If you use a div as a button, you must add role, tabindex, and keyboard event handlers. And even then, you might miss edge cases. Just use a <button>.

Exercise: Fix broken landmarks

The Web A11y for Devs course includes a practice page with intentional landmark errors.

Homework exercise

Navigate to the /broken-landmarks page and identify all landmark element related issues.Common issues to look for:
  • Generic divs used instead of semantic elements
  • Missing or incorrect landmark elements
  • Improper nesting of landmarks
  • Multiple main elements
  • Missing heading structure
Your task: Fix all landmark element related issues in the page.

Example from broken-landmarks.html

The broken landmarks page intentionally uses divs instead of proper semantic elements:
<!-- BAD: Uses div instead of header -->
<div class="sticky top-0 z-10">
  <a href="#main-content" class="skip-link">Skip to content</a>
  
  <!-- BAD: Uses div instead of nav -->
  <div class="container mx-auto">
    <h1><a href="/">Web A11y for Devs</a></h1>
    <ul>
      <li><a href="./introduction">Introduction</a></li>
    </ul>
  </div>
</div>

<!-- BAD: Uses span instead of main -->
<span id="main-content" tabindex="-1">
  <div>
    <p>Content here...</p>
  </div>
</span>

<!-- BAD: Uses div instead of footer -->
<div class="py-5 text-center">
  <p>Web A11y for Devs, <span id="current-year"></span></p>
</div>

ARIA: When semantic HTML isn’t enough

Sometimes semantic HTML alone isn’t sufficient. ARIA (Accessible Rich Internet Applications) fills the gaps.
First rule of ARIA: Don’t use ARIA. Use native HTML whenever possible. ARIA should be a last resort.

When to use ARIA

  • Custom widgets - Complex interactive components not available in HTML (tree views, advanced tabs)
  • Dynamic content - Announcing live updates (aria-live)
  • Enhanced descriptions - Adding context beyond what HTML provides
  • States and properties - Communicating dynamic state changes

When NOT to use ARIA

Don’t use ARIA when a native HTML element exists:
<!-- BAD: Unnecessary ARIA -->
<div role="button" tabindex="0">Click me</div>

<!-- GOOD: Native element -->
<button>Click me</button>
Incorrect ARIA can make accessibility worse than no ARIA at all. Test thoroughly when adding ARIA attributes.

Best practices checklist

Semantic HTML checklist

  • Use exactly one <h1> per page
  • Follow sequential heading order (h1 → h2 → h3)
  • Use <header>, <nav>, <main>, <footer> for page structure
  • Wrap sections in <section> with descriptive headings
  • Use <button> for actions, <a> for navigation
  • Structure lists with <ul>, <ol>, or <dl>
  • Label sections with aria-labelledby or aria-label
  • Include skip links for keyboard navigation
  • Use <article> for self-contained content
  • Avoid divs when semantic elements exist
  • Test with screen readers to verify structure
  • Validate HTML to catch errors

Developer tools for testing

  • Chrome DevTools Accessibility panel
  • Firefox Accessibility Inspector
  • Edge DevTools Accessibility panel
These tools show the accessibility tree and identify issues.
Navigate your page with a screen reader to verify:
  • Landmarks are announced correctly
  • Heading structure makes sense
  • Interactive elements are identifiable
Browser extensions that show your heading structure:
  • HeadingsMap
  • HTML5 Outliner
  • W3C HTML Validator
  • axe DevTools
  • WAVE browser extension

Common mistakes to avoid

  • Using headings for styling - Don’t choose <h3> because you want smaller text; use CSS
  • Skipping heading levels - Always follow sequential order
  • Multiple h1 elements - Use only one <h1> per page (outside of <article> elements)
  • Divs everywhere - Replace generic divs with semantic elements where appropriate
  • Missing main element - Every page needs exactly one <main>
  • Improper nesting - Don’t nest <main> inside <article> or other landmarks
  • Empty landmarks - Don’t use landmark elements with no content
  • Too many landmarks - Not every div needs to be a section; use landmarks for major page regions

Additional resources

Build docs developers (and LLMs) love