# HTML validation errors: which ones matter and which are noise

> A validator can list 200 errors on a page that works. Learn the handful that change how browsers, screen readers and Google read the page, how to find them, and which ones you can safely ignore.

Updated 2026-09-25 · Best practices · HTML version: https://getreport.app/guides/html-validation-errors-which-ones-matter

Run almost any real website through the W3C validator and you get a long list of errors. Most of them change nothing: the browser repairs the markup the same way every time and the page works. A few change the page itself, moving a canonical tag out of `<head>`, pulling text out of its section or breaking the link between a label and its field. This guide shows how to tell the two apart, fix the ones that matter and stop worrying about the rest. Expect an hour for a typical template.

## Quick answer

- Google does not rank pages on validity, and browsers repair most errors. Chasing zero errors is not the goal.
- Fix the errors that change the document: a missing doctype, a late or missing charset, anything that ends `<head>` early, duplicate `id` values, unclosed or misnested elements that move content, broken list and table structure.
- Ignore the noise: obsolete presentational attributes, `type="text/javascript"`, trailing slashes on `<br />`, framework attributes such as `v-if` or `x-data`.
- Validate the HTML your server sends and look at the repaired DOM in DevTools; for JavaScript-built pages, validate the rendered DOM too.
- The [SEO audit](https://getreport.app/tools/seo-audit) checks the viewport tag; the full report adds the doctype and charset checks.

## Why HTML validation errors matter (some of them)

HTML has a precise error-recovery algorithm. When a browser meets a `<div>` inside a `<p>` or a stray `</div>`, it does not guess; it follows rules every modern browser shares. That is why invalid pages usually look fine, and why Google has said for years that valid markup is not a ranking factor.

The problem is what the repair produces. The browser builds a document tree that can differ from the one the template author imagined:

- **Head elements that land in the body.** If `<head>` contains something that does not belong there (a `<div>`, an `<img>`, stray text), the parser closes the head at that point. Every `<meta>` and `<link>` after it is read as part of the body. Google's documentation on [valid page metadata](https://developers.google.com/search/docs/crawling-indexing/valid-page-metadata) says it stops reading the head at the first invalid element, so a canonical, a robots meta or an `og:image` after that point can be ignored.
- **Content that moves.** A block element inside a `<p>` closes the paragraph early. Text or elements placed directly inside a `<table>` but outside a cell are moved out and shown above the table. Headings and paragraphs can end up outside the section or article they were written in.
- **Relationships that break.** Two elements with the same `id` mean `<label for>`, `aria-labelledby`, `aria-describedby` and `#anchor` links point at whichever comes first. The form field with the duplicate loses its label for screen reader users.
- **Rendering modes that change.** Without a doctype, browsers switch to quirks mode, where box sizing, table font sizes and some line heights follow 1990s rules.

## How getReport checks it

> **Free tool:** [Free SEO audit](https://getreport.app/tools/seo-audit): Every on-page and technical SEO check in one run: title and description, headings, canonical, robots and sitemaps, indexability, links, images and mobile readiness — each with a fix.

getReport checks the errors with the largest effect as separate findings rather than as one validator count. The SEO audit covers the viewport tag; the doctype and charset checks are in the Best practices module of the full report, one click from the tool.

![The Best practices module of the example shop report: score 88, one failed finding and six warnings about caching, compression, images and HTTP/1.1, and a collapsed "Show 16 passed checks" link](https://getreport.app/guides/img/html-validation-errors-which-ones-matter/practices.webp "Doctype and charset pass on this shop, so they sit in the collapsed list of passed checks rather than among the findings.")

> **Check: Doctype is present.** Without <!doctype html>, browsers render in quirks mode and layouts can differ between browsers.
>
> 1. Put <!doctype html> as the very first line of the document.

The doctype check reads the start of the HTML the server sent. If `<!doctype html>` is not the first thing there (a byte-order mark and whitespace are allowed), it asks the rendered page whether the browser found a doctype at all. Only a page with no doctype gets the warning. A legacy HTML 4 or XHTML doctype normally passes too, even though some of them still put the browser in quirks or limited-quirks mode; step 2 shows how to tell.

> **Check: Character encoding is declared early.** Browsers need to know the encoding before they read the text. When it is missing or declared late, accented letters such as č, ć and š can appear as garbage and the page re-renders.
>
> 1. Put <meta charset="utf-8"> as the first element inside <head>, within the first 1024 bytes of the document.
> 2. Or send it in the response header: Content-Type: text/html; charset=utf-8.

The charset check passes when a `<meta charset>` sits within the first 1,024 bytes of the document or the `Content-Type` header declares a charset. A meta tag after the first 1,024 bytes, or no declaration anywhere, is a warning.

> **Check: Viewport meta tag is present.** Without a viewport tag, phones render the page at desktop width and shrink it. Google indexes the mobile version first, so this hurts rankings directly.
>
> 1. Add <meta name="viewport" content="width=device-width, initial-scale=1"> in <head>.

A full validator count is catalogued as `html-valid-errors-count`, an information line with no weight, but it is not part of reports yet. Until it is, use the W3C validator as described below. The [accessibility checker](https://getreport.app/tools/accessibility-checker) catches several structural errors from the accessibility side: axe-core flags lists with invalid children and duplicate ids referenced by ARIA.

## Step by step

### 1. Validate the page and sort the list

Paste the page's address into the [Nu HTML Checker](https://validator.w3.org/nu/) from the W3C. It reports errors and warnings with line numbers in the source. Do not start at the top; sort the list with the triage table:

| Error | What it does to the page | Priority |
| --- | --- | --- |
| No doctype | Quirks mode: layout differences between browsers | Fix now |
| Charset missing or late | Garbled accented letters, a second parse | Fix now |
| Element not allowed in `<head>` | Ends the head; later meta and link tags may be ignored | Fix now |
| Duplicate `id` | Labels, ARIA references and anchors point at the wrong element | Fix now |
| Stray end tag, unclosed element | Content moves out of its container; layout breaks after edits | Fix in the template |
| Non-`<li>` children in `<ul>`/`<ol>` | Screen readers announce wrong item counts | Fix in the template |
| Content directly in `<table>` | Moved above the table | Fix in the template |
| `<button>` or `<a>` inside `<a>` | Unpredictable clicks, confusing for assistive tech | Fix when touched |
| Duplicate attribute | The second one is dropped | Fix when touched |
| More than one visible `<main>` | Landmark navigation lands on the first | Fix when touched |
| Obsolete attributes, `type` on scripts and styles, trailing slashes | Nothing | Ignore |
| Unknown attributes from frameworks | Nothing | Ignore |

### 2. Fix the document-level errors

The first three lines of every page decide the rendering mode and the encoding. In your base template (`header.php`, `theme.liquid`, a layout component):

```html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Page title</title>
  <!-- then canonical, description, Open Graph, styles, scripts -->
</head>
```

Keep the `lang` value accurate, since screen readers pick their pronunciation from it ([language attributes and screen readers](https://getreport.app/guides/language-attributes-and-screen-readers)), and never add `user-scalable=no` or `maximum-scale=1` to the viewport tag ([why pinch zoom must stay on](https://getreport.app/guides/zoom-and-viewport-never-disable-pinch-zoom)).

`<meta charset>` goes first because the browser must know the encoding before it reads any text, including the title. If your server already sends `Content-Type: text/html; charset=utf-8`, the meta tag is a belt to those braces. A byte-order mark at the very start of a file overrides both declarations. The most common cause of garbled letters is simpler: a template or a database table saved in one encoding (Windows-1250, Latin-1) and declared as another. Save templates as UTF-8.

To see which mode the browser chose, type `document.compatMode` in the DevTools console: `"CSS1Compat"` is standards mode, `"BackCompat"` is quirks mode. The [MDN page on quirks mode](https://developer.mozilla.org/en-US/docs/Web/HTML/Quirks_Mode_and_Standards_Mode) lists what changes.

### 3. Keep the head clean

Only `<title>`, `<meta>`, `<link>`, `<script>`, `<style>`, `<base>`, `<noscript>` and `<template>` belong in `<head>`. The usual intruders are tracking snippets: the `<noscript><iframe>` part of the Tag Manager snippet belongs right after `<body>`, not in the head, and a pixel's `<img>` fallback likewise. Plugins that print a `<div>` from a `wp_head` hook do the same damage. After a fix, the validator should no longer report "Element … not allowed as child of element head" and the Elements panel in DevTools should show your canonical and Open Graph tags inside `<head>`.

### 4. Fix duplicate ids

Search the validator output for "Duplicate ID". The usual sources are a component rendered twice (a search form in the header and again in the mobile menu, both with `id="search"`), copy-pasted blocks and sliders that clone slides. Make each id unique or remove ids nothing references:

```html
<!-- Before: two fields, one label target -->
<label for="email">Email</label><input id="email" type="email">
<!-- … footer newsletter … -->
<label for="email">Email</label><input id="email" type="email">

<!-- After -->
<label for="contact-email">Email</label><input id="contact-email" type="email">
<label for="newsletter-email">Email</label><input id="newsletter-email" type="email">
```

This matters more for accessibility than for anything else: a screen reader announces the second field without its label.

### 5. Fix structure that moves content

"Stray end tag `div`" or "End tag `div` seen, but there were open elements" usually means one template opens a wrapper and another closes it, often across an `if` branch. Match every opening tag with its closing tag in the same file and the same branch:

```html
<!-- Before: the wrapper only opens when there is a sidebar, but always closes -->
{% if sidebar %}<div class="with-sidebar">{% endif %}
  …
</div>

<!-- After -->
{% if sidebar %}<div class="with-sidebar">{% else %}<div>{% endif %}
  …
</div>
```

Lists take only `<li>` children (plus `<script>` and `<template>`); wrap extra markup inside the `<li>`. Tables need their content inside `<td>` or `<th>`. A `<p>` cannot contain `<div>`, `<ul>`, headings or another `<p>`; use a `<div>` as the outer element instead.

### 6. Leave the noise alone

These errors and warnings change nothing a visitor or a crawler can notice:

- `type="text/javascript"` and `type="text/css"` (unnecessary, harmless).
- Trailing slashes on void elements, `<br />` and `<img … />`.
- Obsolete presentational attributes such as `border="0"` or `align="center"`: move them to CSS when you touch the template, not before.
- Framework attributes such as `v-if`, `@click`, `x-data` or `ng-…`, and hyphenated custom elements. (`data-…` attributes are valid already.)
- A `<div>` inside an `<a>`: valid since HTML5, as long as the link contains nothing interactive and its parent allows the `<div>`.

### 7. Validate in CI

For a static or server-rendered site, run the Nu checker on the built HTML so structural errors do not come back:

```bash
# Once: the checker as a dev dependency (needs Java 11+)
npm install --save-dev vnu-jar

# In CI, after the build: errors only, HTML files only
java -jar node_modules/vnu-jar/build/dist/vnu.jar --errors-only --skip-non-html dist/
```

It exits with a non-zero status when it finds errors. Start with the templates that matter most and add the rest once they are clean.

## Platform notes

### WordPress

The classic editor and older themes run content through `wpautop`, which wraps lines in `<p>`. A shortcode that outputs a `<div>` then ends up inside a paragraph, producing a stray `</p>` and an empty paragraph. Shortcode authors fix it by returning block markup without surrounding line breaks; site owners can put the shortcode in its own Shortcode block in the block editor. Page builders produce deeply nested but mostly valid markup; that is a [DOM size](https://getreport.app/learn/dom-size) problem, not a validation one. Duplicate ids are common with header and mobile-menu widgets that print the same form twice.

### Shopify

Liquid conditionals that open a tag in one branch and close it outside the branch are the main source of misnesting. Sections and app blocks that each print their own `<style>` or `<link>` inside the body are allowed and not worth fixing.

### Sites built with JavaScript

When a framework builds the page in the browser, the source the validator fetches may be an almost empty shell. Validate the rendered DOM as well: in the console, run `copy('<!doctype html>\n' + document.documentElement.outerHTML)` and paste the result into the validator's text input. `outerHTML` does not include the doctype, which is why the snippet adds it.

## Verify

- The validator lists no errors from the "fix now" rows of the triage table on your main templates.
- The report's doctype and charset findings read "Doctype is present" and "Character encoding is declared early".
- In DevTools, the Elements panel shows every `<meta>` and `<link>` inside `<head>`, and `document.compatMode` returns `"CSS1Compat"`.

## Common mistakes

- **Chasing zero errors.** A day spent removing `type` attributes changes nothing. Fix the rows that change the document and stop.
- **Validating the source when JavaScript rewrites the page.** The validator sees the shell, not what visitors get. Validate the rendered DOM too.
- **Fixing framework noise.** Removing `v-if` or `x-data` to please the validator breaks the page. Leave them.
- **Ignoring duplicate ids.** They look harmless in the list and quietly break labels and ARIA references. Search for them first.
- **Moving the charset below other tags.** A plugin that inserts a long comment or script before `<meta charset>` can push it past the first 1,024 bytes. Keep it the first element in `<head>`.
