Semantic HTML
Semantic elements like header, nav, main and article versus div soup, and why it matters for accessibility and SEO.
Why "div soup" is a problem
Early web pages, and a lot of legacy code today, structure everything with generic containers:
<div class="header">
<div class="nav">...</div>
</div>
<div class="main">
<div class="post">...</div>
</div>
<div class="footer">...</div>
This works visually — CSS can style a <div> into anything — but it tells a browser, a screen reader, or a search engine crawler nothing about what each block actually is. <div> is a generic, meaning-free box. Semantic HTML replaces these generic wrappers with elements that describe their own purpose:
<header>
<nav>...</nav>
</header>
<main>
<article>...</article>
</main>
<footer>...</footer>
Same visual result once styled — completely different amount of information conveyed to anything parsing the document.
The core layout elements
| Element | Represents |
|---|---|
<header> |
Introductory content for a page or a section — typically a logo, title, and/or navigation. Can appear once per page or once per <article>/<section>. |
<nav> |
A block of primary navigation links. |
<main> |
The dominant, unique content of the page. There should be exactly one visible <main> per page. |
<article> |
Self-contained content that would make sense distributed on its own — a blog post, a news story, a forum comment. |
<section> |
A thematic grouping of content, usually with its own heading. Use it when content forms a distinct block but isn't standalone enough to be an <article>. |
<aside> |
Content tangentially related to the surrounding content — a sidebar, a pull quote, related links. |
<footer> |
Closing content for a page or section — copyright, contact info, related links. |
A realistic page layout combining them:
<body>
<header>
<h1>NOA Labs Blog</h1>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/tutorials">Tutorials</a></li>
</ul>
</nav>
</header>
<main>
<article>
<h2>Understanding Semantic HTML</h2>
<p>Published on <time datetime="2026-08-25">August 25, 2026</time></p>
<section>
<h3>Why it matters</h3>
<p>...</p>
</section>
</article>
<aside>
<h3>Related articles</h3>
<ul>
<li><a href="/html/forms-and-inputs">Forms & Inputs</a></li>
</ul>
</aside>
</main>
<footer>
<p>© 2026 NOA Labs. All rights reserved.</p>
</footer>
</body>
Notice <article> and <section> are not mutually exclusive with <div> — a <div> is still the right choice for a purely presentational wrapper (e.g. a grid container) that carries no semantic meaning of its own.
<section> vs <article> vs <div>
This is the most common point of confusion:
- Use
<article>when the content could be lifted out and syndicated on its own (an RSS feed entry, a product card, a blog post) and would still make sense. - Use
<section>for a thematic chunk of a larger document that has its own heading, but isn't independently distributable. - Use
<div>when you need a wrapper purely for styling or scripting hooks, and the content has no inherent semantic grouping.
A quick test: if removing the element and just keeping a <div> wouldn't lose any meaning (only styling), a <div> was the right choice all along.
Why it matters: accessibility
Screen readers build a navigation landmark list directly from semantic elements. A blind user can jump straight to <nav>, <main>, or <footer> with a keyboard shortcut instead of tabbing through the entire page linearly. A page built entirely from <div>s has no landmarks at all — the screen reader has no way to know where "the main content" even starts.
Heading levels (<h1>–<h6>) matter for the same reason: screen reader users frequently navigate a page by jumping between headings, the same way a sighted user visually scans a page for bold section titles. Skipping levels (going from <h1> straight to <h3>) breaks that mental outline.
Why it matters: SEO
Search engines weight content inside <article>, <h1>–<h6>, and <nav> differently than content inside generic <div>s — semantic structure helps a crawler understand what the page is about and how it's organized, which can directly affect ranking and how rich snippets are generated.
ARIA basics
ARIA (Accessible Rich Internet Applications) attributes fill gaps semantic HTML can't cover on its own — mostly needed for custom, JavaScript-driven widgets that don't map to a native element (a custom dropdown, a tab panel, a modal).
<button aria-expanded="false" aria-controls="menu">Menu</button>
<ul id="menu" role="menu" hidden>
<li role="menuitem"><a href="/profile">Profile</a></li>
<li role="menuitem"><a href="/logout">Logout</a></li>
</ul>
role— tells assistive technology what a generic element behaves like (role="menu"on a<ul>being used as a dropdown menu).aria-label/aria-labelledby— provides an accessible name when there's no visible text (e.g. an icon-only button).aria-expanded— communicates open/closed state for a toggle.aria-hidden="true"— hides purely decorative content from assistive technology.
The first rule of ARIA is: don't use ARIA if a native semantic element already does the job. <button> is already keyboard-accessible and announces itself as a button — adding role="button" to a <div> just to avoid using an actual <button> recreates work the browser already gives you for free, and usually recreates it incompletely (no keyboard Enter/Space activation by default, for one).
Common mistakes
- Using
<div>and<span>for everything and relying on CSS classes alone to imply structure — invisible to screen readers and crawlers. - Having more than one
<h1>per page, or skipping heading levels (<h1>→<h4>) purely to get a smaller font size — use CSS for sizing, headings for outline structure. - Reaching for ARIA roles on custom
<div>-based widgets when a native element (<button>,<a>,<select>) would provide the same behavior with far less code and fewer edge cases. - Using
<section>as a generic styling wrapper — if it has no heading and no real thematic identity, it should probably be a<div>.