Semantic HTML vs div soup

Semantic HTML communicates the purpose and structure of content to browsers, screen readers, and search engines. Using <div> and <span> for everything creates “div soup” — markup that looks structured visually but means nothing programmatically.

Why semantics matter

Screen readers use semantic elements to build a navigable outline of the page. A user can jump between landmarks, headings, and regions without reading every word.

Compare these two approaches :

<div class="header">
    <div class="nav">
        <div class="nav-item"><a href="/">Home</a></div>
    </div>
</div>
<div class="main">
    <div class="article">
        <div class="title">Understanding Flexbox</div>
        <div class="content">...</div>
    </div>
    <div class="sidebar">...</div>
</div>
<div class="footer">...</div>
<header>
    <nav aria-label="Main">
        <a href="/">Home</a>
    </nav>
</header>
<main>
    <article>
        <h1>Understanding Flexbox</h1>
        <p>...</p>
    </article>
    <aside aria-label="Related posts">...</aside>
</main>
<footer>...</footer>

The second version creates a navigable landmark structure that screen readers announce immediately.

Element reference

<header>

Page or section banner. Contains the logo, nav, and site-level controls :

<header>
    <a href="/" aria-label="Home">Logo</a>
    <nav aria-label="Main navigation">...</nav>
</header>

An <article> can also have its own <header> :

<article>
    <header>
        <h2>Building accessible forms</h2>
        <time datetime="2024-01-15">January 15, 2024</time>
    </header>
    <p>...</p>
</article>

<nav>

A section with navigation links. Label it when there are multiple navs :

<nav aria-label="Main">...</nav>
<nav aria-label="Breadcrumb">...</nav>
<nav aria-label="Pagination">...</nav>

<main>

The primary content of the page. There must be only one per page :

<body>
    <header>...</header>
    <main id="main-content">
        <!-- Primary content here -->
    </main>
    <footer>...</footer>
</body>

<article>

Self-contained content that makes sense on its own — blog posts, news stories, comments, product cards :

<article>
    <h2>Accessible color palettes</h2>
    <p>Choosing colors that meet WCAG contrast requirements...</p>
</article>

Articles can be nested (e.g., a blog post with comments) :

<article>
    <h2>My blog post</h2>
    <p>Content...</p>
    
    <section aria-label="Comments">
        <h3>Comments</h3>
        <article>
            <h4>Jane</h4>
            <p>Great post!</p>
        </article>
    </section>
</article>

<section>

A thematic grouping of content with a heading. Use it when the content would logically appear in a document outline :

<section aria-labelledby="features-heading">
    <h2 id="features-heading">Features</h2>
    <p>...</p>
</section>

Rule of thumb : If you can’t give it a meaningful heading, it probably shouldn’t be a <section>. Use a <div> instead.

<aside>

Content tangentially related to the main content — sidebars, pull quotes, related links :

<aside aria-label="Related articles">
    <h2>Related articles</h2>
    <ul>
        <li><a href="/forms">Accessible forms</a></li>
        <li><a href="/aria">ARIA fundamentals</a></li>
    </ul>
</aside>

<figure> and <figcaption>

Self-contained media with a caption :

<figure>
    <img src="chart.png" alt="Bar chart showing screen reader market share in 2024" />
    <figcaption>
        NVDA leads at 40%, followed by JAWS at 30% and VoiceOver at 20%.
    </figcaption>
</figure>

Use for images, code blocks, diagrams, tables, or any content that has a caption :

<figure>
    <pre><code>const x = 42;</code></pre>
    <figcaption>Declaring a variable in JavaScript</figcaption>
</figure>

<footer>

Closing content for a page or section. Contains copyright, related links, contact info :

<footer>
    <p>&copy; 2024 Your Name. All rights reserved.</p>
    <nav aria-label="Footer">
        <a href="/privacy">Privacy Policy</a>
        <a href="/terms">Terms of Service</a>
    </nav>
</footer>

When to use <div>

Use <div> for purely visual grouping that has no semantic meaning :

<div class="card-grid">
    <article class="card">...</article>
    <article class="card">...</article>
</div>

<div class="flex-spacer"></div>

<div class="background-wrapper">
    <section>...</section>
</div>

Common mistakes

  • <section> without a heading — This creates a landmark with no label, confusing screen reader users.
  • <nav> for every list of links — Only use <nav> for major navigation blocks. A list of social links in the footer doesn’t need it.
  • Multiple <main> elements — There must be exactly one <main> per page.
  • <article> for non-standalone content — If the content doesn’t make sense on its own (like a sidebar widget), use <section> or <div>.
  • Overusing landmarks — Too many landmarks make navigation harder, not easier. A screen reader user pressing D to jump between landmarks benefits from 4-6 landmarks, not 20.

Resources

If you found this helpful, share it with someone who's building for the web.

- Vinay Ranjan