99tools

Heading Outline & A11y Linter

runs in your browser

Paste HTML to see its heading outline and landmarks, and find missing alt text, unlabelled fields and links, skipped levels and bad ARIA, with WCAG references.

Paste a page’s HTML or a piece of it, or open an .html file. For a page built by JavaScript, copy the rendered HTML from the browser’s developer tools.

Drop a file here, or .

Read in your browser; nothing is uploaded.

HTML

Checks the heading outline, landmarks, the names of images, links, buttons and fields, ARIA, and, for a whole page, its language, title and zoom.

Report

Show

The report appears here as you paste.

about this tool

The outline a screen reader hears

Screen-reader users get around a page by its headings: a list of them, read out with their levels, is how they find the part they want. The outline here is that list, built from h1 to h6 and from anything given role="heading", at its aria-level or level 2 without one, in the order they appear. A level that jumps, such as a level 3 straight after a level 1, is marked, as the HTML standard asks headings not to skip; so is a heading with no text, and one hidden by hidden, an inline display: none or visibility: hidden, or aria-hidden. A whole page with no headings at all is flagged too.

Landmarks are the other way around a page: banner, navigation, main, complementary, contentinfo, search, and named forms and regions. As HTML-AAM sets out, a header or footer is a landmark only when it is not inside an article, aside, main, nav or section (Chrome counts the same roles on other elements too), an aside inside an article, aside, nav or section only when it is named, and a section or form only when it has a name.

What is checked

Each finding names the rule and, where it is about one element, that element's opening tag and line, with the WCAG 2.2 success criterion it relates to where there is one. Errors fail a criterion; warnings probably do, or make the page harder to use; notices are worth a look.

  • Names. Images without alt text (alt="" is right for decoration), alt text that is just a file name, links and buttons with nothing to read out, form fields and ARIA widgets with no label or only a placeholder, frames without a title. Names follow the accessible name computation (accname 1.2 and HTML-AAM), simplified where it depends on CSS: aria-labelledby, then aria-label, then the element's own label, alt text or content, then title, then placeholder.
  • ARIA. Roles that do not exist or are abstract, aria- attributes that do not exist or are deprecated (aria-labeledby is a common misspelling), aria-labelledby or label for pointing at an id not on the page, and other references that do, which may be fine if a script adds the target later.
  • Keyboard. A positive tabindex, which reorders the tab sequence, and anything focusable inside aria-hidden, which focus can reach but a screen reader will not announce.
  • The whole page, when you paste one: the lang attribute, the title, a viewport that blocks or limits zooming, a main landmark, and a level-one heading.
  • Also: ids used twice, link text such as "read more" that means nothing out of context, and audio or video set to play by itself without muted or controls.

The line number is found by counting tags in what you pasted, and is left out wherever that count, or the attributes written there, do not match what the browser's parser built, as when it moves stray content out of a table.

What it cannot see

The markup is read by your browser's own HTML parser, but no script runs and no stylesheet is applied. Colour contrast, visible focus, text that only fits at one size, keyboard traps and anything a script adds after load are outside what any markup check can know; a page built in the browser should be checked from the rendered HTML, copied from the developer tools. Passing here is a good start rather than a pass for the page. For colour, the contrast checker measures text against its background.

Nothing you paste is stored or sent anywhere.

questions

Why do heading levels matter for accessibility?
Screen-reader users move around a page by its headings, much as a sighted reader scans it, and the levels tell them what belongs under what. Skipping from a level 1 straight to a level 3 suggests a missing section, and a heading made of bold text rather than a heading element is not in that list at all. WCAG 1.3.1 asks that structure shown visually is also in the markup; the rule against skipping levels comes from the HTML standard rather than WCAG.
Is more than one h1 an error?
No. HTML allows it and WCAG does not forbid it, so the tool reports it as a notice. One level-one heading that names the page is still the easiest outline to navigate. The old outline algorithm, which would have nested an h1 inside a section, never reached the accessibility tree and was dropped from HTML in 2022, and browsers have since removed the smaller default styles too; every h1 is exposed as level 1, as it always was.
What does the checker look at, and what can it not see?
It reads the markup: headings, landmarks, the accessible names of images, links, buttons and form fields, ARIA roles, attributes and id references, tabindex, the page language, title and viewport zoom. It applies no stylesheet and runs no script, so it cannot judge colour contrast, whether focus is visible, keyboard traps or anything a script adds later. Those need a person, or a browser-based tool, to try the page.
Why is a placeholder not a label?
It disappears as soon as someone starts typing, leaving them to remember what the field was for, and it is usually shown in low-contrast grey. Browsers do use it as the accessible name when there is nothing else, so it is reported as a warning rather than a missing label: a persistent, visible <label> is the safer way to meet WCAG 3.3.2, Labels or Instructions.
Is the HTML I paste sent anywhere?
No. It is parsed by your own browser’s HTML parser in the page; no script in it runs, nothing is uploaded, and closing the tab discards it.