Testing and Validating HTML
Prerequisites: Accessibility basics
Browsers never tell you your HTML is wrong. Tools do — and they're easy to run automatically.
The Nu Html Checker on the command line
The checker behind validator.w3.org/nu is a Java program, vnu.jar,
available from the validator project's GitHub releases (and as the npm
package vnu-jar):
java -jar vnu.jar index.html about.html
java -jar vnu.jar --errors-only --skip-non-html dist/
java -jar vnu.jar --also-check-css --format json index.html
It exits with a non-zero status when it finds errors, so it can fail a CI build. Given the file below, it reports the messages that follow it (message text only; the real output also gives the file name, line, and column):
<!-- Wrong: three mistakes -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Test</title>
</head>
<body>
<main>
<h1>Test</h1>
<img src="a.png">
<a href="/"><button>Home</button></a>
<label for="missing">Name</label>
</main>
</body>
</html>
error: An 'img' element must have an 'alt' attribute, except under certain conditions. For details, consult guidance on providing text alternatives for images.
error: The element 'button' must not appear as a descendant of the 'a' element.
error: The value of the 'for' attribute of the 'label' element must be the ID of a non-hidden form control.
The third message is about the label whose for points to an id that
doesn't exist.
Linters
- html-validate (npm) — runs in Node.js with configurable rules, including many accessibility and best-practice checks, and supports template files.
- HTMLHint — a lighter linter with editor integrations.
- Framework linters:
eslint-plugin-jsx-a11y(React),eslint-plugin-vuejs-accessibility.
Automated accessibility tests
axe-core (by Deque) finds problems like missing names, bad contrast, and invalid ARIA. Run it as a browser extension, with Lighthouse, or in end-to-end tests. With Playwright:
npm install --save-dev @playwright/test @axe-core/playwright
import { test, expect } from "@playwright/test";
import AxeBuilder from "@axe-core/playwright";
test("home page has no detectable accessibility violations", async ({ page }) => {
await page.goto("http://localhost:8000/");
const results = await new AxeBuilder({ page }).analyze();
expect(results.violations).toEqual([]);
});
Automated tools find perhaps a third of accessibility problems. Keyboard testing and screen-reader testing are still necessary.
Test the behavior, not just the markup
- Tab through every page; operate every widget with the keyboard.
- Check the heading outline and landmarks (browser extensions list them).
- Test forms: validation messages, error recovery, autofill.
- Test with images and CSS turned off — the content should still make sense in order.
- Zoom to 200% and 400%.
- Query elements by role and name in tests
(
page.getByRole("button", { name: "Save" })): if a test can't find a button by its accessible name, neither can a screen-reader user.