Free Online HTML Validator

Paste HTML below to check for unclosed tags, mismatched closing tags, and duplicate IDs. Runs entirely in your browser.

How to validate HTML online

  1. 1

    Paste your HTML

    Copy in the markup you want to check, from a template, component, or page source.

  2. 2

    Click "Validate"

    The tool scans the tag structure and checks for duplicate IDs.

  3. 3

    Read the result

    A green checkmark means no structural issues were found. Otherwise, each issue lists the tag and what's wrong with it.

  4. 4

    Fix and re-check

    Correct the flagged tags and validate again until you get a clean pass.

Example use cases

1

Debugging broken layout

Find the unclosed or mismatched tag that's causing content to render in the wrong place.

2

Reviewing a pasted template

Check markup copied from a design tool or CMS export for structural issues before using it.

3

Catching duplicate IDs

Find repeated id attributes that can break anchor links, labels, or JavaScript that targets an element by ID.

Frequently asked questions

What does this HTML validator check?

It scans your markup's tags and reports unclosed elements, closing tags that don't match the most recently opened tag, stray closing tags with no matching opener, and duplicate id attributes.

Is this a full W3C HTML validator?

No. This checks tag structure and duplicate IDs only — it doesn't check attribute validity, deprecated elements, accessibility rules, or the full HTML specification the way the W3C Markup Validator does.

Why does it flag tags inside <script> or <style>?

It doesn't — content inside <script>, <style>, and HTML comments is skipped before the tag scan runs, so code and comments containing angle brackets won't produce false positives.

Is my markup uploaded anywhere?

No. Validation runs entirely in your browser using JavaScript — nothing you paste is sent to a server.

') .replace(/]*>[\s\S]*?<\/style>/gi, ''); } function checkHtmlStructure(html) { const issues = []; const cleaned = stripNonTagContent(html); const tagRe = /<\/?([a-zA-Z][a-zA-Z0-9-]*)\b([^>]*)>/g; const stack = []; let match; while ((match = tagRe.exec(cleaned)) !== null) { const full = match[0]; const tag = match[1].toLowerCase(); const attrs = match[2] || ''; const isClosing = full.startsWith(' s.tag).lastIndexOf(tag); if (openIdx === -1) { issues.push(`Closing tag </${tag}> doesn't match any open tag (expected </${top.tag}>).`); } else { const unclosed = stack.slice(openIdx + 1).map((s) => s.tag); issues.push(`<${unclosed.join('>, <')}> ${unclosed.length > 1 ? 'were' : 'was'} opened but not closed before </${tag}>.`); stack.length = openIdx; } } } else if (!isSelfClosing) { stack.push({ tag }); } } if (stack.length > 0) { const unclosed = stack.map((s) => s.tag); issues.push(`<${unclosed.join('>, <')}> ${unclosed.length > 1 ? 'were' : 'was'} never closed.`); } return issues; } function checkDuplicateIds(html) { const cleaned = stripNonTagContent(html); const seen = new Map(); const idRe = /\bid\s*=\s*["']([^"']+)["']/gi; let match; while ((match = idRe.exec(cleaned)) !== null) { const id = match[1]; seen.set(id, (seen.get(id) || 0) + 1); } return Array.from(seen.entries()) .filter(([, count]) => count > 1) .map(([id, count]) => `id="${id}" is used ${count} times — IDs should be unique.`); } function validateHtml() { const input = document.getElementById('input').value; const result = document.getElementById('result'); if (!input.trim()) { result.innerHTML = '
✗ Nothing to validate — paste some HTML first.
'; return; } const issues = [...checkHtmlStructure(input), ...checkDuplicateIds(input)]; if (issues.length === 0) { result.innerHTML = '
✓ No structural issues found
'; return; } const list = issues.map((i) => `
  • ${i}
  • `).join(''); result.innerHTML = `

    ✗ ${issues.length} issue${issues.length > 1 ? 's' : ''} found

      ${list}
    `; }