Docs menu

HTML markup must be valid

WARN Frame web/html-markup-valid
On this page

HTML markup validation pass - the class of problems Vite, the HTML5 spec, and downstream tooling (RSS readers, AMP validators, social-card scrapers) complain about:

  • Unclosed tags - <div> with no matching </div> before EOF
  • Mismatched closing tags - <div><span></div></span> (the </div> closes the <span> implicitly, then </span> closes nothing)
  • Duplicate IDs - two elements with the same id= attribute on the same page

Browsers tolerate most of these via the HTML5 error-recovery algorithm; downstream parsers don’t.

How it works#

Uses the golang.org/x/net/html tokenizer to walk the file. Void elements (<br>, <img>, <input>, etc.) are recognized as auto-closed. Foreign content (<svg>, <math>, <script>, <style>, <template>) is skipped for balance checking because those subtrees have different parsing rules.

For Svelte / Astro files: the framework wrapper (script blocks, frontmatter) is stripped before tokenization, so the report is about the user-visible HTML body.

Common cases this catches#

<!-- Unclosed tag -->
<section>
  <p>Content
</section>

<!-- Mismatched close -->
<div>
  <span>Text</div>
</span>

<!-- Duplicate ID -->
<input id="email" />
<label for="email">Email</label>
<input id="email" />   <!-- the second `email` is the bug -->

Each surfaces as a separate Hit with the line and a description.

What it does NOT catch#

  • Semantic HTML. <button> vs <a> vs <div onclick> is a separate frame’s job.
  • Invalid attribute combinations (e.g. <input type="checkbox" maxlength="10">).
  • Spec compliance. This isn’t a substitute for Nu Html Checker on production builds - call that from CI for a stricter pass.

Site generator templates#

Template fragments of a static site generator are skipped: files under Hugo’s layouts/ (next to hugo.toml, config.toml or another Hugo config) and Jekyll’s _layouts/ and _includes/ (next to _config.yml). They are pieces of a page, not pages; the built output (for example public/) is checked instead. A layouts/ folder without the generator’s config beside it is checked as usual.

Suppressing intentional cases#

Per-file for templates that deliberately leave tags open (rare but legitimate - e.g. partial fragments composed at runtime):

<!-- appframes:disable web/html-markup-valid -->

Per-line is supported via appframes:disable-next-line web/html-markup-valid but rarely useful - markup issues usually span multiple lines.

WARN lets the push through and records the finding. Turn frames on per repo on the dashboard's Policy page - see choosing what the gate checks.

Source on GitHub Live demo How it works Questions: contact@nimblegate.com