Patterns — Semantic HTML Structure
Contents
- Full page skeleton
- Complete head block (SEO + social)
- Article with sections
- Data table
- Figure, time, address, definition list
- Breadcrumbs
- Gotchas
Full page skeleton
html
<!doctype html>
<html lang="en">
<head>…see head block below…</head>
<body>
<header>
<a href="/" class="logo">Acme</a>
<nav aria-label="Main">
<ul>
<li><a href="/products">Products</a></li>
<li><a href="/pricing">Pricing</a></li>
</ul>
</nav>
</header>
<main>
<h1>Page topic — the only h1</h1>
<section>
<h2>First theme</h2>
<p>…</p>
</section>
<aside aria-label="Related links">
<h2>Related</h2>
<ul>…</ul>
</aside>
</main>
<footer>
<p><small>© 2026 Acme</small></p>
</footer>
</body>
</html>Complete head block (SEO + social)
html
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Pricing — Acme</title> <!-- ≤60 chars, specific first -->
<meta name="description" content="Acme plans from $9/mo. Compare Starter, Pro, and Team features."> <!-- ≤160 chars -->
<link rel="canonical" href="https://acme.com/pricing">
<!-- Open Graph -->
<meta property="og:title" content="Pricing — Acme">
<meta property="og:description" content="Acme plans from $9/mo.">
<meta property="og:image" content="https://acme.com/og/pricing.png"> <!-- 1200×630 -->
<meta property="og:url" content="https://acme.com/pricing">
<meta property="og:type" content="website">
<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image">
</head>Article with sections
html
<article>
<header>
<h1>How we cut LCP to 1.8s</h1>
<p>By <address style="display:inline">Jane Doe</address> ·
<time datetime="2026-08-05">August 5, 2026</time></p>
</header>
<section>
<h2>The problem</h2>
<p>…</p>
</section>
<section>
<h2>What we changed</h2>
<h3>Images</h3>
<p>…</p>
</section>
<footer>
<p>Filed under <a href="/tags/perf">performance</a></p>
</footer>
</article>Data table
html
<table>
<caption>Revenue by region, Q2 2026</caption>
<thead>
<tr><th scope="col">Region</th><th scope="col">Revenue</th></tr>
</thead>
<tbody>
<tr><th scope="row">EMEA</th><td>$4.2M</td></tr>
<tr><th scope="row">APAC</th><td>$3.1M</td></tr>
</tbody>
</table>Figure, time, address, definition list
html
<figure>
<img src="chart.png" alt="Revenue grew 40% quarter over quarter in Q2 2026">
<figcaption>Fig 1. Quarterly revenue</figcaption>
</figure>
<time datetime="2026-08-05T14:30">Aug 5, 2:30 PM</time>
<dl>
<dt>Plan</dt><dd>Pro</dd>
<dt>Seats</dt><dd>25</dd>
</dl>Breadcrumbs
html
<nav aria-label="Breadcrumb">
<ol>
<li><a href="/">Home</a></li>
<li><a href="/docs">Docs</a></li>
<li aria-current="page">Installation</li>
</ol>
</nav>Gotchas
<section>without a heading is meaningless — if it has no heading, it should be a<div>.<article>vs<section>: ask "would this make sense in an RSS feed alone?" Yes → article.- Multiple
<nav>landmarks need distinguishingaria-labels ("Main", "Breadcrumb", "Footer") — otherwise they announce identically. <time>requires machine-readabledatetimewhen the text isn't already ISO format.og:imagemust be an absolute URL; relative paths silently break social previews.<title>andog:titlecan differ — title is for tabs/SERP (include brand), og:title for the share card.- Skipping
<thead>/<th scope>makes header cells unassociable; screen readers and copy-paste both degrade. <b>/<i>vs<strong>/<em>: the former are stylistic offsets, the latter carry emphasis semantics — pick by meaning.