HTML Guides for body
Learn how to identify and fix common HTML validation errors flagged by the W3C Validator — so your pages are standards-compliant and render correctly across every browser. Also check our Accessibility Guides.
A <style> element in the <body> comes after other content inside its parent, but the HTML standard only allows it there as the parent's first child.
Since October 2026, a <style> element can sit in the <body> as long as its styles can't reach anything earlier in the page. Its style rules go inside an @scope block, which limits them to the parent of the <style> and everything inside it. Putting the <style> first in that parent means the browser has the styles before it renders any of the content they apply to.
Whitespace and comments before the <style> don't count, so indenting it or adding a comment above it is fine. Any element or text before it triggers the error, including a heading, a <link>, a <script> or another <style>. A <style> at the end of a container, or at the end of the <body>, breaks the rule as well. When two <style> elements sit next to each other, the second one is reported, so merge their rules into a single element.
To fix the error, move the <style> element up so it's the first thing inside its parent. An @scope block without a selector still applies to the same parent, so the styles keep matching the same elements. If the parent has to start with something else, such as a heading, wrap the content the styles target in a new element and put the <style> at the top of it. The scope then becomes that wrapper. Styles meant for the whole page belong in the <head>.
Invalid example
<div class="card">
<p>Card content</p>
<style>
@scope {
p {
color: darkslategray;
}
}
</style>
</div>
Valid example
<div class="card">
<style>
@scope {
p {
color: darkslategray;
}
}
</style>
<p>Card content</p>
</div>
The <meta> element is most commonly used inside the <head> section to define metadata like character encoding, viewport settings, or descriptions. Inside <head>, attributes like charset, http-equiv, and name are perfectly valid. However, the HTML specification also allows <meta> to appear inside the <body> — but only under specific conditions.
When a <meta> element appears in the <body>, it must have either an itemprop attribute (for microdata) or a property attribute (for RDFa). It must also have a content attribute. Additionally, it cannot use http-equiv, charset, or name attributes in this context. These rules exist because the only valid reason to place a <meta> tag in the <body> is to embed machine-readable metadata as part of a structured data annotation — not to define document-level metadata.
Why this matters
- Standards compliance: The HTML living standard explicitly restricts which attributes
<meta>can use depending on its placement. Violating this produces invalid HTML. - Browser behavior: Browsers may ignore or misinterpret
<meta>elements that appear in the<body>without proper attributes. For example, a<meta http-equiv="content-type">tag inside the<body>will have no effect on character encoding, since that must be determined before the body is parsed. - SEO and structured data: Search engines rely on correctly structured microdata and RDFa. A
<meta>element in the body withoutitemproporpropertywon't contribute to any structured data and serves no useful purpose.
Common causes
- Misplaced
<meta>tags: A<meta>element meant for the<head>(such as<meta http-equiv="...">or<meta name="description">) has accidentally been placed inside the<body>. This can happen due to an unclosed<head>tag, a CMS inserting tags in the wrong location, or simply copying markup into the wrong section. - Missing
itemproporproperty: A<meta>element inside the<body>is being used for structured data but is missing the requireditemproporpropertyattribute.
Examples
Incorrect: <meta> with http-equiv inside the <body>
This <meta> tag belongs in the <head>, not the <body>:
<body>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<form>
<input type="text" name="q">
</form>
</body>
Fixed: Move the <meta> to the <head>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>My Page</title>
</head>
<body>
<form>
<input type="text" name="q">
</form>
</body>
Incorrect: <meta> in the <body> without itemprop or property
<div itemscope itemtype="https://schema.org/Offer">
<span itemprop="price">9.99</span>
<meta content="USD">
</div>
The <meta> element is missing the itemprop attribute, so the validator reports the error.
Fixed: Add the itemprop attribute
<div itemscope itemtype="https://schema.org/Offer">
<span itemprop="price">9.99</span>
<meta itemprop="priceCurrency" content="USD">
</div>
Correct: Using property for RDFa
The property attribute is also valid for <meta> elements in the <body> when using RDFa:
<div vocab="https://schema.org/" typeof="Event">
<span property="name">Concert</span>
<meta property="startDate" content="2025-08-15T19:00">
</div>
Incorrect: <meta name="..."> inside the <body>
The name attribute is only valid on <meta> elements inside the <head>:
<body>
<meta name="author" content="Jane Doe">
<p>Welcome to my site.</p>
</body>
Fixed: Move it to the <head>
<head>
<title>My Site</title>
<meta name="author" content="Jane Doe">
</head>
<body>
<p>Welcome to my site.</p>
</body>
When the browser encounters a </body> tag while elements like <div>, <section>, <p>, or <span> are still open, it must guess where those elements were supposed to end. Different browsers may guess differently, leading to inconsistent rendering and a DOM structure that doesn't match your intent. This can cause layout problems, broken styling, and accessibility issues — screen readers rely on a well-formed DOM to convey the correct document structure to users.
The root causes of this error typically include:
- Forgetting a closing tag — the most common scenario, especially with deeply nested structures.
- Mismatched tags — closing tags that don't correspond to their opening tags (e.g., opening a
<div>but closing it with</section>). - Copy-paste errors — duplicating or deleting code that leaves behind orphaned opening tags.
- Incorrect nesting — overlapping elements where tags cross boundaries instead of being properly nested.
To fix the issue, work through the validator's error list from top to bottom. The error message usually identifies which elements are unclosed. Find each one and either add the missing closing tag in the correct position or remove the unnecessary opening tag. Using consistent indentation makes it much easier to spot mismatches visually.
Examples
Missing closing tag
This triggers the error because the <section> element is never closed:
<body>
<section>
<h1>Welcome</h1>
<p>Hello, world!</p>
</body>
Add the missing </section> closing tag:
<body>
<section>
<h1>Welcome</h1>
<p>Hello, world!</p>
</section>
</body>
Multiple unclosed elements
Here, both the <div> and the <ul> are left open:
<body>
<div class="sidebar">
<ul>
<li>Home</li>
<li>About</li>
</body>
Close each element in the correct (reverse) order:
<body>
<div class="sidebar">
<ul>
<li>Home</li>
<li>About</li>
</ul>
</div>
</body>
Mismatched closing tag
A mismatch between opening and closing tags can also produce this error. The <div> is opened but </section> is used to close it, leaving the <div> unclosed:
<body>
<div class="content">
<p>Some text here.</p>
</section>
</body>
Fix the closing tag so it matches the opening tag:
<body>
<div class="content">
<p>Some text here.</p>
</div>
</body>
Overlapping (improperly nested) elements
Elements that overlap instead of nesting properly will also trigger this error. Here the <b> and <i> tags cross each other's boundaries:
<body>
<p><b>Bold and <i>italic</b> text</i></p>
</body>
Ensure elements are closed in the reverse order they were opened:
<body>
<p><b>Bold and <i>italic</i></b><i> text</i></p>
</body>
According to the HTML specification, <meta> elements are metadata content and must appear within the <head> element. When a <meta> tag appears between </head> and <body>, browsers have to error-correct by either ignoring the element or silently relocating it into the head. This can lead to unpredictable behavior — for instance, a <meta charset="utf-8"> tag in the wrong position might not be processed in time, causing character encoding issues. Similarly, a misplaced <meta name="viewport"> could fail to apply on some browsers, breaking your responsive layout.
There are several common causes for this error:
- A
<meta>tag accidentally placed after</head>— perhaps added hastily or through a copy-paste error. - A duplicate
<head>section — if a second<head>block appears in the document, the browser closes the first one implicitly, leaving orphaned<meta>elements in limbo. - An unclosed element inside
<head>— a tag like an unclosed<link>or<script>can confuse the parser, causing it to implicitly close</head>earlier than expected, which pushes subsequent<meta>tags outside the head. - Template or CMS injection — content management systems or templating engines sometimes inject
<meta>tags at incorrect positions in the document.
To fix the issue, inspect your HTML source and ensure every <meta> element is inside a single, properly structured <head> section. Also verify that no elements within <head> are unclosed or malformed, as this can cause the parser to end the head section prematurely.
Examples
Incorrect — <meta> between </head> and <body>
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Page</title>
<meta charset="utf-8">
</head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<body>
<p>Hello, world!</p>
</body>
</html>
The <meta name="viewport"> tag is outside <head>, triggering the validation error.
Incorrect — duplicate <head> sections
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Page</title>
<meta charset="utf-8">
</head>
<head>
<meta name="description" content="A sample page">
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
The second <head> block is invalid. The browser ignores it, leaving the <meta name="description"> element stranded between </head> and <body>.
Incorrect — unclosed element forces early head closure
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Page</title>
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
<div>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
The <div> is not valid inside <head>, so the parser implicitly closes the head section when it encounters it. The subsequent <meta> tag ends up outside <head>.
Correct — all <meta> elements inside <head>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="A sample page">
<title>My Page</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
All <meta> elements are properly contained within a single <head> section. Note that <meta charset="utf-8"> should ideally be the very first element in <head> so the browser knows the encoding before processing any other content.
The HTML specification defines a strict structure for documents: the <html> element contains a <head> and a <body>, and all visible page content must live inside the <body>. Once the browser encounters the </body> closing tag, it expects nothing but optional whitespace (spaces, tabs, newlines) before the closing </html> tag. Any non-space characters in that gap — whether text, HTML elements, script tags, or even stray punctuation — are considered outside the document body and trigger this validation error.
Why this matters
- Standards compliance: The HTML living standard (WHATWG) is clear that content after
</body>is not valid. W3C validation will flag this as an error. - Unpredictable rendering: Browsers use error recovery to handle misplaced content, and different browsers may handle it differently. Most will implicitly reopen the
<body>and move the content inside it, but relying on this behavior is fragile and unpredictable. - Accessibility: Screen readers and assistive technologies parse the DOM structure. Content that lands in unexpected places due to error recovery may be announced out of order or missed entirely.
- Maintainability: Stray content after
</body>is often a sign of a code organization problem — a misplaced include, a template rendering issue, or an accidental paste — and cleaning it up improves long-term maintainability.
Common causes
- Accidental text or typos after the closing
</body>tag. - Script or analytics tags mistakenly placed between
</body>and</html>instead of at the end of the<body>. - Server-side template engines appending content after the body in a layout file.
- Comments or debugging output left behind after
</body>.
Examples
Stray text after </body>
This triggers the validation error because the text this is invalid appears after </body>:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Example</title>
</head>
<body>
<p>Content goes here.</p>
</body> this is invalid
</html>
Move all content inside the <body>:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Example</title>
</head>
<body>
<p>Content goes here.</p>
</body>
</html>
Script placed outside the body
A common mistake is placing a <script> tag between </body> and </html>:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Example</title>
</head>
<body>
<p>Main content</p>
</body>
<script src="analytics.js"></script>
</html>
Place the script at the end of the <body> instead:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Example</title>
</head>
<body>
<p>Main content</p>
<script src="analytics.js"></script>
</body>
</html>
Stray HTML element after </body>
Sometimes an element like a <div> ends up after the body due to a template issue:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Example</title>
</head>
<body>
<main>Page content</main>
</body>
<div id="overlay"></div>
</html>
Move it inside <body>:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Example</title>
</head>
<body>
<main>Page content</main>
<div id="overlay"></div>
</body>
</html>
The fix is always the same: ensure that everything between </body> and </html> is either whitespace or nothing at all. All content — text, elements, scripts, and comments — belongs inside the <body> element.
A <style> element in the <body> contains an @import rule, which the HTML standard doesn't allow there.
Since October 2026, a <style> element can sit in the <body> as long as its styles can't reach anything earlier in the page. Its style rules have to go inside an @scope block, and it can't use @import at all. The rules in an imported file aren't scoped, so they would apply to the whole page. Every @import in the element triggers the error, including ones with layer() or a media query, even when the rest of the style sheet is valid.
Moving the @import into the @scope block doesn't work either. CSS only accepts @import at the top of a style sheet, before other rules, so the validator reports a CSS parse error instead. Older validator builds worded this message differently and mentioned @scope, but the problem and the fix are the same.
To fix the error, load the file with a <link rel="stylesheet"> element. A stylesheet link is allowed in the <body>, but the linked rules still apply to the whole page, so the <head> is the more usual place for it. If the imported rules only target this part of the page, copy them into the @scope block of the <style> element instead. A <style> in the <body> also has to stay the first child of its parent.
Invalid example
<div class="card">
<style>
@import url("card.css");
@scope {
p {
color: darkslategray;
}
}
</style>
<p>Card content</p>
</div>
Valid example
<div class="card">
<style>
@scope {
p {
color: darkslategray;
}
}
</style>
<link rel="stylesheet" href="card.css">
<p>Card content</p>
</div>
Older builds of the validator reported at-rules like @keyframes and @font-face outside an @scope block in a <style> element in the <body>, but the HTML standard allows them there.
Since October 2026, a <style> element can sit in the <body> as long as its styles can't reach anything earlier in the page. Its style rules have to go inside an @scope block, and it can't use @import. An earlier draft of that change allowed only a few at-rules at the top level of the style sheet, such as @media, @supports and @layer. Builds of the Nu HTML Checker from late September and early October 2026 followed the draft and reported every other at-rule with this message.
The final rule lets at-rules that don't select elements stay outside @scope, at the top level or inside @media. That includes @keyframes, @font-face, @page and @counter-style. Current builds of the validator don't report them, so the code doesn't need to change: validate the page again and the error goes away. @import is still not allowed, and it now has a message of its own.
The names these at-rules define, such as an animation name or a font family, apply to the whole page. The HTML standard suggests using them only in the scoped rules of the same <style> element, and picking names that nothing else on the page uses. That way they can't restyle content the browser has already drawn.
Example
Older builds reported both at-rules in this <style> element. Current builds accept it, because the only style rule is inside @scope:
<div class="card">
<style>
@font-face {
font-family: "Card Sans";
src: url("card-sans.woff2") format("woff2");
}
@keyframes card-fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@scope {
p {
font-family: "Card Sans", sans-serif;
animation: card-fade-in 0.3s ease-in;
}
}
</style>
<p>Card content</p>
</div>
Every valid HTML document follows a strict structure: a <!DOCTYPE html> declaration, followed by an <html> element containing exactly two children — <head> and <body>. All visible content and its associated markup must live inside <body>. Once the browser encounters the </body> closing tag, it expects nothing else except the closing </html> tag.
When an end tag appears after </body> has been closed, the browser's parser enters an error-recovery mode. While most browsers will attempt to handle this gracefully, the behavior is not guaranteed to be consistent. This can lead to unexpected DOM structures, which may cause issues with JavaScript that relies on the DOM tree, break CSS styling, or create accessibility problems for screen readers that depend on proper document structure.
This issue commonly arises from:
- Extra or duplicate closing tags — for example, an extra
</div>that doesn't match any open element inside<body>. - Accidentally placing elements after
</body>— such as moving a<script>or</div>outside the body during editing. - Copy-paste errors — fragments of markup pasted in the wrong location.
- Template or CMS issues — server-side templates that inject closing tags in the wrong order.
To fix this, review your document structure carefully. Make sure every opening tag inside <body> has a matching closing tag also inside <body>. Ensure nothing appears between </body> and </html> except whitespace.
Examples
Incorrect — end tag after body is closed
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Page</title>
</head>
<body>
<div>
<p>Hello world</p>
</div>
</body>
</div>
</html>
Here, an extra </div> sits between </body> and </html>. This triggers the validation error because the body has already been closed when the parser encounters </div>.
Incorrect — script tag placed after body
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Page</title>
</head>
<body>
<p>Hello world</p>
</body>
<script src="app.js"></script>
</html>
While browsers will typically still execute the script, placing it after </body> is invalid and produces this warning.
Correct — all content and tags inside body
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Page</title>
</head>
<body>
<div>
<p>Hello world</p>
</div>
<script src="app.js"></script>
</body>
</html>
All elements, including <script> tags, are properly nested inside the <body> element. The only thing that follows </body> is </html>.
Debugging tip
If your document is large and you're having trouble finding the stray tag, try these approaches:
- Count your tags — make sure every opening tag (like
<div>) has exactly one matching</div>inside<body>. - Use an editor with bracket matching — most code editors can highlight matching open/close tags, making orphaned tags easy to spot.
- Check the validator's line number — the W3C validator reports the line where the stray end tag was found, which points you directly to the problem.
- Indent properly — consistent indentation makes structural problems visually obvious.
A <script> element sits between the closing </head> tag and the opening <body> tag, where the HTML parser doesn't expect any elements.
After </head>, the parser expects the <body> start tag, with nothing but whitespace or comments before it. When it finds a <script> there, browsers move the element back into the <head> and run it from there, so the page usually works. The markup is still invalid. An analytics or tag manager snippet pasted right after </head> is a common cause, and so is a theme or plugin that prints its scripts there.
The validator treats this error as fatal. Right after it, the report shows "Cannot recover after last error. Any further errors will be ignored." and nothing later in the page gets checked. Other HTML problems below this point stay hidden until you fix it.
To fix it, move the <script> element inside the <head>, before </head>. Browsers already run it from there, so the page behaves the same after the change. A script can also go anywhere inside the <body>, for example right before </body>.
Invalid example
<!DOCTYPE html>
<html lang="en">
<head>
<title>My page</title>
</head>
<script src="analytics.js"></script>
<body>
<p>Hello, world!</p>
</body>
</html>
Valid example
<!DOCTYPE html>
<html lang="en">
<head>
<title>My page</title>
<script src="analytics.js"></script>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
An HTML document must have exactly one <body> element. According to the HTML specification, the <body> element represents the content of the document and must appear as the second child of the <html> element, right after <head>. When the browser's parser encounters content that belongs in the body before seeing an explicit <body> tag, it implicitly opens one. If a <body> tag then appears later in the markup, the parser sees it as a duplicate—hence the error "an element of the same type was already open."
There are several common causes for this error:
- Duplicate
<body>tags: A second<body>tag was accidentally typed or left behind after editing or merging templates. - Content between
</head>and<body>: Placing visible content (like text,<div>, or<script>with content) between the closing</head>tag and the opening<body>tag causes the parser to implicitly open the body, making your explicit<body>tag a duplicate. - Template or include issues: When using server-side includes, CMS templates, or JavaScript-based frameworks that inject partial HTML, multiple
<body>tags can end up in the final rendered output. - Misplaced elements inside
<head>: Placing elements that aren't valid inside<head>(such as<div>,<p>, or<img>) will cause the parser to implicitly close the<head>and open the<body>before you intended.
This matters because a malformed document structure can lead to unpredictable rendering across browsers. While modern browsers are forgiving and will attempt to recover, the implicit body opening may cause elements you intended to be in the <head> (like <meta> or <link> tags placed after the stray content) to end up in the body instead, where they have no effect. This can break stylesheets, SEO metadata, and other head-level functionality. Screen readers and other assistive technologies also rely on correct document structure.
To fix the issue, inspect your full HTML output (not just your templates individually) and make sure there is only one <body> tag. Verify that no visible content or body-level elements appear before it, and that nothing invalid is lurking inside <head>.
Examples
Duplicate <body> tag
This triggers the error because a second <body> tag appears while the first body is still open, as happens when an included template starts with its own <body>:
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Page</title>
</head>
<body>
<h1>Welcome</h1>
<body>
<p>More content</p>
</body>
</html>
If the second <body> comes after </body> instead, the validator also reports "Stray start tag “body”", because no body can start once the first one is closed.
Fixed — combine everything into a single <body>:
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Page</title>
</head>
<body>
<h1>Welcome</h1>
<p>More content</p>
</body>
</html>
Content placed between </head> and <body>
The stray <div> between </head> and <body> implicitly opens the body, making the explicit <body> tag a duplicate:
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Page</title>
</head>
<div id="banner">Hello</div>
<body>
<p>Main content</p>
</body>
</html>
Fixed — move the <div> inside <body>:
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Page</title>
</head>
<body>
<div id="banner">Hello</div>
<p>Main content</p>
</body>
</html>
Invalid element inside <head>
Placing a <div> inside <head> forces the parser to implicitly open <body>, so the actual <body> tag becomes a duplicate. This also means the <link> tag ends up in the body where it may not work as intended:
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Page</title>
<div>Oops</div>
<link rel="stylesheet" href="style.css">
</head>
<body>
<p>Content</p>
</body>
</html>
Fixed — remove the <div> from <head> and place it in <body>:
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Page</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div>Oops</div>
<p>Content</p>
</body>
</html>
A <body> start tag is stray when it appears where the parser can no longer open a body: after </body> or </html>, after a <frameset>, or inside a <template>.
A document has exactly one <body>. Once the parser has read </body> or </html>, a second <body> tag has nowhere to go. Browsers put the content that follows it back into the first body, and copy over any attributes the first <body> doesn't already have. That is why a second <body> after </body> also gets the "Start tag body seen but an element of the same type was already open" error. That error on its own means the extra <body> showed up while the first body was still open.
When the stray <body> comes after </html>, or carries an attribute the first <body> lacks (an onload handler, for example), the validator stops at that tag with "Cannot recover after last error". The rest of the page isn't checked until you remove it.
The extra tag usually comes from a template or include that wraps its output in its own <body>, such as a footer partial or a tracking snippet added after the layout has already closed the document. Move that content into the single <body>, before </body>, and drop the wrapper.
Old frameset pages trigger the error too. A <frameset> takes the place of the body, so the parser ignores a <body> that follows it, along with everything inside it. The <frameset> and <frame> elements are obsolete, so rebuild the page with one <body> and use <iframe> elements where the frames were. Inside a <template>, leave out the <body> tag and keep only its content.
Invalid example
<!DOCTYPE html>
<html lang="en">
<head>
<title>Opening hours</title>
</head>
<body>
<h1>Opening hours</h1>
<p>Monday to Friday, 9:00 to 17:00.</p>
</body>
<body>
<footer>
<p>Call us on 555-0100.</p>
</footer>
</body>
</html>
Valid example
<!DOCTYPE html>
<html lang="en">
<head>
<title>Opening hours</title>
</head>
<body>
<h1>Opening hours</h1>
<p>Monday to Friday, 9:00 to 17:00.</p>
<footer>
<p>Call us on 555-0100.</p>
</footer>
</body>
</html>
A <footer> placed after the closing </body> or </html> tag is stray, because the HTML parser allows no elements there.
Everything visible on a page goes inside <body>, the site footer included. Once the parser reads </body>, only whitespace, comments and the closing </html> tag may follow. Any element after that point is a parse error. Browsers recover by moving the footer back into the body as its last child, so the page usually looks right and the mistake goes unnoticed.
Templates are the usual source. A layout file closes the body before it includes the footer partial, or the page ends up with two </body> tags. The parser takes the first </body> as the end of the body, so a footer after it is stray even when another </body> follows. The validator doesn't flag that second </body>, so search the page for every </body> and keep only the last one.
Move the <footer> inside <body>, before its closing tag. It can sit directly in <body> as the page footer, or inside an <article> or <section> as the footer for that part of the page.
Invalid example
<!DOCTYPE html>
<html lang="en">
<head>
<title>Contact us</title>
</head>
<body>
<main>
<h1>Contact us</h1>
<p>Send us a message and we will reply within two days.</p>
</main>
</body>
<footer>
<p>Example Company, 123 Main Street</p>
</footer>
</html>
Valid example
<!DOCTYPE html>
<html lang="en">
<head>
<title>Contact us</title>
</head>
<body>
<main>
<h1>Contact us</h1>
<p>Send us a message and we will reply within two days.</p>
</main>
<footer>
<p>Example Company, 123 Main Street</p>
</footer>
</body>
</html>
A heading, from <h1> to <h6>, that comes after a </body> or </html> end tag is stray, because the HTML parser accepts no more elements once the body has ended. The message names the heading level the validator found, and the cause and fix are the same for every level.
Once the parser reads </body>, only whitespace, comments and the closing </html> tag may follow. Any element after that point is a parse error. Browsers recover by treating the heading as part of the body again, so the page usually looks right and the mistake goes unnoticed. After </html> the validator also gives up: it reports "Cannot recover after last error" and checks nothing further down the page, so other errors stay hidden until you fix this one.
The </body> that ends the body is often not the one at the bottom of the page. A block of HTML included from a standalone file, such as a widget or a newsletter form, can bring its own <html>, <body>, </body> and </html> tags. The parser ignores the extra start tags, which the validator reports a few lines earlier, but it obeys the end tags, so the real body ends in the middle of the page. The heading that opens the next section is then the first element after that point. A template that closes the body before printing the rest of the content has the same effect.
Remove the <html>, <head> and <body> tags from the included block so only its content reaches the page, and keep one </body> and one </html> at the very end of the document. If the heading sits after the page's own </body>, move it inside the body.
Invalid example
<!DOCTYPE html>
<html lang="en">
<head>
<title>News</title>
</head>
<body>
<main>
<h1>News</h1>
<!-- Included newsletter block -->
<html>
<body>
<p>Subscribe to our newsletter.</p>
</body>
</html>
<h2>Latest articles</h2>
<p>New articles appear here every week.</p>
</main>
</body>
</html>
Valid example
<!DOCTYPE html>
<html lang="en">
<head>
<title>News</title>
</head>
<body>
<main>
<h1>News</h1>
<!-- Included newsletter block -->
<p>Subscribe to our newsletter.</p>
<h2>Latest articles</h2>
<p>New articles appear here every week.</p>
</main>
</body>
</html>
A <style> element sits between the closing </head> tag and the opening <body> tag, where the HTML parser doesn't expect any elements.
After </head>, the parser expects the <body> start tag, with nothing but whitespace or comments before it. When it finds a <style> there, browsers put the element back into the <head> and still apply its CSS, so the page usually looks right. The markup is still invalid. A theme or plugin that prints inline CSS right after </head> is a common cause.
The validator treats this error as fatal. Right after it, the report shows "Cannot recover after last error. Any further errors will be ignored." and nothing later in the page gets checked. Other HTML problems below this point stay hidden until you fix it.
To fix it, move the <style> element inside the <head>, before </head>. Since October 2026, the HTML standard also allows a <style> element in the <body>, but only as the first child of its parent and with its rules wrapped in @scope, which limits them to that part of the page. For styles that apply to the whole page, the <head> is the right place.
Invalid example
<!DOCTYPE html>
<html lang="en">
<head>
<title>My page</title>
</head>
<style>
p {
color: navy;
}
</style>
<body>
<p>Hello, world!</p>
</body>
</html>
Valid example
<!DOCTYPE html>
<html lang="en">
<head>
<title>My page</title>
<style>
p {
color: navy;
}
</style>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
A <style> element in the <body> contains a CSS style rule that isn't inside an @scope block.
Since October 2026, the HTML standard allows <style> in the <body> as long as its styles can't reach anything earlier in the page. That means every style rule has to sit inside an @scope block with nothing between @scope and the opening brace. Without a selector, @scope applies to the parent element of the <style> and everything inside it, so p only matches the paragraphs in that parent. A top-level rule like .card p could restyle any paragraph on the page, including ones the browser has already drawn.
Style rules nested in @media, @supports or @container count too. Put those at-rules inside the @scope block, or put an @scope block inside them. At-rules that don't select elements, like @font-face and @keyframes, can stay at the top level. Two other limits apply to a <style> in the <body>: it can't contain @import, and it must be the first child of its parent.
To fix the error, wrap the rules in @scope { } and drop the part of each selector that pointed at the parent. Inside the block, :scope matches the parent element itself. Writing a selector after the keyword, as in @scope (.card), triggers a different error unless that rule is nested in another @scope. If the styles need to apply to the whole page, move the <style> element into the <head>.
Invalid example
<div class="card">
<style>
.card p {
color: darkslategray;
}
</style>
<p>Card content</p>
</div>
Valid example
<div class="card">
<style>
@scope {
:scope {
padding: 1rem;
}
p {
color: darkslategray;
}
}
</style>
<p>Card content</p>
</div>
Validate at scale.
Ship accessible websites, faster.
Automated HTML & accessibility validation for large sites. Check thousands of pages against WCAG guidelines and W3C standards in minutes, not days.
1K Weekly
1,000 monthly checks. Cancel anytime.
Get 1K Weekly →Join teams across 40+ countries