Div soup, good intentions paving the road to digital hell
Table of contents
The invisible symptom
Open the developer tools on any website. Expand the
DOM. Count the <div>s.
On an average site, you'll find hundreds. Not necessarily replacing semantic
elements — most projects use a <header>, a few <button>s, a <form>. The
problem is more insidious: it's the <div>s that pile up around them. Three
levels of wrappers to centre a button. A <div class="content-wrapper">
doubling a <main> that's already there. A <div class="form-group"> where a
<fieldset> would do. <div>s nested inside <div>s nested inside <div>s,
carrying CSS classes that the semantic elements could have carried themselves.
Visually, everything works. The colours are there, the layout holds, the animations run. The client is happy. The sighted user notices nothing.
But the <div> says nothing. It's an empty container — no role, no semantics,
no meaning. To a screen reader, a <div> doesn't exist. To a search engine,
it's noise. To the browser, it's one more node to build, to paint, to recycle.
This article details what every unnecessary <div> costs — in accessibility,
in performance, in SEO, in GEO. And it shows which HTML elements already exist to do the
job.
The HTML that already exists
HTML has over 100 elements. Most
projects use about ten — and wrap each one in <div>s that add nothing. Here
are the elements that <div> most often replaces or smothers — and what is lost
each time.
Page structure
| Instead of… | Use… | What you gain |
|---|---|---|
<div class="header"> | <header> | The screen reader jumps straight to the header |
<div class="nav"> | <nav> | Identifiable navigation, listed automatically |
<div class="main"> | <main> | Main content, one per page |
<div class="footer"> | <footer> | Footer information |
<div class="sidebar"> | <aside> | Complementary content that can be removed without losing information |
<div class="section"> | <section> | Thematic region (a landmark only if it has an accessible name) |
<div class="article"> | <article> | Self-contained, redistributable content |
Most of these elements create landmarks, waypoints that
the screen reader announces and the user can reach with a single keyboard
shortcut. Three nuances: <header> and <footer> are only landmarks at page
level (not nested inside an <article> or a <section>), <section> only
becomes one if it carries an accessible name (aria-label or
aria-labelledby), and <article> isn't one at all — its value lies elsewhere:
it delimits self-contained content that assistive technologies know how to
announce. Every <div> that replaces one removes a waypoint. And every <div> that wraps one adds an
empty node between the user and the information.
Interactive elements
| Instead of… | Use… | What you gain |
|---|---|---|
<div onclick="..."> | <button> | Keyboard focus, announced role, native Enter + Space |
<div class="link"> | <a href="..."> | Keyboard focus, announced role, browser history |
<div class="input"> | <input> | Native validation, autocomplete, specialised types |
<div class="select"> | <select> | Native dropdown, keyboard support, mobile optimised |
A <div onclick> doesn't receive keyboard focus. It
isn't announced as a button. It doesn't respond to Enter or Space. To make it
functional, you need role="button", tabindex="0", a keydown handler, and a
focus style — exactly the kind of
ARIA tinkering best avoided. Four
attributes and JavaScript to reinvent what <button> does in six characters.
The forgotten elements
| Element | What it does | What you find instead |
|---|---|---|
<details> / <summary> | Native accordion, no JS needed | A <div> + JS + ARIA |
<dialog> | Native modal, built-in focus trap | A <div> + overlay + JS + ARIA |
<figure> / <figcaption> | Image or block with associated caption | A <div> + a disconnected <p> |
<time> | Machine-readable date/time | A <span> with no semantics |
<address> | Contact information | A <div> or a <p> |
<mark> | Highlighted text (search result) | A <span> with CSS |
<fieldset> / <legend> | Field group with label | Nested <div>s |
<blockquote> / <cite> | Quote with attribution | An italicised <div> |
<abbr> | Abbreviation with full title | Nothing, or a <span> with a title |
<code> / <pre> | Source code | A <div> with a monospace font |
<dl> / <dt> / <dd> | Definition list, glossary, key-value summary | <div>s with <span>s |
Each of these elements carries semantics that the browser, screen reader, search
engine and LLM exploit. Every <div>
that replaces them erases those semantics.
Anatomy of a soup
Take a typical page: a header, navigation, content with a heading, a footer. The semantic HTML is there — but buried.
<!-- What most codebases actually look like -->
<div class="page-wrapper">
<div class="top-container">
<header>
<div class="header-inner">
<div class="logo-wrapper">
<a href="/">Acme</a>
</div>
<div class="nav-wrapper">
<nav>
<div class="nav-inner">
<div class="menu-item"><a href="/">Home</a></div>
<div class="menu-item"><a href="/about">About</a></div>
</div>
</nav>
</div>
</div>
</header>
</div>
<div class="content-container">
<div class="content-inner">
<main>
<div class="title-wrapper">
<h1>Welcome</h1>
</div>
<div class="text-block">
<p>First paragraph.</p>
</div>
<div class="text-block">
<p>Second paragraph.</p>
</div>
</main>
</div>
</div>
<div class="footer-container">
<footer>
<div class="footer-inner">
<p>© 2026</p>
</div>
</footer>
</div>
</div><!-- The same page, without the noise -->
<header>
<a href="/">Acme</a>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
</header>
<main>
<h1>Welcome</h1>
<p>First paragraph.</p>
<p>Second paragraph.</p>
</main>
<footer>
<p>© 2026</p>
</footer>The first example isn't a caricature. It's what most projects produce — semantic
elements drowned in layers of CSS wrappers. The <header> is there, the
<main> is there, the <h1> is there. But between the user and those
waypoints, there are 15 <div> nodes that say nothing.
The second does the same thing in 10 lines. Zero <div>s. The semantic elements
carry their own styles.
What this changes for a screen reader
Modern browsers are smart: they filter semantics-free <div>s out of the
accessibility tree to reduce noise. A <div class="wrapper"> around a
<button> doesn't stop the screen reader from finding the button.
But if the browser ignores them, that's precisely because they serve no purpose.
Everything a <div> wrapper does — carry a class, receive a display: flex,
define a margin — a semantic element does too. A <nav class="main-nav">
accepts the same CSS properties as a <div class="nav-wrapper">. A <header>
can be a flex container just like a <div class="header-container">. The browser
sees no difference on the rendering side — but it sees a huge difference on the
accessibility side.
And that filter doesn't solve the deeper problem. What's missing in a <div>
soup isn't the extra nodes — it's the missing waypoints.
71.6% of screen reader users navigate by headings. 31.8% frequently use landmarks. These are the two primary methods for orienting within a page. I go into detail in Screen readers don't read like you.
When a <div class="section-title"> replaces an <h2>, a heading disappears
from the navigation list. When a <div class="sidebar"> replaces an <aside>,
a landmark ceases to exist. The user loses a shortcut to the information — and
there's no way to know without inspecting the code.
A 2007 study of 100 blind users measured that they lose an average of 30.4% of their time to frustrating situations, all causes combined, with confusing page structure as the leading one — missing waypoints, mislabelled content, unpredictable layouts.
You can see the accessibility tree yourself: in Chrome, open DevTools → Elements tab → Accessibility pane. It's an X-ray of what your HTML actually says — regardless of what it shows.
What it costs
In practice, few sites are built entirely out of <div>s. Most use some
semantic elements — a <header> here, a <button> there. The problem is more
insidious: it's the <div>s that accumulate around, inside and between the
useful elements. Three levels of wrappers for a button. A
<div class="form-group"> where a <fieldset> would do. A
<div class="content"> doubling a <main> that's already there.
The site doesn't collapse. It works. But every unnecessary <div> dilutes the
semantics, bloats the DOM and adds
noise — on four fronts.
Accessibility
A screen reader navigates by waypoints: headings, landmarks, links, buttons. In
a <div> soup, there are no waypoints. The user must traverse every
DOM node, one by one, top to bottom.
On a 200-node page, it's the equivalent of reading a book with no table of
contents, no chapters and no page numbers.
Interactive elements built from <div>s are invisible to the keyboard. A user
who can't use a mouse — and there are
many — simply cannot interact with your site.
Performance
Every unnecessary <div> is an extra node in the
DOM. The deeper the
DOM, the longer the browser takes to
calculate styles, layout and paint. Google Lighthouse triggers a warning above
800 elements and an error above 1,400, with a recommended maximum depth of 32
levels. According to the
Web Almanac 2022, the 90th
percentile of web pages is already approaching 2,000 elements (1,832 on mobile,
2,023 on desktop), with a median of 653.
And every hand-rolled interactive <div> ships JavaScript that the native
element wouldn't have needed. A native <dialog> opened with showModal() handles the focus trap on its
own. A <div> modal needs a script, an overlay and several event handlers. More
code, more weight, more bugs.
SEO
Google uses HTML semantics to
understand a page's structure. An <h2> is a heading. A <nav> is navigation.
An <article> is self-contained content. A <div> is nothing — Google can work
without semantics, but semantic HTML makes its job easier.
Featured snippets, rich results, position-zero extracts — all rely on structure that Google can interpret unambiguously. A clear heading hierarchy, lists, tables. The more explicit the structure, the more material Google has to generate those results.
GEO
LLMs don't rank pages — they select passages to cite. And to slice a page into passages, they rely on structure: headings, sections, articles, lists. Without those markers, the LLM sees an undifferentiated stream of text — harder to segment, harder to cite, easier to ignore.
An <h2> delimits a topic. A <ul> structures an enumeration. An <article>
isolates self-contained content. A <div> does none of that. And when the
LLM must choose between a structured
page and a <div> soup, the outcome is predictable.
I go into detail in How GEO forces web accessibility: generative engines favour content that is structured, sourced and machine-readable. Div soup is the exact opposite.
Four axes, one symptom. Div soup isn't an accessibility problem. It's a quality problem that manifests everywhere.
To see what this looks like in practice, I took a real product card from an
e-commerce site — 87 elements including 27 <div>s, 17 <span>s, 5 <i>s,
and only 6 semantic tags — and rewrote it without a single <div>. Same
rendering, zero visual compromise. The step-by-step breakdown is in Anatomy of
a product card.
So when should you use a <div>?
One case is in the specification: inside a <dl>, a <div> can group a
<dt> / <dd> pair. This is the only place where <div> provides a structure
that HTML doesn't offer
otherwise. The GOV.UK Design
System uses it
in its Summary List component — and it's one of the rare examples where <div>
is the right answer.
Beyond that, every <div> is an admission: I don't know how to do it any other
way. Not "there's nothing else" — because there's always something. A
<header>, a <main>, a <footer>, a <section>, a <fieldset>, a <ul>,
a <figure>. Over 100 elements, each with a role, a behaviour and semantics.
The right HTML element exists —
you just have to look for it.
The <div> isn't an enemy. It's a shortcut. And every shortcut has a cost — in
accessibility, in performance, in
SEO, in
GEO. That cost, you now
know.
Sources
- Empty and Nested HTML Divs — a11y.cafe
- Screen Reader User Survey #10 — WebAIM, 2024
- What Frustrates Screen Reader Users on the Web — Lazar et al.
- Avoid an excessive DOM size — Google Lighthouse
- Web Almanac 2022 — Markup — HTTPArchive
- Semantics and screen readers — web.dev
- Summary list — GOV.UK Design System