If you've been writing <div> for everything — page wrapper, navigation, article container, sidebar — you're not alone. It was the default move for years. But HTML has had proper semantic elements since HTML5, and using them makes your code easier to read, better for SEO, and genuinely more accessible. Let's dig into the elements that actually matter.

The word "semantic" just means the element carries meaning beyond its visual presentation. A <div> means nothing — it's a generic block. A <nav> tells the browser, search engines, and assistive technology: "this contains navigation links." That context costs you nothing to add and pays off in multiple ways.

Why Semantics Matter

Three real benefits, not theoretical ones:

  • SEO. Search engines use document structure to understand content hierarchy. A <main> element signals the primary content. An <article> inside it signals a self-contained piece. Google uses these signals when ranking pages.
  • Accessibility. Screen readers expose landmark regions to users. With <nav>, <main>, and <aside> in place, keyboard users can jump directly to the section they want — without tabbing through every link on the page.
  • Maintainability. A new developer reading your template instantly understands the page structure. <header>/<main>/<footer> is self-documenting in a way that <div class="top">/<div class="content">/<div class="bottom"> is not.

The Core Layout Elements

These five elements handle the outer skeleton of most pages. Use them for every page and you've already won half the semantic battle:

html
<body>
  <header>
    <a href="/" class="logo">MyBlog</a>
    <nav>
      <ul>
        <li><a href="/articles">Articles</a></li>
        <li><a href="/about">About</a></li>
        <li><a href="/contact">Contact</a></li>
      </ul>
    </nav>
  </header>

  <main>
    <!-- Primary page content goes here -->
  </main>

  <aside>
    <!-- Sidebar: related links, ads, author bio -->
  </aside>

  <footer>
    <p>&copy; 2026 MyBlog. All rights reserved.</p>
  </footer>
</body>
  • <header>. Introductory content for the page or a section. Usually holds the logo, site title, and primary nav. You can also use it inside <article> for an article's own heading block.
  • <nav>. A set of navigation links. Not every group of links needs a <nav> — use it for major navigation blocks like site nav or pagination.
  • <main>. The dominant content of the page. There should be only one <main> per page, and it should not contain anything repeated across pages (header, nav, footer).
  • <aside>. Content tangentially related to the main content. Sidebars, pull quotes, related articles. Screen readers expose this as a complementary landmark.
  • <footer>. Footer for the page or a section. Can contain copyright info, secondary nav, contact links, or a brief author note.
Common mistake: <header> and <footer> are not restricted to the page level. Each <article> or <section> can have its own <header> and <footer>. This is especially useful for blog post bylines and article-level metadata.

article vs section — When to Use Each

This is the one that trips people up most. The rule of thumb that actually works: if the content would make sense if you syndicated it on its own (e.g., published it as an RSS entry or shared it on another site), it's an <article>. If it only makes sense as part of a larger whole, it's a <section>.

html
<!-- article: self-contained, could stand alone -->
<article>
  <header>
    <h2>Understanding HTTP/2 Push</h2>
    <p>By Alice Chen — <time datetime="2026-03-15">March 15, 2026</time></p>
  </header>
  <p>HTTP/2 Server Push lets the server send resources...</p>
  <footer>
    <p>Tagged: <a href="/tags/http">HTTP</a>, <a href="/tags/performance">Performance</a></p>
  </footer>
</article>

<!-- section: thematic grouping within a page -->
<section>
  <h2>Related Articles</h2>
  <ul>
    <li><a href="/http3-quic">HTTP/3 and QUIC Explained</a></li>
    <li><a href="/cdn-strategy">CDN Strategy for Global Apps</a></li>
  </ul>
</section>

<section> should always have a heading (<h2> through <h6>). If your section doesn't have a heading, that's usually a sign you should use a <div> instead. The WHATWG spec is clear on this: <section> is for thematic groupings, not arbitrary layout divisions.

figure, figcaption, time, and address

Four elements developers often miss but reach for regularly:

html
<!-- figure + figcaption: images, code blocks, diagrams, charts -->
<figure>
  <img src="/images/latency-chart.png" alt="P99 latency over 24 hours showing a spike at 14:30">
  <figcaption>Figure 1: API latency during the Tuesday incident (UTC).</figcaption>
</figure>

<!-- A code block as a figure (totally valid) -->
<figure>
  <pre><code>const result = await db.query('SELECT * FROM users WHERE active = true');</code></pre>
  <figcaption>Listing 1: Basic active user query.</figcaption>
</figure>

<!-- time: machine-readable dates and times -->
<p>Published <time datetime="2026-04-16T09:00:00Z">April 16, 2026</time></p>
<p>Sale ends in <time datetime="PT2H30M">2 hours 30 minutes</time></p>

<!-- address: contact info for nearest article or body ancestor -->
<address>
  Written by <a href="mailto:[email protected]">Alice Chen</a>.<br>
  123 Dev Street, San Francisco, CA.
</address>
  • <figure>. Any self-contained content that's referenced from the main flow — images, code listings, charts, videos. The key: it could be moved to an appendix without disrupting reading flow.
  • <figcaption>. The caption for a <figure>. Must be the first or last child of <figure>. Gives you a semantic association between the caption and content that <p> below an image does not.
  • <time>. Human-readable dates with a machine-readable datetime attribute. The datetime value uses ISO 8601 format. Search engines use this for event markup and freshness signals.
  • <address>. Contact information for the nearest <article> ancestor (or the whole <body>). Not a general-purpose address element — it's specifically for contact info of the content author or site owner.

The Divitis Anti-Pattern

"Divitis" is what happens when every element in your HTML is a <div>. Here's a real before/after that shows the problem:

html
<!-- Before: divitis -->
<div class="page">
  <div class="header">
    <div class="brand">TechBlog</div>
    <div class="nav">
      <div class="nav-item"><a href="/posts">Posts</a></div>
    </div>
  </div>
  <div class="post">
    <div class="post-title">Why TypeScript Is Worth It</div>
    <div class="post-meta">By Bob — Jan 2026</div>
    <div class="post-body">TypeScript adds static types to JavaScript...</div>
  </div>
</div>

<!-- After: semantic HTML -->
<body>
  <header>
    <span class="brand">TechBlog</span>
    <nav>
      <a href="/posts">Posts</a>
    </nav>
  </header>
  <main>
    <article>
      <header>
        <h1>Why TypeScript Is Worth It</h1>
        <p>By Bob — <time datetime="2026-01-10">January 10, 2026</time></p>
      </header>
      <p>TypeScript adds static types to JavaScript...</p>
    </article>
  </main>
</body>

The semantic version is actually shorter, requires fewer CSS class names, and gives browsers and assistive tools everything they need to understand the page structure. You can still style everything exactly the same with CSS — semantic elements don't impose any visual constraints.

ARIA Roles — A Last Resort, Not a First Move

ARIA roles let you add semantic meaning to elements that don't have it natively. The problem is developers often reach for ARIA when a semantic HTML element would do the job better. The first rule of ARIA: don't use ARIA if you can use native HTML.

html
<!-- Wrong: using ARIA where native HTML works -->
<div role="navigation">
  <a href="/home">Home</a>
</div>

<!-- Right: just use the semantic element -->
<nav>
  <a href="/home">Home</a>
</nav>

<!-- ARIA is appropriate here: custom widget with no HTML equivalent -->
<div
  role="tablist"
  aria-label="Code examples"
>
  <button role="tab" aria-selected="true" aria-controls="panel-js">JavaScript</button>
  <button role="tab" aria-selected="false" aria-controls="panel-py">Python</button>
</div>

Use ARIA for interactive widgets that native HTML doesn't cover — tab panels, comboboxes, tree views, date pickers. For structural landmarks like navigation, headers, and main content, lean on native elements every time.

A Complete Blog Post Page Structure

Here's what a fully semantic blog post page looks like. This is the pattern used on sites that score well in accessibility audits without extra effort:

html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Why TypeScript Is Worth It — TechBlog</title>
</head>
<body>
  <header>
    <a href="/" class="logo">TechBlog</a>
    <nav aria-label="Site navigation">
      <ul>
        <li><a href="/posts">Posts</a></li>
        <li><a href="/topics">Topics</a></li>
        <li><a href="/about">About</a></li>
      </ul>
    </nav>
  </header>

  <main>
    <article>
      <header>
        <h1>Why TypeScript Is Worth It</h1>
        <address>
          By <a rel="author" href="/authors/bob">Bob Martinez</a>
        </address>
        <p>Published <time datetime="2026-01-10">January 10, 2026</time></p>
      </header>

      <section>
        <h2>The Setup Cost Is Real but Front-Loaded</h2>
        <p>TypeScript adds a compilation step and requires type annotations...</p>

        <figure>
          <pre><code>interface User {
  id: number;
  name: string;
  email: string;
}</code></pre>
          <figcaption>Listing 1: A typed User interface catches shape errors at compile time.</figcaption>
        </figure>
      </section>

      <section>
        <h2>Where TypeScript Pays Off</h2>
        <p>The ROI kicks in as team size and codebase complexity grows...</p>
      </section>

      <footer>
        <p>
          Tagged: <a href="/tags/typescript">TypeScript</a>,
          <a href="/tags/javascript">JavaScript</a>
        </p>
      </footer>
    </article>

    <aside aria-label="Related articles">
      <h2>You Might Also Like</h2>
      <ul>
        <li><a href="/ts-vs-js">TypeScript vs JavaScript — A Practical Comparison</a></li>
        <li><a href="/ts-generics">TypeScript Generics Explained</a></li>
      </ul>
    </aside>
  </main>

  <footer>
    <nav aria-label="Footer navigation">
      <a href="/privacy">Privacy</a>
      <a href="/terms">Terms</a>
    </nav>
    <p><small>&copy; 2026 TechBlog</small></p>
  </footer>
</body>
</html>

Notice the dual use of <header> and <footer> — once at the page level, once inside <article>. That's intentional and correct. Also notice two <nav> elements with distinct aria-label values — this prevents screen readers from listing two identical "navigation" regions.

Quick Reference: Tools That Help

If you're working with HTML and want to clean up or validate your markup, HTML Formatter will pretty-print and tidy your code, and HTML Validator catches structural errors like unclosed tags and missing required attributes. For a deeper accessibility audit, web.dev/accessibility has excellent diagnostics and explanations.

Wrapping Up

Semantic HTML isn't about following rules for their own sake — it's about writing code that communicates clearly to browsers, search engines, assistive tools, and the next developer who reads your template. The core elements are straightforward: <header>, <nav>, <main>, <article>, <section>, <aside>, and <footer> cover the overwhelming majority of real page structures. Add <figure>, <time>, and <address> where they fit, reach for ARIA only when native HTML falls short, and you'll have markup that's genuinely a pleasure to work with.