Concept
Semantic HTML is the practice of using elements according to their meaning, not their default appearance. A <button> and a <div onclick="..."> can look identical after CSS, but only one of them tells the browser, assistive technology, and search engines "this is a clickable control." The other is just a rectangle that happens to respond to clicks.
This matters more than it looks like it should, because HTML is read by more than just your CSS:
- Screen readers build a navigable outline from headings, landmarks, and roles,
<nav>,<main>,<h1>,<h6>. - Search engines weight content inside
<article>,<h1>, and semantic landmarks more heavily than an equivalent<div>soup. - Browsers give semantic elements free behavior,
<button>is keyboard-focusable and triggers onEnter/Spacewith zero JavaScript; a<div>gives you none of that. - Other engineers (including future you) read the DOM tree to understand the page's structure before reading any CSS or JS.
The document outline
<body>
<header>
<nav>...</nav>
</header>
<main>
<article>
<h1>Post title</h1>
<section>
<h2>Section heading</h2>
<p>...</p>
</section>
</article>
<aside>Related links</aside>
</main>
<footer>...</footer>
Each landmark element (<header>, <nav>, <main>, <aside>, <footer>) maps to an ARIA landmark role automatically, banner, navigation, main, complementary, contentinfo, for free. A screen reader user can jump straight to <main> with a single keystroke ("skip to main content" is literally built into the element, no skip-link required if you use it correctly).
<div>/<span> vs semantic elements
<div> and <span> are semantically neutral, they carry no meaning. They're the right choice only when no semantic element fits (a generic styling wrapper, a layout grid cell). If you're reaching for <div> to represent a button, a list, a heading, or a piece of navigation, there's almost always a better element:
| Instead of | Use |
|---|---|
<div class="button"> | <button> |
<div class="header"> | <header> |
<div class="list"><div>item</div></div> | <ul><li>item</li></ul> |
<div class="link" onclick> |
Sectioning elements: <article> vs <section> vs <div>
This is the distinction most engineers get wrong:
<article>, content that's independently distributable and makes sense on its own outside the page (a blog post, a forum comment, a product card in a listing). Ask: "would this make sense in an RSS feed?"<section>, a thematic grouping of content, usually with its own heading, that's part of a larger whole and doesn't stand alone (a chapter, a tab panel, "Reviews" on a product page).<div>, no semantic meaning at all. Use it when you need a wrapper purely for styling/layout and neither of the above applies.
<article>
<h2>How closures work</h2>
<section>
<h3>Lexical scoping</h3>
<p>...</p>
</section>
<section>
<h3>Common mistakes</h3>
<p>...</p>
</section>
</article>A useful heuristic: if removing the heading from a <section> would make it meaningless, it's correctly used. <section> without a heading is almost always a <div> in disguise.
Inline semantics
Semantics aren't only about page structure, they apply within a sentence too:
<p><strong>Warning:</strong> this action <em>cannot</em> be undone.</p>
<p>The variable <code>count</code> is <mark>reassigned</mark> on line 12.</p>
<p>Meeting is at <time datetime="2026-07-12T15:00">3pm on July 12th</time>.</p>
<blockquote cite="https://example.com/source">
<p>Quoted text goes here.</p>
</blockquote><strong> and <em> carry semantic weight (importance / stress emphasis) that screen readers announce with a change in tone, <b> and <i> are purely visual (bold/italic) with no meaning. Use <b>/<i> only for stylistic conventions that genuinely have no semantic weight (e.g. a ship's name, a taxonomic term).
Headings form an outline, not a font-size picker
<!-- Wrong: skipping levels for visual size -->
<h1>Page title</h1>
<h4>Section</h4> <!-- skipped h2, h3 -->
<!-- Right: sequential, styled with CSS separately -->
<h1>Page title</h1>
<h2>Section</h2>
<h3>Subsection</h3>Never choose a heading level because it "looks right" at that font size, style it with CSS. Heading level should reflect document structure, and screen reader users frequently navigate by jumping between headings of a specific level.
Common Mistakes
1. <div> soup for interactive elements
<!-- Wrong -->
<div class="btn" onclick="submit()">Submit</div>Not focusable by keyboard (no Tab stop), not announced as a button by screen readers, doesn't respond to Enter/Space without extra JS, doesn't get :focus-visible styling for free.
<!-- Right -->
<button type="submit">Submit</button>2. Using <section> as a generic wrapper
<!-- Wrong, no heading, no distinct theme, just a styling wrapper -->
<section class="flex gap-4">
<div>Card 1</div>
<div>Card 2</div>
</section>If it has no heading and isn't a distinct thematic grouping, it's a <div>.
3. Multiple <h1>s without understanding the outline algorithm
The HTML5 "outline algorithm" that would have let you nest multiple <h1>s per <section> was never implemented by any browser or screen reader and was removed from the spec. In practice: one <h1> per page, then descend sequentially (<h2>, <h3>...).
4. <a> without href for buttons
<!-- Wrong -->
<a onclick="doThing()">Click me</a>An <a> without href isn't a link, it's not keyboard focusable and isn't announced as a link by assistive tech. If it triggers an action instead of navigating, it's a <button>.
5. Forgetting <label> in favor of placeholder
<!-- Wrong, placeholder disappears once typing starts, low contrast -->
<input placeholder="Email" />
<!-- Right -->
<label for="email">Email</label>
<input id="email" type="email" />Covered in depth in Forms & Validation, but it's fundamentally a semantics mistake: placeholder is a hint, not a label.
6. Using <br> for spacing instead of CSS
<!-- Wrong -->
<p>Line one<br><br><br>Line two</p>
<!-- Right, one <br> for an actual line break within content (e.g. a poem, an address); use margin/padding for spacing -->
<p>Line one</p>
<p style="margin-top: 2rem">Line two</p>Best Practices
- Reach for the most specific element first.
<nav>over<div class="nav">,<button>over<div onclick>,<time>over a plain<span>for dates. - One
<main>per page, containing the primary content, not the header, nav, or footer. - One
<h1>per page that describes the page's purpose; descend headings sequentially without skipping levels. - Use landmark elements for page regions:
<header>, , , , . They give you free ARIA landmark roles and free "skip navigation" behavior for screen reader users.
Further Resources
- MDN, HTML elements reference, the canonical list of every element and what it means.
- MDN, Document and website structure
- WHATWG HTML Living Standard, sections, the actual spec definitions for
<article>,<section>,<nav>,<aside>. - web.dev, HTML5 semantics
