What it finds
TagCheck reads HTML files and reports mistakes a person would notice by looking at the page, or by using it with a keyboard or a screen reader.
- Missing alt text, missing labels, and empty buttons and links.
- Fake buttons: a
<div>or<span>withonclickorrole="button". - Skipped heading levels, duplicate ids, and references to ids that don't exist.
- Classes and
data-tag…attributes that TagBrush.css and TagAction don't document, such as an inventedclass="hero"ordata-tag-sortable. - With
--browser: text contrast below WCAG AA (4.5:1) in light and dark mode, and sideways scrolling at 360px and 1280px wide.
It works on any HTML page, with or without TagBrush.css and TagAction.
page.html:12:3 error missing-alt <img src="photo.jpg"> needs alt text (alt="" if it's decorative)
page.html:20:3 error broken-reference aria-controls="menu" on <button> points to an id that doesn't exist
page.html:26:3 warning unknown-class class="hero" isn't part of TagBrush.css or TagAction: semantic HTML usually does the job
1 file checked: 2 errors, 1 warning
Install and run
You need Node.js 20 or later. Run it with npx on files or folders:
npx @kovirlabs/tagcheck page.html
Get a machine-readable report for agents and CI:
npx @kovirlabs/tagcheck site/ --json
Also check contrast and phone width in a real browser. This needs Playwright:
npm install -D playwright && npx playwright install chromium
npx @kovirlabs/tagcheck page.html --browser
With --browser, pages that link TagBrush.css or TagAction from jsDelivr get the copy installed in your project, if there is one. The check then works offline and tests your version. A stylesheet or script that still doesn't load is reported as not-loaded.
Options
--json- Print the report as JSON.
--browser- Also render each page in Chromium to check contrast and sideways scrolling.
--strict- Fail (exit code 1) on warnings too.
--ignore=a,b- Skip these rules.
--rules- List every rule.
--version- Print the version.
npx @kovirlabs/tagcheck site/ --strict
npx @kovirlabs/tagcheck page.html --ignore=inline-style,style-block
Exit codes
| Code | Meaning |
|---|---|
| 0 | Clean. |
| 1 | Problems to fix. Errors only, or warnings too with --strict. |
| 2 | Bad usage, such as no files given. |
In CI
- run: npx @kovirlabs/tagcheck site/ --strict
The JSON report
--json prints one object for all the files. browser says whether the --browser checks ran. Browser problems have line and column set to null.
{
"tool": "tagcheck",
"version": "0.1.1",
"browser": false,
"files": [
{
"file": "page.html",
"problems": [
{
"line": 6,
"column": 3,
"level": "error",
"rule": "missing-alt",
"message": "<img src=\"photo.jpg\"> needs alt text (alt=\"\" if it's decorative)"
},
{
"line": 7,
"column": 13,
"level": "warning",
"rule": "heading-skip",
"message": "<h4> follows <h2>: don't skip heading levels"
}
]
}
],
"errors": 1,
"warnings": 1
}
Rules
Errors fail the check. Warnings fail it only with --strict. Rules marked --browser run only with that option. The llms.txt guide explains each rule and its fix.
| Rule | Level | What it reports |
|---|---|---|
missing-lang | error | <html> has no lang attribute |
missing-viewport | warning | no viewport <meta>, so phones show a zoomed-out desktop page |
multiple-main | error | more than one visible <main> |
missing-alt | error | <img> without alt text |
unlabeled-field | error | form field with no label, aria-label, or aria-labelledby |
label-for-missing | error | <label for> points to an id that does not exist |
empty-button | error | <button> with no text or aria-label |
empty-link | error | <a href> with no text or aria-label |
clickable-div | error | <div> or <span> used as a button or link |
heading-skip | warning | a heading level is skipped (h2 to h4) |
duplicate-id | error | the same id is used more than once |
broken-reference | error | aria-controls, commandfor, for, list, or similar points to a missing id |
unknown-command | error | command="…" is not a browser command or one TagAction handles |
clone-target | error | command="--clone" must point at a <template> |
remember-needs-id | warning | <details> under data-tag-remember has no id, so its state is not saved |
unknown-class | warning | a class the Tag family does not document |
unknown-attribute | warning | a data-tag… (or TagBrush) attribute or value the Tag family does not document |
generated-markup | warning | markup TagAction generates (data-tag="…") was copied into the source |
inline-style | warning | inline style sets layout or colors instead of CSS variables |
style-block | warning | <style> sets properties other than CSS variables |
contrast | error | text below WCAG AA contrast (4.5:1) in the light or dark scheme (--browser) |
overflow | error | the page scrolls sideways at a phone width (--browser) |
not-loaded | warning | a stylesheet or script did not load, so the browser checks measured the page without it (--browser) |
For AI agents
An agent that can't look at a screen can still run a command. TagCheck gives it the same feedback a person gets from looking at the page.
- Write the page.
- Run
npx @kovirlabs/tagcheck page.html --json. Add--browserif Playwright is installed. - For each problem, fix the HTML at
lineandcolumnas themessagesays. Don't silence rules with--ignoreunless the user asks. - Run it again until
errorsis 0.
Give your agent TagCheck's llms.txt. It lists every rule and its fix. The family guide says how TagBrush.css, TagAction, and TagCheck fit together.
From code
Import check for one HTML string, or checkFiles for files and folders.
import { check, checkFiles } from '@kovirlabs/tagcheck';
check('<img src="a.png">'); // [{ line, column, level, rule, message }, …]
await checkFiles(['site/'], { browser: true }); // the same report as --json
checkFiles(paths, { browser, ignore }) takes files and folders. It skips dot folders and node_modules. ignore is a list of rule names to skip.