Using for buttons, broken outlines
⚠ Using for interactive elements breaks accessibility
Always use
THECODEFORGE.IO
thecodeforge.io
Semantic HTML: Structure, Accessibility & SEO
Semantic Html ExplainedThe Essential Semantic Elements and When to Use Them
Here are the semantic elements you'll reach for daily. Each has a specific purpose and usage rule.
<header>: Introductory content or navigational aids. Typically contains logo, site title, and primary navigation. Use once per page or inside <article>/<section>.<nav>: A section with navigation links. Major navigation blocks only — not every link group. Use aria-label when multiple <nav> elements exist.<main>: The dominant content of the document. Exactly one per page. Should not include content repeated across pages (sidebars, nav, copyright).<article>: A self-contained composition — blog post, news story, user comment. Ideally has its own heading. Can be nested.<section>: A thematic grouping of content. Always needs a heading (<h1>-<h6>). Used to break <article> or <main> into logical chunks.<aside>: Content indirectly related to the main content — sidebar, pull quote, glossary. Does not change the main document's meaning if removed.<footer>: Footer for its nearest sectioning root (page, article, section). Contains copyright, author info, related links.<figure> and <figcaption>: Self-contained content like images, diagrams, code blocks. The <figcaption> provides a caption. Better than <div> with class 'image-wrapper'.<time>: A machine-readable date or time. Use datetime attribute for precision. Important for calendar apps and search snippets.<address>: Contact information for the author or organization. Not for arbitrary postal addresses.
The golden rule: if you reach for a <div> with a class that describes structure (like 'nav', 'header', 'sidebar'), stop and use the semantic element instead.
io/thecodeforge/semantic/blog-article-example.htmlHTML 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<article>
<header>
<h1>How to Build Accessible Forms</h1>
<time datetime="2026-03-15">March 15, 2026</time>
<address>By Jane Doe, <a href="mailto:jane@example.com">jane@example.com</a></address>
</header>
<section aria-labelledby="intro-heading">
<h2 id="intro-heading">Introduction</h2>
<p>Forms are the most common interactive element on the web...</p>
</section>
<section>
<h2>Labeling Inputs</h2>
<p>Every input needs a <code><label></code>...</p>
<figure>
<figcaption>Example of a labeled input</figcaption>
<pre><code><label for="email">Email address</label>
<input type="email" id="email" /></code></pre>
</figure>
</section>
<footer>
<p>Tags: <a href="/tags/accessibility">accessibility</a>, <a href="/tags/forms">forms</a></p>
</footer>
</article>Don't Memorize — Recognise
You don't need to memorize every HTML5 element. The instinct to ask 'is there a semantic element for this?' is the skill. When in doubt, check MDN's HTML elements reference. Over time, the pattern becomes automatic.
Production Insight
Using <section> without a heading triggers an accessibility warning in most audit tools.
WCAG SC 1.3.1 requires section headings to convey structure.
Rule: every <section> must contain at least one heading element.
Key Takeaway
Map each content type to its semantic element.
<main> once, <nav> for major navigation, <article> for self-contained content.
If it has a heading and is a distinct block, it's probably <section>.
Accessibility: Why Screen Readers Depend on Semantic HTML
Screen readers like NVDA and VoiceOver use the accessibility tree — not the DOM directly. The accessibility tree is built from elements that have a semantic role. A <div> with class 'button' has no role unless you add role="button". But a <button> element already has the 'button' role, plus keyboard interaction expectations (Enter/Space to activate, focus styles).
Semantic elements come with built-in ARIA roles, keyboard handling, and browser defaults. For example: - <nav> automatically gets role='navigation' and is listed in landmarks. - <main> gets role='main' and screen readers offer a 'skip to main content' shortcut. - <h1>-<h6> automatically get heading roles at appropriate levels. - <table> automatically has grid role with row/column semantics.
When you overwrite these with <div> plus ARIA, you often introduce bugs: missing ARIA properties, misaligned focus management, or inconsistent behaviour across browsers. Semantic HTML is more robust because browsers have tested the native elements for decades.
Use ARIA only when a native semantic element does not exist — for example, a progress bar (role="progressbar") or a tab interface (role="tab").
io/thecodeforge/semantic/accessibility-test.jsJAVASCRIPT // Quick test to check if your page has semantic landmarks
(function checkLandmarks() {
const landmarks = [
'header', 'nav', 'main', 'aside', 'footer',
'[role="banner"]', '[role="navigation"]', '[role="main"]',
'[role="complementary"]', '[role="contentinfo"]'
];
const present = landmarks.filter(sel => document.querySelector(sel)).length;
if (present < 3) {
console.warn('Your page has fewer than 3 semantic landmarks. This will confuse screen readers.');
} else {
console.log(`Good: ${present} semantic landmarks found.`);
}
})();Output
Good: 5 semantic landmarks found.
ARIA Is Not a Free Pass
Adding role="button" to a <div> does not make it a real button. You still need to:
- Add tabindex="0" to make it keyboard focusable.
- Add role="button" to announce it as a button.
- Handle Enter and Space keydown events.
- Manage focus styling.
- Avoid it altogether if a <button> element works.
Production Insight
A news site replaced all <article> elements with <div> to work around a CMS bug.
Screen reader users lost the ability to navigate between articles by landmark.
Impact: 15% drop in page views per session from assistive tech users.
Rule: never strip semantic elements for visual or CMS convenience — find another fix.
Key Takeaway
Semantic HTML is the backbone of web accessibility.
Screen readers rely on roles, landmarks, and heading hierarchy.
ARIA is a supplement, never a replacement for native elements.
SEO: How Search Engines Interpret Semantic Markup
Google, Bing, and other search engines parse HTML structure to understand content hierarchy and relevance. Semantic HTML directly influences rich snippets, featured snippets, and page ranking.
<article> tells Google this is a self-contained piece of content — often used for blog posts in Google News and Discover.<nav> signals the primary navigation, helping Google understand site structure for sitelinks.<header> and <footer> are used for page-level metadata extraction.- Proper heading hierarchy (
<h1>-<h6>) is one of the strongest on-page SEO signals. Search engines use it to infer the main topic and subtopics. An <h1> is the page title equivalent. <time> with datetime helps extract publication dates for news search and freshness signals.<figure> and <figcaption> help associate images with descriptions, improving image search ranking.
A common mistake: using multiple <h1> elements or skipping heading levels. Google's algorithm devalues content that doesn't follow a logical outline.
Semantic HTML also improves Core Web Vitals indirectly — cleaner markup means smaller DOM size, faster parsing, and better performance scores.
io/thecodeforge/semantic/recipe-seo.htmlHTML 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<article itemscope itemtype="https://schema.org/Recipe">
<h1 itemprop="name">Classic Margherita Pizza</h1>
<time datetime="2026-04-10" itemprop="datePublished">April 10, 2026</time>
<p itemprop="description">A simple, authentic Neapolitan pizza...</p>
<section>
<h2>Ingredients</h2>
<ul>
<li itemprop="recipeIngredient">500g pizza dough</li>
<li itemprop="recipeIngredient">200g San Marzano tomatoes</li>
</ul>
</section>
<section>
<h2>Instructions</h2>
<ol itemprop="recipeInstructions">
<li>Preheat oven to 250°C...</li>
</ol>
</section>
<figure itemprop="image">
<img src="pizza.jpg" alt="A freshly baked Margherita pizza" />
<figcaption>Final result</figcaption>
</figure>
</article>Structured Data Amplifies Semantic HTML
Adding schema.org markup (JSON-LD or microdata) on top of semantic HTML gives search engines even richer context. For example, marking up an <article> with itemscope itemtype="https://schema.org/Article" can trigger rich snippets with author, date, and image.
Production Insight
An e-commerce site had 12 <h1> tags on the homepage (product grids rendered with heading classes).
Google's algorithm saw no clear primary topic, and the homepage dropped from rank 3 to 11 for brand keywords.
Fix: Only one <h1> per page, moved other titles to <h2>.
Rule: your page should read like a well-structured book, not a shouting match.
Key Takeaway
Search engines use semantic HTML as a content map.
One <h1>, logical heading depth, <article> for standalone content.
Ranking correlates with semantic structure quality.
Common Semantic HTML Mistakes and How to Fix Them
Even experienced developers make these errors. Here are the most frequent ones:
- Using
<br> for line breaks inside a paragraph — should use separate <p> elements or CSS display block. - Using
<b> and <i> instead of <strong> and <em> — they look the same but convey no emphasis meaning. - Using
<div class='nav'> instead of <nav> — you lose the navigation landmark. - Multiple
<main> elements — only one allowed per document. - Nesting
<section> inside <aside> incorrectly — sections should be for thematic grouping, not general containers. - Omitting alt text on
<img> — not exactly a semantic element issue, but related: every <img> must have an alt attribute conveying its function. - Using
<blockquote> for indentation — <blockquote> is for quoted content, not visual styling. Use CSS padding or margin. - Using
<div> for interactive elements — always prefer <button>, <a>, <input>, <select>.
io/thecodeforge/semantic/common-fixes.htmlHTML 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!-- BEFORE: Non-semantic mess -->
<div class="article">
<div class="title">Tips for Java 25</div>
<div class="meta">Posted on 15 March 2026</div>
<div class="content">
<div class="section">
<span class="heading">Performance</span>
<span class="text">New GC algorithms...<br>Test with ZGC</span>
</div>
</div>
</div>
<!-- AFTER: Semantic structure -->
<article>
<h1>Tips for Java 25</h1>
<p><time datetime="2026-03-15">Posted on 15 March 2026</time></p>
<section>
<h2>Performance</h2>
<p>New GC algorithms...</p>
<p>Test with ZGC</p>
</section>
</article>The 'Find and Replace' Test
- If your page still makes structural sense, you likely already used adequate semantics.
- If it breaks — e.g., a <section> becomes an <article> when it's not self-contained — you misused a semantic element.
- The test shows whether your markup reflects content meaning, not just visual layout.
- A good semantic structure survives such a rename without logical errors.
Production Insight
A developer used <article> for every product card on a listing page (50+ on one page).
This violated the spec (each should have own heading, not just a price) and broke screen reader navigation between pages.
Fix: use <section> for card lists and <article> only for individual product detail pages.
Rule: not every repeating block is an article — think newspapers, not index cards.
Key Takeaway
Common mistakes stem from treating HTML as a styling tool, not a semantic one.
Choose elements by meaning, not by default appearance.
When in doubt, ask: 'What does this content represent?'
Why Your Document Outline Is Probably Broken
You've carefully nested your <article> inside <section> inside <main>. Looks clean. But run it through an outline checker and your entire hierarchy collapses into flat text. That's because screen readers and browsers build their navigation from heading levels - not from <section> or <article> tags. They respect <h1> through <h6> as the only signal for content importance.
A common mistake: using <section> as a generic wrapper and skipping heading levels because 'it looks right.' But when a blind user tabs through your page, they hear 'heading level 3' followed by 'heading level 1' - which tells them your content structure is broken. The fix is brutal simplicity: maintain a strict heading hierarchy. Start every document with one <h1>. Nest <h2> under it, then <h3> under that. Never use a heading just for styling; that's what CSS exists for.
I've seen production incidents where a last-minute design change added an <h2> between existing <h1> and <h3> elements - and nobody noticed until the accessibility audit flagged it. The fix took 30 seconds and prevented a lawsuit.
semantic-outline.htmlHTML 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// io.thecodeforge
<!-- Good outline -->
<main>
<h1>Product Documentation</h1>
<section>
<h2>Installation Guide</h2>
<p>Steps below assume macOS Sequoia.</p>
<article>
<h3>Quick Start</h3>
<p>Run the installer, accept defaults.</p>
</article>
</section>
</main>
<!-- Bad outline - flat hierarchy -->
<main>
<section>
<h2>Product Documentation</h2>
</section>
<article>
<h2>Installation Guide</h2>
</article>
</main>Output
Accessibility tree: Heading level 2 -> Heading level 2 (flat, no relationship)
Production Trap:
Automated outline checkers are cheap. Run one in your CI pipeline. Flag any PR that breaks heading hierarchy. Your accessibility team will buy you coffee.
Key Takeaway
Your <section> and <article> tags are ignored by screen readers. Only correct heading hierarchy builds a navigable document outline.
Every team I've joined eventually has the debate: 'Should we wrap this chart in a <div> or a <figure>?' The answer is always <figure> - but not for the reason most think. The <figure> element is a semantic container for content that is referenced from the main flow but could be moved without breaking the narrative. That includes images, sure, but also code blocks, pull quotes, diagrams, and even tables.
Paired with <figcaption>, you tell the browser: 'This block is a self-contained unit with a label.' Screen readers announce both the content and its caption together. Search engines treat the caption as metadata for the enclosed content. The practical upshot: when a user copies your chart into a report, the caption follows automatically.
I once debugged a production issue where an interactive D3 chart was wrapped in a <div> with an aria-label. The label worked for screen readers, but the chart's SVG metadata was invisible to search engines. Moving it to <figure> with a <figcaption> solved both problems with zero code changes to the chart itself.
figure-example.htmlHTML 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// io.thecodeforge
<article>
<h2>2024 Revenue Breakdown</h2>
<p>Subscription revenue grew 12% YoY, driven by enterprise tiers.</p>
<figure>
<figcaption>Figure 3: Quarterly Revenue by Segment (2024)</figcaption>
<table>
<thead>
<tr><th>Q</th><th>Subscriptions</th><th>Services</th></tr>
</thead>
<tbody>
<tr><td>Q1</td><td>$2.4M</td><td>$1.1M</td></tr>
<tr><td>Q2</td><td>$2.7M</td><td>$1.3M</td></tr>
</tbody>
</table>
</figure>
<p>Enterprise subscriptions now account for 65% of recurring revenue.</p>
</article>Output
Screen reader: 'Figure 3: Quarterly Revenue by Segment (2024). Table with 2 columns and 3 rows.'
Senior Engineer Note:
Always put the <figcaption> first or last inside <figure>. If you put it in the middle, some screen readers won't associate it with the figure.
Key Takeaway
Use <figure> for any self-contained content that needs a caption - images, tables, code blocks, charts. It improves both accessibility and SEO in one element.
Details and Summary: The Collapsible You've Been Spamming
You've written that custom collapsible component three times this month. Each time: a button, a div with display:none, some JavaScript to toggle a class, and an ARIA attribute to make it work. Stop. HTML5 gives you <details> and <summary> - native, accessible, zero-JS collapsible sections.
<details> creates the wrapper. <summary> provides the visible label. When clicked, the <details> toggles an 'open' attribute that the browser uses to show or hide the content. Screen readers get the announcement for free. Search engines index the content regardless of whether it's collapsed - because the DOM always contains the full content.
I've seen a production outage caused by a custom accordion that failed to render on mobile Safari. The fix: replace 150 lines of custom JS with a <details> element. It worked everywhere, instantly. The only caveat: you cannot animate the open/close transition natively (yet). If your designer insists on a slide animation, use CSS grid with grid-template-rows: 0fr to 1fr - no JavaScript needed.
details-accordion.htmlHTML 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// io.thecodeforge
<details>
<summary>Why semantic HTML matters for performance</summary>
<div class="content">
<p>Semantic elements reduce DOM complexity by eliminating wrapper divs. Browser rendering engines parse them faster because they convey structural intent directly.</p>
<ul>
<li>Less CSS specificity battles</li>
<li>Smaller bundle sizes</li>
<li>Faster paint times</li>
</ul>
</div>
</details>
<style>
details[open] .content {
animation: fadeIn 0.3s ease-in;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
</style>Output
Browser renders a clickable disclosure widget. No JavaScript needed. Screen reader announces 'collapsed' or 'expanded' state automatically.
Production Trap:
You cannot nest <details> inside <summary>. The browser will push the inner <details> out of the flow. It looks broken on every browser. Test your accordion nesting early.
Key Takeaway
Replace custom collapsible components with <details>/<summary>. Native accessibility, zero JavaScript, indexed by search engines, works everywhere.