The short version
Semantic markup means choosing HTML elements for their meaning. Use one <h1> for the title, <h2> and <h3> for sections, <article> around the post, <figure> with <figcaption> for images, and <time> for dates. Add JSON-LD BlogPosting data so search engines get an explicit description of the page.
What semantic markup does for SEO
Semantic markup means choosing each HTML element for what it means, not for how it looks. An <h1> is the title, a <nav> is navigation, and a <time> is a date. That tells browsers, search engines, and assistive technology such as screen readers how your page is put together. Semantic HTML is not a trick to rank higher. It does make a page easier to read and index, and that is the foundation of good SEO.
Every blog post starts with exactly one <h1>: the title of the article. Below it, use a logical order without skipping levels, with <h2> for main sections and <h3> for subsections. That makes the article easy to scan for readers and easy to parse for machines.
You can also add an id attribute to each <h2> and <h3>, usually a short version of the heading with hyphens instead of spaces. A table of contents can then link straight to that section with a fragment such as #name-of-the-id.
All the examples in this guide belong to one made-up blog post: “Starting your first vegetable garden” by the author Sam Example on example.com. That way you can see how the pieces form a single page.
The basic structure of a blog post
Here is the basic structure of that blog post with semantic markup:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Starting your first vegetable garden</title>
</head>
<body>
<header>
<a href="/">
<img src="/logo.svg" alt="Garden blog, go to the homepage" />
</a>
<nav aria-label="Main navigation">
<!-- aria-label instead of a heading: navigation doesn't belong in the content outline -->
<ul>
<li><a href="/">Home</a></li>
<li><a href="/blog">Blog</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
</header>
<main>
<article>
<h1>Starting your first vegetable garden</h1>
<h2>Choosing the right spot</h2>
<p>A paragraph about sun and shade.</p>
<h2>Preparing the soil</h2>
<p>A paragraph about digging and compost.</p>
<h3>Making your own compost</h3>
<p>A paragraph about a compost bin.</p>
<h2>What to sow in the first year</h2>
<p>A paragraph about easy vegetables.</p>
</article>
</main>
<footer>
<nav aria-label="Footer navigation">
<ul>
<li><a href="/privacy">Privacy</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
<p>© 2026 Garden blog</p>
</footer>
</body>
</html>Notice that the navigation does not need an <h2>. Headings describe content sections; an aria-label names the <nav> landmark instead. The heading hierarchy stays clean, while screen reader users can still tell one navigation area from another.
Landmarks: main, article, section, and aside
Landmarks tell browsers and screen readers what each part of a page is for. A blog post usually needs four of them.
Use <main> once per page, around the content that is unique to that page. The header, navigation, and footer stay outside it.
Use <article> for a self-contained post: something that would still make sense if you published it somewhere else. The title, the date, and the body all belong inside it.
Use <section> for a themed part of a page that has its own heading. If you can’t think of a heading for it, a <div> is usually the better choice.
Use <aside> for content that relates to the post but isn’t essential to it, such as an author box or a list of related articles.
Here is the same blog post twice: first built from generic <div> elements, then with semantic landmarks.
<!-- Before: every block is a div -->
<div class="content">
<div class="post">
<div class="title">Starting your first vegetable garden</div>
<div class="date">April 14, 2026</div>
<div class="text">A paragraph about sun and shade.</div>
</div>
<div class="sidebar">Related articles</div>
</div>
<!-- After: landmarks and headings carry the meaning -->
<main>
<article>
<h1>Starting your first vegetable garden</h1>
<time datetime="2026-04-14">April 14, 2026</time>
<p>A paragraph about sun and shade.</p>
</article>
<aside aria-label="Related articles">Related articles</aside>
</main>Both versions can look identical on screen. Only the second one tells a browser, a screen reader, or a crawler where the post starts and ends. On this site, the article itself sits in an <article>, and the author card and the related articles below it are <aside> elements. You can check that in the page source.
Images and their alt text
Images need the same care as the surrounding text. Use descriptive filenames and write alt text that explains the content or purpose of each informative image. Alt text is primarily an accessibility feature, though it can also give search engines useful context. For a purely decorative image, use an empty alt="" so screen readers skip it.
A <figcaption> provides a visible caption that connects the image to your article. The alt text describes the image for someone who cannot see it; the caption is visible to everyone. The two may overlap, but they do not need to repeat each other word for word.
Here is an image with a caption in HTML:
<figure>
<img
src="/blog/vegetable-garden-raised-beds.jpg"
alt="Three wooden raised beds with lettuce, carrots, and beans in a small city garden"
width="1200"
height="800"
/>
<figcaption>Raised beds help when your soil is heavy or contaminated.</figcaption>
</figure>Using <time> and <address>
Use the <time> element for a machine-readable publication or modification date. The <address> element is meant for the article author’s contact information, not for every postal address that happens to appear in the copy. Link to an author page with useful information about the author and their profiles.
Here is an example using <time> and <address>:
<p>
Published on <time datetime="2026-04-14">April 14, 2026</time>
</p>
<address>
Written by <a href="/authors/sam-example">Sam Example</a>.
Follow Sam on <a href="https://www.linkedin.com/in/sam-example/">LinkedIn</a>.
</address>Schema markup for blog posts
Schema markup gives search engines an explicit description of the page. This kind of structured data usually comes as JSON-LD in a script element. A blog post can use Article or its more specific BlogPosting type. Here is the BlogPosting markup for the garden example:
{
"@context": "https://schema.org",
"@type": "BlogPosting",
"headline": "Starting your first vegetable garden",
"url": "https://www.example.com/blog/first-vegetable-garden",
"image": [
"https://www.example.com/blog/vegetable-garden-raised-beds-1x1.jpg",
"https://www.example.com/blog/vegetable-garden-raised-beds-4x3.jpg",
"https://www.example.com/blog/vegetable-garden-raised-beds-16x9.jpg"
],
"datePublished": "2026-04-14T09:00:00+02:00",
"dateModified": "2026-06-02T14:30:00+02:00",
"author": {
"@type": "Person",
"name": "Sam Example",
"url": "https://www.example.com/authors/sam-example",
"sameAs": ["https://www.linkedin.com/in/sam-example/"]
},
"publisher": {
"@type": "Organization",
"name": "Garden blog",
"url": "https://www.example.com",
"logo": {
"@type": "ImageObject",
"url": "https://www.example.com/logo.png"
}
}
}Google stopped showing FAQ rich results on May 7, 2026 (see the Google Search documentation changelog). So don’t write FAQ markup that exists only for search engines. When the same FAQ is visible on the page, you can keep using FAQPage: machines can then read the questions and answers. We still implement it on this site, and our own SEO workflow keeps recommending it. Here is an example for the FAQ under the garden article:
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "How much sun does a vegetable garden need?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Most vegetables need at least six hours of direct sunlight a day. Leafy greens such as lettuce tolerate more shade."
}
},
{
"@type": "Question",
"name": "When should I sow in a new vegetable garden?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Only sow outdoors once the soil has warmed up, usually from mid-April. Tender crops such as beans wait until after mid-May."
}
}
]
}Always validate your markup after you paste it:
- Test the page or the code in Google’s Rich Results Test. It shows right away whether Google recognizes the supported structured data.
- Check the vocabulary with the Schema.org validator. It also lists types that Google doesn’t use for rich results.
- Look at the published page with URL inspection in Google Search Console. That shows how Google reads it.
As a quick local check, the JSON must pass JSON.parse without errors and without HTML entities such as " in the text. This article also appears as a BlogPosting in JSON-LD in the page source. Its table of contents is built from the same h2 headings you read, so the headings and their anchors come from one source. The FAQ below is the exact FAQ in the FAQPage markup.
Semantic markup and AI crawlers
AI crawlers read the same server-rendered HTML as search engines. Clear headings and landmarks make it easier to quote one passage from a page without the rest. Structured data describes the page, but it does not guarantee that an AI answer will cite it.
Conclusion
Correct semantic markup gives readers, assistive technology, and search engines a clearer article. Use headings in order and put the post in an <article>. Describe informative images with useful alt text and put dates in a <time> element. Then add valid schema markup that matches what is on the page.
For the editorial side, read the guide to formatting and structuring a blog post. MDN also has a useful reference on semantics in HTML.
Prefer to leave the implementation to a professional? Semantically correct HTML and valid schema come standard with every website we build. If you have questions about your own blog posts, get in touch.

