Debugging HTML

Recognise the symptoms of a tree the browser had to repair, and find the cause quickly.

22of 22
≈ 10Min read
8Parts

HTML never throws

There is no error message. Whatever you write, the parser produces a document — it closes what you left open, moves what cannot be where you put it, and drops what makes no sense. Your page renders, just not as the tree you meant.

So debugging HTML is not about reading errors. It is about comparing the tree you intended with the tree you got.

Source and DOM are two different things

  • View source (Ctrl/Cmd + U) — the bytes the server sent. What you wrote.
  • The Elements panel — the live DOM: after repair, after every script. What you have.
  • If they differ and no script ran, the parser repaired something. That difference is your bug.
The fastest check there is

Right-click the element that is misbehaving and choose Inspect. Nine times out of ten you are looking at a different element than you assumed, or at one that has been moved out of its parent.

Symptoms and their causes

What you seeUsually means
A style applies to far more than it shouldA tag was never closed, so everything after it is inside that element.
An element is a sibling when it should be a childThe parent cannot contain it — a <div> inside a <p>, a <div> inside a <ul>.
Empty elements appear that you never wroteThe parser closed a <p> and opened a fresh one after a block element.
A whole block vanishedIt was inside a <table> but not in a cell — the parser moves stray content out before the table.
& turned into something strangeAn unescaped ampersand started what looked like an entity.
Text is missing from the middle of the pageAn unclosed quote in an attribute swallowed everything up to the next quote.
Only the first of two elements respondsTwo elements share an id.
A label does not focus its fieldThe for does not match the id, or the id is duplicated.

The unclosed tag

The <p> is never closed, but a </div> implies it, so this one is harmless. Move the same mistake to a <span> or a <div> and the second card ends up inside the first, and every rule you wrote for .card now nests.

one missing character
<div class="card">
  <h3>Painted</h3>
  <p>Flush with the wall.
</div>
<div class="card">
  <h3>Veneer</h3>
</div>

The validator

The W3C validator at validator.w3.org reads your markup and lists what is wrong — unclosed elements, forbidden nesting, duplicate ids, missing required attributes. It is the one tool that tells you about mistakes the browser silently forgave.

Not every warning matters, but the errors almost always do. Run it on a page you are about to ship.

Reading the Elements panel

  • The breadcrumb at the bottom shows the ancestor chain of the selected element — the quickest way to see that something has the wrong parent.
  • A DOM node with a grey triangle can be expanded; if an element you expected to be empty has children, something was absorbed.
  • Right-click → Copy → Copy outerHTML gives you the actual markup of that subtree, after repair.
  • $0 in the console refers to the element currently selected in the panel. $0.parentElement, $0.getBoundingClientRect() and $0.matches(".card") answer most layout questions in one line.

Checking from the console

quick audits you can paste anywhere
// duplicate ids
const ids = [...document.querySelectorAll("[id]")].map(e => e.id);
ids.filter((id, i) => ids.indexOf(id) !== i);

// images with no alt attribute at all
[...document.images].filter(i => !i.hasAttribute("alt"));

// form controls with no accessible label
[...document.querySelectorAll("input,select,textarea")]
  .filter(el => !el.labels?.length && !el.getAttribute("aria-label"));

// links whose text says nothing
[...document.links].filter(a => /^(click here|here|read more|link)$/i.test(a.textContent.trim()));

// headings, in order — look for a level that was skipped
[...document.querySelectorAll("h1,h2,h3,h4,h5,h6")].map(h => h.tagName + " " + h.textContent.trim());

Habits that prevent most of it

  • Indent consistently. A closing tag at the wrong indentation is visible; a missing one is not.
  • Let the editor close tags for you and highlight the matching pair.
  • Write the closing tag immediately, then fill in the middle.
  • Keep ids rare. Most things want a class.
  • When something looks wrong, inspect before you change anything. Editing on a guess turns one bug into two.

Practice