Why Semantic HTML Matters
Why Semantic HTML Matters
Semantic HTML uses elements that describe their meaning, not just their appearance. Browsers, screen readers, and search engines all benefit from proper semantics.
Div vs Semantic Elements
<!-- Bad: non-semantic -->
<div class="header">
<div class="nav">
<div class="nav-item">Home</div>
</div>
</div>
<div class="main">
<div class="article">...</div>
</div>
<!-- Good: semantic -->
<header>
<nav aria-label="Main">
<a href="/">Home</a>
</nav>
</header>
<main>
<article>...</article>
</main>
Benefits
- Accessibility: Screen readers announce landmarks like
<nav>,<main>,<aside> - SEO: Search engines infer content hierarchy and importance
- Maintainability: Developers understand document structure at a glance
- Reduced CSS complexity: Semantic elements replace verbose class names
Document Outline
HTML5 introduced the document outline algorithm. While browsers don't fully implement it yet, using headings in order (h1 → h2 → h3) creates a logical outline. Every page should have exactly one <h1>.
HTML5 Structural Elements
HTML5 Structural Elements
<header>, <footer>, <main>, <nav>
These are sectioning elements that define page regions:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Blog Post</title></head>
<body>
<header>
<h1>My Blog</h1>
<nav aria-label="Main">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/posts">Posts</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
</header>
<main>
<article>
<header>
<h2>Understanding CSS Grid</h2>
<time datetime="2026-09-10">September 10, 2026</time>
</header>
<section>
<h3>Introduction</h3>
<p>CSS Grid is a two-dimensional layout system...</p>
</section>
<section>
<h3>Grid Container</h3>
<p>Set <code>display: grid</code> on a parent element...</p>
</section>
<footer>
<p>Tags: <a href="/tags/css">CSS</a>, <a href="/tags/grid">Grid</a></p>
</footer>
</article>
<aside aria-label="Related posts">
<h3>Related Articles</h3>
<ul>
<li><a href="/posts/flexbox">Flexbox Guide</a></li>
</ul>
</aside>
</main>
<footer>
<p>© 2026 My Blog</p>
</footer>
</body>
</html>
<article> vs <section>
<article>: Self-contained content that makes sense independently (blog post, comment, card)<section>: Thematic grouping of content, typically with a heading
<aside>
Content tangentially related to the content around it — sidebars, pull quotes, advertising.
Interactive and Inline Semantics
Interactive and Inline Semantics
Forms
Use semantic form elements with proper labels:
<form action="/api/contact" method="POST" novalidate>
<fieldset>
<legend>Contact Information</legend>
<label for="name">Full Name</label>
<input type="text" id="name" name="name" required
autocomplete="name" placeholder="Jane Doe">
<label for="email">Email</label>
<input type="email" id="email" name="email" required
autocomplete="email">
<label for="subject">Subject</label>
<select id="subject" name="subject" required>
<option value="">-- Select --</option>
<option value="support">Technical Support</option>
<option value="billing">Billing</option>
</select>
<label for="message">Message</label>
<textarea id="message" name="message" rows="5" required></textarea>
<button type="submit">Send Message</button>
</fieldset>
</form>
<details> and <summary>
Native accordion without JavaScript:
<details>
<summary>Click to expand FAQ</summary>
<p>This is the answer to the frequently asked question. It can contain any HTML content including code blocks and lists.</p>
</details>
<figure> and <figcaption>
<figure>
<img src="/images/grid-layout.png" alt="A three-column CSS Grid layout with sidebar">
<figcaption>Figure 1: CSS Grid three-column layout with named areas</figcaption>
</figure>
<kbd>, <code>, <samp>
<p>Press <kbd><kbd>Ctrl</kbd> + <kbd>S</kbd></kbd> to save</p>
<p>Run <code>npm run build</code> in the terminal</p>
<p>Output: <samp>Build complete in 3.2s</samp></p>
ARIA Roles and Accessibility
ARIA Roles and Accessibility
ARIA attributes supplement HTML semantics when native elements are insufficient.
Landmark Roles
<div role="banner"><!-- same as <header> at top level --></div>
<nav role="navigation" aria-label="Breadcrumb">
<ol>
<li><a href="/">Home</a></li>
<li><a href="/products">Products</a></li>
<li aria-current="page">Widget Pro</li>
</ol>
</nav>
<main role="main">
<h2 id="page-title">Product Details</h2>
<div aria-labelledby="page-title">
<!-- content referencing the heading -->
</div>
</main>
Live Regions for Dynamic Content
<!-- Announce updates to screen readers -->
<div role="status" aria-live="polite" aria-atomic="true">
<span id="cart-count">3 items in cart</span>
</div>
<!-- Error announcements -->
<div role="alert" aria-live="assertive">
Form submission failed. Please check the errors above.
</div>
Accessible Modal Dialog
<dialog id="confirm-dialog" aria-labelledby="dialog-title" aria-modal="true">
<h2 id="dialog-title">Confirm Deletion</h2>
<p>Are you sure you want to delete this item? This action cannot be undone.</p>
<form method="dialog">
<button value="cancel">Cancel</button>
<button value="confirm">Delete</button>
</form>
</dialog>
<script>
const dialog = document.getElementById('confirm-dialog');
document.getElementById('delete-btn').addEventListener('click', () => dialog.showModal());
</script>
Real-World Page Template
Real-World Page Template
Complete Semantic Structure
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="E-commerce product listing with filters and search">
<title>ShopHub - Products</title>
</head>
<body>
<a href="#main-content" class="skip-link">Skip to main content</a>
<header role="banner">
<nav aria-label="Primary">
<a href="/" aria-label="ShopHub Home">
<img src="/logo.svg" alt="ShopHub" width="120" height="40">
</a>
<ul>
<li><a href="/products" aria-current="page">Products</a></li>
<li><a href="/deals">Deals</a></li>
<li><a href="/cart" aria-label="Cart (2 items)">Cart (2)</a></li>
</ul>
</nav>
</header>
<main id="main-content">
<h1>Products</h1>
<section aria-label="Search and filters">
<form role="search">
<label for="search-input" class="sr-only">Search products</label>
<input type="search" id="search-input" placeholder="Search products..." aria-describedby="search-hint">
<span id="search-hint" class="sr-only">Type at least 3 characters</span>
</form>
</section>
<section aria-label="Product listing">
<h2 class="sr-only">Product results</h2>
<p aria-live="polite"><span id="result-count">24</span> products found</p>
<ul role="list">
<li>
<article class="product-card">
<img src="/products/widget.jpg" alt="Widget Pro - $29.99" loading="lazy" width="300" height="300">
<h3>Widget Pro</h3>
<p aria-label="Price: $29.99">$29.99</p>
<button aria-label="Add Widget Pro to cart">Add to Cart</button>
</article>
</li>
</ul>
</section>
</main>
<footer role="contentinfo">
<nav aria-label="Footer">
<ul><li><a href="/privacy">Privacy</a></li><li><a href="/terms">Terms</a></li></ul>
</nav>
</footer>
</body>
</html>
Key Patterns
- Skip link: Allows keyboard users to jump past navigation
aria-current="page": Marks the active page in navigationloading="lazy": Defers offscreen image loadingsr-onlyclass: Visually hidden but available to screen readersrole="search": Identifies search landmarks