Skip to main content
SiteScan.top

Invalid HTML: which errors browsers forgive and which cost you

Duplicate ids, unclosed tags, nested forms. The markup mistakes that survive because the browser repairs them, and the ones that quietly break behaviour.

HTML · Published: 8 September 2026 · 3 min read · Марк Лебедев

Browsers are forgiving by design: the HTML parser has a documented recovery procedure for almost any malformed input. That forgiveness is why invalid markup survives for years — and why some of it does real damage while nobody notices.

The errors that actually break things

Duplicate ids. document.getElementById returns the first match, and only the first. A label points at the wrong field; a script wires a handler to an element nobody clicks. This is the single most common finding on component-based sites, because an id hard-coded inside a component becomes a duplicate the moment the component renders twice.

Nested forms. HTML does not allow them and the parser silently drops the inner one. The submit button inside it then does nothing, and the code looks correct in the editor.

Labels with no target. for="email" with no id="email" breaks the click-the-label behaviour and removes the accessible name — one mistake that costs both usability and accessibility.

Interactive elements nested inside each other. A button inside a link, or a link inside a button, produces inconsistent behaviour between browsers and unpredictable keyboard focus.

Duplicate attributes. <input value="a" value="b"> — the second is discarded, usually the one someone added later to fix a bug.

The errors that are mostly cosmetic

An unclosed <li>, a stray & that should have been &amp;, an obsolete <center>, a missing type on a script tag. The parser handles all of them and the rendered page is identical. They are worth fixing when they are cheap, but they do not deserve a sprint.

The one caveat: unclosed tags in table and list structures can change the shape of the DOM tree rather than just the markup, which does move content on the page. Treat those as real.

Why linting the source is not enough

Templates are assembled at runtime. A component that is valid on its own becomes invalid in a page where it appears twice, or inside a wrapper that already opened a form. Your linter reads files; the browser sees the assembled document. The only reliable check is on the rendered HTML of real pages:

for each crawled page:
  parse the served HTML
  ids seen more than once?
  forms nested inside forms?
  label[for] without a matching id?
  interactive elements inside interactive elements?

Run it across the whole site and group by template — like most markup problems, one bad component explains a hundred bad pages.

What it costs when you skip it

Nothing dramatic, which is exactly the trouble. A form that cannot submit on one template. A screen reader announcing the wrong field. A click handler bound to an invisible duplicate. Each looks like an isolated bug, gets a ticket, gets a workaround — and the actual cause, three lines of markup, stays in place, ready to produce the next isolated bug.

Read next

Check how much of this is on your own site

A free scan surfaces broken links, JS errors and performance problems in about a minute.