Skip to main content

Home › Tools › HTML Linter

Free Online HTML Linter

Paste your HTML and get a line-by-line quality report: missing alt text, duplicate ids, deprecated tags, skipped heading levels, unbalanced tags and 24 more checks — plus an accessibility score. No signup, no uploads.

Quality linter, not a spec validator. This tool checks how well your HTML is written (best practices & accessibility). To check whether markup conforms to the HTML5 specification, use the W3C Nu HTML Checker instead.
Private by design: everything runs locally in your browser with JavaScript. Your HTML never leaves this tab — zero network requests.
Choose rules to check (29 available)
Your HTML click an issue line number to jump there
Lint results

Press Lint HTML to analyze your markup.

Worked examples

Each example loads into the editor with one click. The issue counts below are computed live by the linter engine itself.

Missing alt text + deprecated tags

1 error(s), 2 warning(s), 0 info note(s) \u2014 computed by the linter engine

<!DOCTYPE html>
<html lang="en">
<head>
<title>Gallery</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<center>
<font size="4">Welcome to our gallery</font>
<img src="hero.jpg">
<img src="logo.png" alt="">
</center>
</body>
</html>

Unclosed & mismatched tags

2 error(s), 0 warning(s), 0 info note(s) \u2014 computed by the linter engine

<!DOCTYPE html>
<html lang="en">
<head>
<title>Broken page</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div class="wrap">
<p>Hello, world!
</div>
</span>
</body>
</html>

Missing document basics

0 error(s), 4 warning(s), 0 info note(s) \u2014 computed by the linter engine

<html>
<body>
<h1>Hello</h1>
<p>No doctype, no lang, no title, no viewport.</p>
</body>
</html>

Duplicate ids

2 error(s), 0 warning(s), 0 info note(s) \u2014 computed by the linter engine

<!DOCTYPE html>
<html lang="en">
<head>
<title>IDs</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div id="main">First</div>
<p id="main">Second</p>
<span id="main">Third</span>
</body>
</html>

Skipped heading levels

0 error(s), 3 warning(s), 0 info note(s) \u2014 computed by the linter engine

<!DOCTYPE html>
<html lang="en">
<head>
<title>Headings</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<h1>Page title</h1>
<h3>Skipped h2</h3>
<h1>Second h1</h1>
<h5>Skipped h2-h4</h5>
</body>
</html>

Buttons without type + inline style overuse

0 error(s), 13 warning(s), 0 info note(s) \u2014 computed by the linter engine

<!DOCTYPE html>
<html lang="en">
<head>
<title>Form</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<form>
<button>Submit</button>
<button style="color:red">A</button>
<button style="color:blue">B</button>
<button style="color:green">C</button>
<button style="color:black">D</button>
<button style="color:gray">E</button>
<button style="color:pink">F</button>
</form>
</body>
</html>

Uppercase tags & sloppy attributes

0 error(s), 1 warning(s), 6 info note(s) \u2014 computed by the linter engine

<!DOCTYPE html>
<html lang="en">
<head>
<title>Case</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<DIV CLASS="box" ID='x'>
<!-- TODO: clean this up -->
<P>Fish & chips</P>
</DIV>
</body>
</html>

Invalid nesting

2 error(s), 0 warning(s), 0 info note(s) \u2014 computed by the linter engine

<!DOCTYPE html>
<html lang="en">
<head>
<title>Nesting</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<a href="/x"><button type="button">Click</button></a>
<p>Some text <div>a block</div> more text</p>
</body>
</html>

Clean document (zero issues)

0 error(s), 0 warning(s), 0 info note(s) \u2014 computed by the linter engine

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Clean page</title>
</head>
<body>
<h1>Hello</h1>
<h2>Section</h2>
<p><img src="a.png" alt="A small icon"> <a href="/about">About</a></p>
<button type="button">OK</button>
</body>
</html>

Rules reference

RuleSeverityWhat it checks
mismatched-tagerrorA closing tag does not match the currently open element (e.g. <div> closed by </span>).
unclosed-tagerrorAn element was opened but never closed before the end of the document.
unexpected-closeerrorA closing tag with no matching open tag anywhere in the document.
duplicate-iderrorThe same id value is used on more than one element; ids must be unique per page.
duplicate-attributeerrorThe same attribute name appears twice on one element.
missing-alterror<img> without an alt attribute. Screen readers and search engines need it; use alt="" only for purely decorative images.
img-missing-srcerror<img> without a src attribute will render as a broken image.
unclosed-commenterror<!-- without a matching --> swallows the rest of the document.
invalid-nestingerrorElements nested in a way HTML forbids: interactive content inside <a>/<button>, <p> inside <p>, or block elements inside <p>.
doctype-firstwarningThe document should start with <!DOCTYPE html> so browsers use standards mode.
missing-langwarning<html> needs a lang attribute (e.g. lang="en") so screen readers pronounce text correctly.
missing-titlewarningEvery document needs a <title> in <head> for the tab label, bookmarks and SEO.
empty-titlewarningA <title> element is present but has no text.
missing-viewportwarningNo <meta name="viewport"> — the page will not scale properly on mobile devices.
deprecated-tagwarningObsolete presentational tags (center, font, marquee, big, strike, tt, applet, ...) — use CSS instead.
heading-order-skipwarningHeadings must not skip levels (h1 followed by h3) — screen readers use heading order for navigation.
multiple-h1warningMore than one <h1> on the page weakens the document outline; use one h1 per page.
button-missing-typewarning<button> without type="button|submit|reset" defaults to submit and can submit forms unexpectedly.
inline-style-overusewarningMore than 5 elements use inline style="..." — move presentation to a stylesheet for maintainability.
unquoted-attributewarningAttribute values should be wrapped in quotes; unquoted values break on spaces and special characters.
unescaped-ampersandwarningA bare & in text must be written as &amp; (or another entity) to avoid parsing surprises.
tagname-uppercaseinfoHTML convention is lowercase tag names.
attr-uppercaseinfoHTML convention is lowercase attribute names.
single-quote-attributeinfoAttribute uses single quotes; double quotes are the common convention.
todo-commentinfoComment contains TODO, FIXME, XXX, HACK or BUG — consider resolving before shipping.
trailing-whitespaceinfoLine ends with spaces or tabs.
line-too-longinfoLine exceeds 200 characters — hard to review and diff.
placeholder-linkinfo<a href="#"> is a placeholder; keyboard and screen-reader users get a dead link.
empty-documentinfoThere is nothing to lint.

Frequently asked questions

Is this HTML linter a replacement for the W3C validator?

No. The W3C Nu HTML Checker answers a different question: is this markup valid HTML5 according to the specification? This linter answers: is this markup well-written? It checks style, best practices and accessibility — missing alt text, skipped heading levels, deprecated tags, duplicate ids, buttons without a type — things a validator does not grade you on. Use both: validate for correctness, lint for quality.

Which rules does the HTML linter check?

29 rules across three severities. Errors: mismatched tags, unclosed tags, unexpected closing tags, duplicate ids, duplicate attributes, images missing alt text, images missing src, unclosed comments and invalid nesting. Warnings: missing or misplaced doctype, missing lang on <html>, missing or empty <title>, missing viewport meta, deprecated tags (center, font, marquee and 14 more), skipped heading levels, multiple <h1>, buttons without a type, inline style overuse, unquoted attributes and unescaped ampersands. Info notes: uppercase tag/attribute names, single-quoted attributes, TODO comments, trailing whitespace, long lines, placeholder links and empty documents. The full list with descriptions is in the rules reference table below.

Is my HTML uploaded to a server?

No. The linter is 100% client-side JavaScript: tokenizing, rule checks and the accessibility score all run locally in your browser tab. There are zero network requests — no uploads, no signup, no file-size gate imposed by a server. You can even disconnect from the internet after the page loads and it keeps working.

What accessibility issues does it catch?

Nine rules feed the accessibility score: images missing alt text, <html> missing a lang attribute, skipped heading levels, multiple <h1> elements, <button> without a type, deprecated presentational tags, duplicate ids, and missing or empty <title>. Each one maps to a real barrier: screen readers announce images by their alt text, use heading order and the lang attribute to navigate and pronounce content, and depend on unique ids for form labels and skip links. Fixing these also helps SEO, since search engines use the same signals to understand your page.

How is the accessibility score calculated?

The score starts at 100 and deducts 10 points per accessibility error, 4 per warning and 1 per info note, floored at 0. Only the nine accessibility-related rules count — pure style notes like trailing whitespace never touch your score. The result is graded A (90+) through F (below 60). Treat it as a triage signal, not a certification: a page can score 100 and still have usability problems no automated check can see.

Can the linter fix my HTML automatically?

No — deliberately. Unlike formatting, most lint fixes need human judgment: only you can write a good alt description or choose the right heading level. Instead of guessing, every issue explains exactly what is wrong and how to fix it, with the precise line number. Use the Copy report button to paste the full issue list into your editor or a pull request.

Why doesn't the linter use the browser's built-in HTML parser?

Because the browser's parser is too forgiving to be a linter. DOMParser silently repairs broken markup — unclosed tags get auto-closed, misplaced elements get moved — so a parser-based check literally cannot see the errors you most need to find. This linter uses its own hand-rolled tokenizer, so it keeps going after an error, reports every issue with an exact line number, and checks style and accessibility rules that a parser never looks at.

What are the limitations of this HTML linter?

It is a heuristic linter, not a specification validator: it does not check your markup against the HTML5 spec the way the W3C checker does. It never fetches external resources, never executes JavaScript or CSS, and assumes the snippet you paste is the document you mean (a fragment without <html> will flag missing doctype/lang/title — paste full documents for document-level rules). On severely broken markup the error recovery is best-effort, so fix fatal tag-balance errors first and re-lint.

Related tools

Free tool by NRTechStudio