This article is published in English.
From Div Soup to Meaningful Markup: A Practical Semantic HTML Guide
Learn why generic divs break accessibility, crawling and maintainability, which semantic elements to use instead, and how to refactor a real card component.
Open the element inspector on a typical modern web app and you will often find the same picture: a deep, anonymous stack of <div> elements, each identified only by a class name. The page may look perfect, yet to screen readers, search crawlers and the next developer on the team it says almost nothing about what the content is. This guide explains what that costs you, which semantic elements replace most of those divs, how to settle the confusing choices (<article> or <section>, link or button), and how to refactor a realistic component step by step.
Here is the pattern in its purest form: a header, navigation, main content and footer, all expressed as generic boxes.
<!-- The modern web's favorite anti-pattern -->
<div class="header">
<div class="nav-container">
<div class="nav-item">Home</div>
<div class="nav-item">About</div>
</div>
</div>
<div class="main-content">
<div class="article-title">Stop Using Divs</div>
<div class="article-body">
<div class="paragraph">Divs everywhere.</div>
</div>
</div>
<div class="footer">
<div class="copyright">© 2026</div>
</div>
Every piece of structure in that snippet lives in class names. Remove the classes and nothing is left to tell a machine, or a person, where navigation ends and the article begins. This style is usually called "div soup", and it is far more common than it should be.
Why HTML Turned Into a Styling Scaffold
As frameworks such as React, Vue, Angular and Svelte came to dominate frontend work, and as utility-first CSS made styling almost effortless, markup quietly lost its original job. HTML started to function as a neutral frame for hanging classes and event handlers on, rather than a language for describing content. That is a misunderstanding of the platform: HTML was designed to express meaning, not to be a layout engine.
When that meaning disappears, the damage is spread across several areas at once. Assistive technology loses its navigation aids, search engines get a weaker signal about what matters on the page, the browser can no longer provide built-in behavior, and the codebase becomes harder for colleagues to read. None of these problems are visible in a screenshot, which is exactly why they persist.
Component roots default to div
Component architecture splits the interface into small pieces like <Button/>, <Card/>, <Navbar/> and <Sidebar/>. Each component needs a root element for its JSX, and the path of least resistance is a <div>. Nest a handful of components inside each other and the rendered DOM becomes ten layers of anonymous wrappers, even though every individual component looked reasonable on its own. (If you work in React, it is worth remembering that a fragment removes the need for a wrapper entirely when no box is required.)
Utility classes shift attention to appearance
Utility-first CSS is excellent for speed, but it puts all the attention on how an element looks. When the markup reads <div class="flex items-center space-x-4">, the question being answered is "how is this arranged?", and the question "what is this?" never gets asked. The element choice becomes an afterthought.
Pixels are only the top layer
The most dangerous habit is judging your work by what a mouse user sees on a large monitor. A <div> with a blue background, rounded corners and bold text certainly looks like a button. But below the rendered pixels there is the DOM, the accessibility tree the browser exposes to assistive technology, the crawler's view of the document and the browser's input handling. To every one of those layers, the styled div is still just a div.
What Semantic HTML Actually Means
"Semantic" refers to meaning. Semantic HTML is the practice of choosing elements whose names describe the role of the content they contain, so that the browser, assistive technology and human readers can all understand the document without guessing.
Generic containers
Two elements are intentionally meaningless:
<div>is a generic block-level grouping.<span>is a generic inline grouping.
Text inside a <div> could be a page title, a navigation link, a paragraph of a blog post, a sidebar note or a legal disclaimer. The browser has no way to tell, so it treats all of them identically.
Elements that carry meaning
Semantic elements state what their content is:
<header>holds introductory content for the page or a section, often including navigation.<nav>marks a block of major navigation links.<main>wraps the primary, unique content of the page.<article>represents a self-contained composition such as a post or a news item.<section>groups content around a single theme.<aside>contains material only indirectly related to the main content, such as a sidebar.<footer>holds closing information like authorship, copyright or legal links.<button>is an interactive control that performs an action.
When the browser meets an <article>, it knows the contents could stand on their own. When it meets a <nav>, it knows those links are how people move around the site. That knowledge is what the rest of this guide builds on.
Four Reasons Semantic Markup Is Worth the Effort
Picking the right element takes a moment of thought, and a styled div renders the same pixels. The payoff shows up in four places.
Accessibility: landmarks and native controls
Many people use the web with screen readers such as NVDA, JAWS or VoiceOver, or navigate entirely with the keyboard. Screen readers ignore your CSS. They work from the accessibility tree, which the browser derives from your HTML.
With well-structured markup, the browser can expose landmarks, and assistive technology can present them as a list the user jumps between. For a semantic page, that list looks roughly like this:
Landmark Navigation Map:
- Header
- Navigation (3 links)
- Main Content
- Heading 1: Stop Using Divs
- Article
- Sidebar (Aside)
- Footer
A user can press a single shortcut to skip dozens of navigation links and land directly in <main>, or move straight to the article. Now compare the same page built only from divs:
Landmark Navigation Map:
- Division
- Division
- Division
- Division
In practice it is even worse than this sketch suggests: plain divs are not landmarks at all, so the list is simply empty. The user has to move through the page element by element to find what they came for.
Buttons show the same problem at a smaller scale. Here is a fake button next to a real one:
<!-- BAD: Fake Button -->
<div class="my-button" onclick="submitForm()">Submit</div>
<!-- GOOD: Native Button -->
<button type="submit">Submit</button>
The native <button> includes, at no cost:
- Focusability, so it appears in the Tab order.
- Activation with both Enter and Space.
- A correct role, so a screen reader announces something like "Submit, button" and the user knows it performs an action.
The div version cannot receive focus, does not respond to the keyboard and is read out as plain text with no hint that it is interactive. To make it behave properly you would need tabindex="0", role="button", key handlers for both Enter and Space, focus styling and disabled-state handling. That is a lot of code to reproduce, usually imperfectly, something the platform already ships.
Search engines: a clearer picture of the page
Crawlers such as Googlebot are, in a sense, specialized text readers that try to work out what each page is about. Semantic structure gives them useful hints:
- The
<h1>points to the primary topic. <main>separates the unique content from the header and footer repeated on every page.<article>signals a distinct piece of editorial content.<nav>exposes the internal linking structure.
A page made of undifferentiated divs forces the crawler to infer all of this from layout and text alone. Be realistic about the size of the effect, though: markup is one input among many, and search engines do not publish a rule that semantic pages outrank others. Treat clean structure as something that makes your content easier to parse and index correctly, not as a ranking guarantee.
Maintainability: markup that documents itself
Code is communication with other developers and with yourself months later. Consider two versions of the same layout. The first relies entirely on class names:
<div class="top-bar">
<div class="logo-box">...</div>
<div class="menu-list">
<div class="menu-item"><a href="#">Home</a></div>
<div class="menu-item"><a href="#">Blog</a></div>
</div>
</div>
<div class="wrapper">
<div class="content-box">
<div class="title-text">My Post</div>
<div class="body-text">Hello world...</div>
</div>
<div class="right-bar">
<div class="widget">...</div>
</div>
</div>
<div class="bottom-bar">
<div class="legal">© 2026</div>
</div>
The second expresses the same structure with semantic elements:
<header>
<div class="logo">...</div>
<nav>
<ul>
<li><a href="#">Home</a></li>
<li><a href="#">Blog</a></li>
</ul>
</nav>
</header>
<main>
<article>
<h1>My Post</h1>
<p>Hello world...</p>
</article>
<aside>
<div class="widget">...</div>
</aside>
</main>
<footer>
<small>© 2026</small>
</footer>
In the first version you have to read each class name and reconstruct the intent. If someone left out or misnamed a class, the structure becomes a wall of identical boxes. In the second, the shape of the page is visible at a glance: where it starts (<header>), where the core content lives (<main>), what is supplementary (<aside>) and where it ends (<footer>). Notice that the navigation also became a real list, which lets screen readers announce how many items it contains.
Self-describing markup lowers the mental load during reviews and reduces the chance of breaking something when a layout changes.
Performance and built-in browser behavior
Browser engines such as Chromium, Gecko and WebKit are tuned for standard elements, which come with default styles, event handling and state management already implemented. Form controls are the clearest example. <input type="email"> and <input type="date"> give mobile users an appropriate on-screen keyboard or a native date picker, and <details> provides a working disclosure widget, all without any third-party JavaScript.
Each time you rebuild a dropdown or a button from a div plus scripts, you send extra code over the network, increase the bundle, spend more of the device's CPU and battery, and create another component you now have to maintain and test. For a broader tour of this idea, see six native HTML features that replace common JavaScript UI libraries.
Resolving the Tricky Semantic Choices
Knowing the element list is the easy part. The real confusion comes from a few pairs that seem interchangeable.
article or section
This is the question developers argue about most. A useful test: could this content be lifted out of the page, dropped onto a different site, and still make complete sense? If yes, use <article>. If it only makes sense in its current context, use <section>.
Good candidates for <article>:
- A blog post or news story.
- A single user comment.
- A product card in a shop grid.
- A self-contained widget, such as a live weather panel.
Each of these could be syndicated, included in a feed or embedded elsewhere without the surrounding page.
Good candidates for <section>:
- The "About us" block of a homepage.
- A chapter within a digital book.
- The features area of a landing page.
- A group of testimonials.
A <section> groups content under one theme, so it should nearly always begin with a heading from <h2> to <h6>. If you cannot think of a sensible heading for it, it is probably not a section, and a <div> may be the honest choice.
The two nest naturally. An article can be split into sections, each with its own heading:
<!-- Proper Nesting Example -->
<main>
<!-- Main article describing a topic -->
<article>
<h1>Understanding Semantic HTML</h1>
<p>Introductory paragraph...</p>
<!-- Sections dividing the article into sub-topics -->
<section>
<h2>Why Accessibility Matters</h2>
<p>Content about accessibility...</p>
</section>
<section>
<h2>SEO Benefits</h2>
<p>Content about SEO...</p>
</section>
</article>
</main>
Link or button
Confusing links and buttons is one of the most frequent accessibility bugs. The rule is short:
- Use
<a>with a realhrefwhen activating it changes the URL or takes the user to another page or location. - Use
<button>when activating it does something on the current page: submits data, opens a modal, toggles a menu or otherwise changes state.
Both kinds of mistake are common, as are their fixes:
<!-- WRONG: A link styled like a button that triggers JavaScript -->
<a href="#" onclick="openModal()">Open Modal</a>
<!-- WRONG: A button that navigates to a new webpage -->
<button onclick="window.location.href='/about'">About Us</button>
<!-- RIGHT -->
<button type="button" onclick="openModal()">Open Modal</button>
<a href="/about">About Us</a>
The distinction matters because screen readers announce the role. "Link" sets the expectation that the location will change; "button" suggests something will happen here. Getting it wrong breaks that expectation. There are practical side effects too: a link with href="#" can jump the page to the top and cannot be activated with Space, and a button that navigates cannot be opened in a new tab or have its address copied.
Building a sensible heading outline
Headings form the table of contents of your page, and both assistive technology and crawlers rely on them. Screen reader users frequently navigate by heading alone. A few rules keep the outline useful:
- Use one
<h1>for the main subject of the page, such as the article title or the name of the dashboard. The HTML specification does not strictly forbid several, but a single top-level heading is the widely recommended convention and gives the clearest outline. - Do not skip levels. Going from
<h2>straight to<h4>because you want smaller text is a styling decision disguised as structure; change the font size with CSS instead. - Nest logically: under the
<h1>title come<h2>major sections, each with its own<h3>subsections, followed by the next<h2>.
Also note that the <header> element and heading elements are different things. A <header> is a region of the page; <h1> to <h6> are the titles that build the outline.
When a div Is the Right Tool
None of this means <div> is forbidden. It has a legitimate job: wrapping content purely for layout or styling when no semantic meaning applies. Centering something with flexbox, creating a grid gap, painting a gradient background or hooking up a CSS animation are all perfectly good reasons to reach for a <div> (or a <span> for inline content).
In the following card, the meaningful parts use semantic elements, while a single div exists only to lay out two buttons side by side:
<!-- PERFECTLY VALID USE OF A DIV -->
<article>
<h1>Card Title</h1>
<p>Card description goes here...</p>
<!-- This div exists purely to align two buttons side-by-side with Flexbox -->
<div class="button-group flex gap-4 mt-4">
<button type="button">Save</button>
<button type="button">Cancel</button>
</div>
</article>
The <article>, <h1>, <p> and <button> elements describe the content; the <div> is purely a layout layer. That is the whole principle in one sentence: use semantic elements for meaning and divs for layout. (In a real page where this card sits inside a larger document, its title would typically be an <h2> or <h3> to fit the page outline.)
Refactoring a Blog Post Card, Step by Step
Consider a card component of the kind found in almost every content site: an image, a label, a title, author and date, an excerpt and two actions. Here is the div soup version:
<div class="card">
<div class="card-image">
<img src="tech.jpg" alt="Technology background" />
<div class="badge">Article</div>
</div>
<div class="card-content">
<div class="post-title" onclick="goToPost()">10 Tips for Better Code</div>
<div class="post-author">By Jane Doe</div>
<div class="post-date">August 12, 2026</div>
<div class="post-excerpt">
Learn how to write cleaner, more maintainable frontend code today...
</div>
<div class="card-footer">
<div class="share-btn" onclick="sharePost()">Share</div>
<div class="read-more" onclick="goToPost()">Read More</div>
</div>
</div>
</div>
The problems are easy to list once you look for them:
- The outer wrapper is a
<div>although the card is a self-contained piece of content, which is exactly what<article>is for. - The title is a clickable div, so keyboard users cannot reach it and screen readers do not know it is either a heading or a link.
- Author and date sit in generic containers with no machine-readable meaning.
- "Share" and "Read More" are fake buttons with the keyboard and announcement problems described earlier.
Now the rewritten version:
<article class="card">
<figure class="card-image">
<img src="tech.jpg" alt="Abstract technology grid pattern" />
<span class="badge">Article</span>
</figure>
<div class="card-content">
<h2>
<a href="/posts/10-tips-for-better-code">10 Tips for Better Code</a>
</h2>
<p class="meta-info">
Written by <span class="author">Jane Doe</span> on
<time datetime="2026-08-12">August 12, 2026</time>
</p>
<p class="post-excerpt">
Learn how to write cleaner, more maintainable frontend code today...
</p>
<footer class="card-footer">
<button type="button" onclick="sharePost()" aria-label="Share this article">
Share
</button>
<a href="/posts/10-tips-for-better-code" class="btn-primary">
Read More
</a>
</footer>
</div>
</article>
What changed and why it helps:
- The
<article>wrapper tells assistive technology and crawlers that the card is a standalone item. - The image and its badge are grouped in a
<figure>, and the alt text now describes the image instead of labeling it generically. - The title is a real
<h2>containing a real link, so it appears in the heading outline, can be reached with Tab and exposes its destination to crawlers. - The date uses
<time datetime="2026-08-12">, giving scrapers, translation tools and calendar integrations an unambiguous machine-readable value regardless of how the visible text is formatted. - "Share" is a
<button>because it acts on the current page, while "Read More" is an<a>because it navigates. - The
aria-labelon the share button gives screen reader users more context about what will be shared. Keep the visible text inside the label, as it is here ("Share" is part of "Share this article"), so voice-control users can still activate it by saying what they see.
One refinement worth considering: the card now contains two links to the same URL. Some teams keep only the title link, or make the whole card clickable through that single link, so that keyboard and screen reader users do not encounter the same destination twice.
Habits That Keep Your Markup Semantic
You do not need to relearn web development to write better HTML. A handful of routine checks catch most problems.
Run an automated audit
Browser extensions such as axe DevTools or WAVE, available for Chrome and Firefox, scan a page in seconds and flag issues like invalid ARIA attributes, missing button types, broken heading order and interactive elements without proper semantics. Automated tools only catch part of what matters, so treat a clean report as a starting point rather than proof of accessibility.
Try the page without CSS
Disable all styles, either through the developer tools or an extension that turns off CSS, and look at what remains. Is it still a readable document? Can you distinguish headings from paragraphs and find the navigation? If the result is an undifferentiated block of text, the structure depends on CSS for its meaning. A well-structured document stays organized even with no styles at all.
Put the mouse away
Try operating your application with only Tab, Shift + Tab, Enter, Space and the arrow keys, and check:
- Whether every interactive element can be reached.
- Whether you can always see which element has focus.
- Whether forms, dropdowns and modals all work.
Whenever you get stuck on a div that ignores Enter or Space, replace it with a <button>. It is often one of the quickest accessibility fixes available.
Ask what the content is before typing div
Before writing a new container, pause and ask what its content actually represents:
- Navigation calls for
<nav>. - A sidebar calls for
<aside>. - A clickable action calls for
<button>. - An independent card or post calls for
<article>. - The primary content of the page calls for
<main>. - Only a layout wrapper with no meaning of its own calls for
<div>.
The same applies inside components: the root element of a React component is part of the final document, so choose it with the same care. For more on how JSX and real HTML differ, see JSX is not HTML: the real trade-offs behind component markup.
Key Takeaways
- Rendered pixels are only one layer; the accessibility tree, crawlers and browser input handling all read your elements, not your styles.
- Native elements such as
<button>,<a>,<input>and<details>bring focus, keyboard support, roles and mobile behavior that are costly to rebuild by hand. - Use
<article>for content that stands on its own and<section>for a themed part of a larger whole, and give every section a heading. - Links navigate, buttons act; mixing them confuses users and breaks expected browser behavior.
- Keep one clear heading outline without skipped levels, and use CSS rather than heading levels to control size.
- Divs remain the right choice for pure layout. Frameworks change every few years, but this foundation has held for decades, and markup that states its meaning pays off for users, search engines and your team alike.