Semantic HTML: why div soup costs you machine readability
Any layout can be built entirely from div and span. The result is pixel-identical, ships the same, and passes review, which is exactly why so much of the web is built that way. What it does not do is survive being read by anything that is not a pair of eyes.
The short answer
An element name tells a machine what a thing is, and a div tells it nothing. Five substitutions do most of the work: main for the content region, article or section for a self-contained block, nav for navigation, real heading levels for headings, and ul or ol for anything that is a list. Screen readers gain the most; extraction systems gain the rest.
What an element name is actually for
A div means nothing. That is not a criticism, it is the specification: it is a generic container, defined as having no semantics at all. A nav means this is navigation. A table means these cells relate to those headers. The name is metadata that travels with the content for free.
Which is why the difference only shows up when something tries to consume the page programmatically. A browser needs no help drawing boxes. A screen reader building a landmark list, a reader-mode extracting an article, or a retrieval pipeline deciding which part of the document is content rather than chrome, all need to know what the boxes are.
What this check looks at
The composition of your markup: whether the page declares landmarks (header, nav, main, article, footer), whether things that are lists are marked as lists, whether tabular data is in a table with real th cells, and the ratio of meaningful elements to generic containers.
The ratio is the interesting number rather than any absolute count. Every real site has plenty of divs, and it should: a container that exists purely to hold a grid is exactly what a div is for. What the check reports is a page whose content carries no structural elements at all, which is a different thing from a page that uses divs for layout.
The five substitutions that do most of the work
You do not need to re-architect a codebase. In practice five swaps cover the majority of the gap, and none of them changes a single pixel once your existing classes come along for the ride.
<div class="page">
<div class="top">
<div class="logo">Acme</div>
<div class="links">
<div class="link"><a href="/docs">Docs</a></div>
<div class="link"><a href="/pricing">Pricing</a></div>
</div>
</div>
<div class="content">
<div class="title">Choosing a widget</div>
<div class="para">Three things matter.</div>
<div class="items">
<div class="item">Bore diameter</div>
<div class="item">Pressure rating</div>
</div>
</div>
<div class="bottom">© Acme</div>
</div><!-- Same classes, same CSS, same rendering.
Every element now says what it is. -->
<header class="top">
<div class="logo">Acme</div>
<nav class="links" aria-label="Main">
<ul>
<li class="link"><a href="/docs">Docs</a></li>
<li class="link"><a href="/pricing">Pricing</a></li>
</ul>
</nav>
</header>
<main class="content">
<article>
<h1 class="title">Choosing a widget</h1>
<p class="para">Three things matter.</p>
<ul class="items">
<li class="item">Bore diameter</li>
<li class="item">Pressure rating</li>
</ul>
</article>
</main>
<footer class="bottom">© Acme</footer>The five: a container becomes header, main or footer when that is what it is; a group of links becomes nav wrapping a ul; a repeated set of siblings becomes a list; a styled title becomes a heading; and a paragraph of prose becomes a p. Note that mainshould appear once, and that a list restores the "2 of 5" announcement a screen reader gives, which a stack of divs cannot.
Checking your own markup
For a single page, the fastest read is your browser's accessibility tree: it shows the landmark list and the heading outline exactly as they are derived, and a page with an empty landmark list is the failure in one glance.
This one needs the full crawl
This is a count of what your markup is made of, real headings, lists, tables and landmarks against generic containers, and it only means anything in aggregate across your templates.
The instant search on this site audits a single page, so rather than show you a verdict it cannot support, this guide sends you to the place the check actually runs.
Where component libraries push you the wrong way
Most of this defect is not authored by hand, it is inherited. A design system component named Stack or Box renders a div by definition, and a page assembled entirely from primitives is a page with no semantics regardless of how careful the author was.
The pattern that fixes it at the root is an escape hatch on the primitive rather than a rule for authors to remember:
// One prop turns a layout primitive into whatever the
// content actually is. Default stays a div, so nothing
// existing changes, and correctness becomes available
// instead of forbidden.
export function Stack({ as: Tag = 'div', gap = 4, children, ...rest }) {
return (
<Tag className={`flex flex-col gap-${gap}`} {...rest}>
{children}
</Tag>
)
}
// <Stack as="nav" aria-label="Main">…</Stack>
// <Stack as="ul">…</Stack>Headless component libraries are the other common source, and they usually get this right: a listbox or a menu emits the roles it needs. The gap is almost always the layout layer, not the interactive one.
The overcorrection, which is its own problem
Do not wrap everything in section, and do not add ARIA roles to divs instead of using the element.
A section without an accessible name contributes nothing to the outline, so a page of nested unnamed sections is div soup with longer tag names. And <div role="navigation"> is strictly worse than <nav>: same result, more markup, and it fails the first rule of ARIA, which is not to use ARIA when an element already does the job.
The test for whether an element earns a semantic name is whether you can say what it is in one word. If you cannot, it is a layout container and div is the correct choice.
Why the accessibility harm sets this rating
Because the content is present and readable. A model fetching a page of div soup still gets all of your text; what it loses is the map, so it has to infer structure from formatting rather than read it from the markup. Inference degrades quality, it does not prevent it.
The reason it is not Refinement is that there is a named group of people for whom this is not a degradation at all. A screen reader user on a page with no landmarks and no lists has lost the ability to navigate it, which is a present, concrete harm rather than a hypothetical ranking cost. The scale treats that as Important on its own, before any search argument is added.
Does this affect AI search?
Yes, and the effect is on precision rather than access. A model can read div soup; what it cannot do reliably is tell your article apart from your cookie banner, your navigation and your footer.
That matters at the extraction step. A pipeline deciding which part of a document to keep uses whatever signals the markup offers, and main or article is the strongest available: an explicit statement that this, and not the surrounding chrome, is the content. Without it the extractor is guessing from text density, and the thing most likely to get included by mistake is the navigation that appears on every page you own.
The compounding case is worth naming. Semantic markup, a sound heading outline and real lists and tables are one piece of work that pays three times, because they are all the same signal at different scales.
Confirming the outline is real
Open the accessibility tree and look for the landmark list. You want to see banner, navigation, main and contentinfo, in that order, with your content inside main. That view is the derived outline from the diagram at the top of this page, which is the thing consumers of your markup actually receive.
Then re-crawl for the aggregate, since the useful answer is which templates are still generic rather than whether one page is fixed. Expect the ratio to move template by template: this defect lives in layout components, so one edit tends to fix a whole section of the site at once.
Questions this check raises
- Does semantic HTML help SEO?
- Marginally and indirectly for classic ranking, and substantially for anything extracting content from your page. The larger benefit is accessibility: landmark elements are how a screen reader user skips to the content, and there is no equivalent for a page built from anonymous divs.
- Is one main element per page required?
- Yes, one visible main per page. It marks where the content of this document begins, as opposed to the header, navigation and footer that appear on every page, and it is the single most useful landmark to add to a page that has none.
- Can I overdo semantic HTML?
- Yes, and the overcorrection is its own problem. Wrapping every div in a section, nesting articles that are not self-contained, or adding ARIA roles alongside elements that already carry them produces an outline full of unnamed regions, which is harder to navigate than plain divs.