What does your page's heading outline actually look like?
Enter your domain. We read the HTML your server sends and rebuild the h1–h6 outline a screen reader navigates by — indented, in document order, with every heading that announces nothing, every level that skips, a missing h1 and every heading hidden from assistive technology marked in place.
Free, instant, no signup and no card. Nothing is stored.
Free, instant, no signup and no card. We read the HTML your server sends for that one page and rebuild the heading outline a screen reader navigates by. Nothing is stored.
The one accessibility failure with no visual symptom
Most accessibility problems announce themselves eventually. Thin contrast looks thin. A tiny tap target is annoying on your own phone. A broken form gets a support ticket. A broken heading outline looks like nothing at all — the page renders exactly as designed, the type sizes step down the way the mockup said, and every review it has ever had has passed it. The structure is only visible to software, and the people who depend on it are the ones not looking at the screen.
That matters more than the criterion list suggests, because headings are not decoration for a screen-reader user — they are the navigation. Screen readers build a list of every heading on the page and let people jump straight between them, and when users are surveyed about how they find things on an unfamiliar page, headings come first. A page whose outline starts at level 3, or where the section titles are styled divs, has no navigation at all: the only way through it is from the top, every time.
It is also the check with two audiences. Search crawlers read the same outline to work out what a page is about and which parts of it answer which question, which is why h1 checker and heading structure are searched by people who have never opened a WCAG document. Both audiences want the same fix, and the picture below is the same picture.
What the checker looks for
Three of these are failures under a named success criterion. The rest are real problems that WCAG does not actually forbid — nothing in the specification says a level may not skip or that a page must have exactly one h1 — so they are reported separately and never counted as violations. A checker that inflates its own failure count is selling you something.
- Heading with no text in itFailureWCAG 2.1 SC 1.3.1 Info and Relationships / SC 2.4.6 Headings and Labels (Level A)
- The element carries a heading role and has nothing to announce — no text, no aria-label, no nested image with alt text. It still appears in the rotor list a screen-reader user navigates by, as a blank row they can jump to and learn nothing from. Nearly every one of these is a spacer: an <h2> used to get a margin, or a heading left in place after its content moved into a component.
- The fix: If it is decoration, make it a <div> and style it. If it is a real section heading whose words live in an image or an icon, give the element the words — aria-label on the heading, or alt text on the image inside it.
- Heading that is an image with no alt textFailureWCAG 2.1 SC 1.1.1 Non-text Content / SC 2.4.6 Headings and Labels (Level A)
- The heading's entire content is an image, and that image has no alt attribute. This is the classic wordmark-as-heading: a logotype or a rendered display typeface set as a picture. Visually it is the most prominent thing on the page; in the outline it is a blank row.
- The fix: Put the heading's words in the alt attribute of the image — alt="Pricing", not alt="pricing-header.png". If the words also exist as text elsewhere in the heading, mark the image alt="" so it is skipped instead of announced twice.
- role="heading" with no aria-levelFailureWCAG 2.1 SC 1.3.1 Info and Relationships / SC 4.1.2 Name, Role, Value (Level A)
- An element declares itself a heading through ARIA but never says which level it is. The specification gives no default a browser can rely on; in practice assistive technologies disagree, and some drop the element out of the outline entirely. The page looks structured and navigates as if the section were not there.
- The fix: Add aria-level="2" (or whichever level the section really is). Better, use the native <h2> — it carries the role and the level together and cannot fall out of step with the design.
- No level-1 heading on the pageReviewWCAG 2.1 SC 1.3.1 Info and Relationships (Level A) — supporting technique
- Nothing on this page announces what the page is. A screen-reader user opening the rotor sees the outline start partway down, and has to infer the subject from the first h2 they meet. No success criterion says a page must have exactly one h1 — which is why this is in the review band and not the failure count — but every automated tester reports it, and it is the single line most often missing from an otherwise clean page.
- The fix: Give the page one h1 carrying the same words as its title. On a template this usually means the page heading was built as a styled div, or the h1 is in a header component that this route does not render.
- More than one level-1 headingReviewWCAG 2.1 SC 1.3.1 Info and Relationships (Level A) — supporting technique
- The page declares itself to be about several things at the top level. HTML5 permits this — sectioning elements were supposed to scope it, and browsers never implemented that scoping — so it is not a failure. In practice it flattens the outline: a listing page where every card title is an h1 gives a screen-reader user twenty equally-weighted entries and no way to tell the page's subject from an item on it.
- The fix: Keep one h1 for the page itself and demote the others to the level they really sit at — card titles on a listing page are usually h2 or h3.
- Heading level skippedReviewWCAG 2.1 SC 1.3.1 Info and Relationships (Level A) — supporting technique
- The outline jumps down more than one step — an h2 followed directly by an h4, for instance. A screen-reader user navigating by level hears a subsection announced at a depth with no parent, which reads as missing content rather than as a design choice. Almost always the level was picked for its font size rather than its position.
- The fix: Size headings with CSS and choose the tag by depth. If the h4 exists because the h3 style is too big, change the style, not the tag.
- Heading hidden from assistive technologyReviewWCAG 2.1 SC 1.3.1 Info and Relationships (Level A)
- The heading carries aria-hidden="true" or the hidden attribute, so it is in the visual design and absent from the outline. Sometimes that is deliberate and correct — a duplicated mobile header, a heading inside a closed accordion panel. Sometimes it means the visible structure of the page and the structure a screen reader gets are two different documents, which is what SC 1.3.1 is about.
- The fix: Check whether this heading is visible on screen. If it is, remove the attribute — hiding text that is on display is the one case that is never right. If it is genuinely a duplicate, leave it.
- Heading longer than a headingReviewWCAG 2.1 SC 2.4.6 Headings and Labels (Level AA)
- Over 120 characters, and almost certainly a paragraph that was given a heading tag for emphasis. Level AA asks headings to describe the section that follows; a sentence is hard to scan in a rotor list of thirty of them, and if it is not actually a section heading it should not be in that list at all.
- The fix: If it introduces a section, shorten it to the section's topic and move the rest into the body text. If it is body text that just needed to look bigger, make it a <p> with a class.
- Heading declared with role="heading"Verify
- This is valid, has a level, and is never a violation — it is listed so you can see where the outline depends on ARIA rather than on the markup. The risk is drift: a native <h2> carries its level in the tag, whereas an aria-level is a number in an attribute that no one updates when the section moves. If the element could be a real heading tag, it is safer as one.
- The fix: Swap it for the matching native heading tag where the layout allows it. Where it cannot be (a heading inside a grid cell, say), keep the aria-level in the same component as the styling so they move together.
How to choose a heading level, in one rule
Pick the tag by where the section sits, and set the size in CSS. That is the whole rule, and nearly every broken outline is a page where it was inverted — the tag was chosen for its font size and the position followed.
- One
h1, naming the page. Usually the same words as the title tag. If your template puts the site name in an h1 in the header, that is the bug: every page then announces itself as the company rather than as the page. h2for each section of the page. On a listing page, that is each card. On an article, each part of the argument.h3and down only inside anh2, and only when the section really has subsections. Going back up is always fine — h4 followed by h2 closes two sections and opens one. Only going down more than a step is a jump.- Nothing that is not a heading gets a heading tag. A large paragraph, a pull quote, a price, a stat — those are styled text. If it goes in the tag, it goes in the navigation list of everyone who moves through the page by structure.
A styled div is not on that list either, in the other direction: it looks like a heading and is not one. If the layout genuinely cannot take a heading tag, role="heading" with an aria-level is valid and this tool treats it as such — it just asks you to look at it, because a level in an attribute drifts in a way a tag cannot.
Common questions
- What does this checker actually read?
- One URL, once. It fetches the page you enter, takes the HTML your server returns, and walks every <h1> through <h6> plus every element carrying role="heading", in document order. For each one it asks what a screen reader asks: does this have anything to announce, what level is it, and does that level follow on from the one before. It does not render the page, run your JavaScript or crawl beyond the address you typed; all three of those are the full scan, which is a different tool on this site and also free.
- Is skipping a heading level actually a WCAG failure?
- No, and you should be suspicious of any tool that tells you it is. There is no success criterion in WCAG 2.1 that says heading levels must not skip. What SC 1.3.1 requires is that the structure conveyed visually is also conveyed programmatically — so if a section visually reads as a subsection of the one above it and is marked h4 under an h2, the relationship is misreported and that is arguable. This checker keeps skipped levels in a review band, out of the failure count, and tells you the argument rather than the verdict. Only three things here are called failures, and each is a heading that announces nothing at all.
- Do I need exactly one h1?
- One is the sane default and it is not a rule. HTML5 intended sectioning elements to scope heading levels, so multiple h1s inside <section> and <article> would nest properly — browsers and screen readers never implemented that, so the outline you get is flat. In practice: one h1 naming the page, everything else below it. The place this matters most is a listing page where every card title was built as an h1; a screen-reader user then gets twenty top-level entries and no way to tell the page's subject from an item on it.
- Why does an empty heading matter if nobody can see it?
- Because the people it affects are not looking at it. Screen readers build a rotor list of every heading on the page and most users navigate by jumping between them rather than reading top to bottom — surveys of screen-reader users have put headings first among the ways people find things on a page, for as long as the question has been asked. An empty heading is a row in that list with nothing in it: something to land on, with no information, and no way to tell whether the content they wanted is underneath it. It also has no visual symptom whatsoever, which is exactly why it survives every design review the page ever gets.
- It found no headings, but my page clearly has them. What happened?
- Almost always client-side rendering. If the page's content is built by React, Vue or an embedded widget after the document arrives, the headings are not in the HTML your server sent and this reader cannot see them — the same limitation as the form label checker and the alt text checker on this site, and the same honest answer. The free scan renders each page in a real browser before measuring it, so it sees what your visitors see. Run that one instead.
- My headings are styled divs. Does that count?
- Not as headings, no — and that is the single most common way a page ends up with no outline at all. A <div class="text-3xl font-bold"> is text that happens to be large. It carries no role, appears in no rotor list, and is skipped by every heading-navigation shortcut. Adding role="heading" and aria-level fixes it and is reported here as something to verify rather than as a fault, because it is valid. Using the real tag is still better: the level lives in the tag, so it cannot drift out of step when the section moves.
- Does a long heading fail anything?
- It is advisory. SC 2.4.6 at level AA asks headings and labels to describe the topic or purpose, which is a quality bar rather than a length limit, so this one is in the review band and matched purely by character count. The reason it is worth surfacing is that a heading over about 120 characters is usually not a heading at all — it is a sentence that was given a heading tag to make it look important, and it is now sitting in the navigation list of everyone who moves through the page by structure.
- Does this store my domain?
- No. The check is a single HTTP GET of the page you name, run inside the request and discarded when it answers. There is no account, no email field and no row written anywhere. If you go on to run the free scan, that one does keep a report so you can come back to it, which is the whole point of a report.