H1 tag and heading hierarchy: what matters, and how to check
The H1 is the page's main heading: the one line that says what the page is about. Give every page one clear, visible H1, usually close to the title tag. Use H2 to H6 for sections beneath it, in order, without jumping levels. Google says heading order doesn't matter for Search and there's no ideal number of headings, but a clear main heading helps it pick your title, and a tidy outline is how screen reader users find their way around a page.
What the H1 is for
The <h1> is the headline at the top of the content, like the title of a chapter. It tells visitors they're in the right place, gives screen reader users the page's topic, and is one of the places Google looks when it writes the title link for search results.
It's not the same as the title tag. The <title> is what shows in the browser tab and search results; the H1 is what shows on the page. They usually say similar things, and Google uses the main heading as one of the sources when it creates a title link (see title tag length).
What Google says about headings
Here's what Google actually says:
- Order doesn't affect Search. Google's starter guide says it doesn't matter if headings are used out of order, though order helps screen readers.
- There's no ideal number. Google says there's no magical, ideal amount of headings a page should have.
- A clear main heading helps titles. Google's title link guidance recommends one prominent main heading, such as the first visible
<h1>, that stands out from the rest of the page.
So treat skipped levels as an accessibility and clarity fix, not a ranking fix. A missing or empty H1 matters more: the page loses its clearest statement of what it's about.
A good heading outline
Headings form an outline of the page, like a table of contents. Each level sits inside the one above it:
Going back up is fine: from an H3 to the next H2 starts a new section. It's jumping down more than one level, H2 straight to H4, that leaves a gap. The W3C says to avoid that where possible.
Skips usually happen because a heading level was chosen for its size, not its place. If an H3 is too big for a sidebar, change its size with CSS, not its level.
The common problems
<div> or <p>) rather than a heading. An H1 added only by JavaScript is fine for Google, which renders pages, but invisible to most AI crawlers; see JavaScript SEO.How to check by hand
1Print the outline 30 seconds
Open DevTools (F12), go to the Console and paste:
// Prints the heading outline a screen reader meets, and flags missing, empty or multiple H1s and skipped levels
const all = [...document.querySelectorAll('h1, h2, h3, h4, h5, h6, [role="heading"][aria-level]')];
const shown = h => h.checkVisibility({ visibilityProperty: true }) && !h.closest('[aria-hidden="true"]');
const heads = all.filter(shown);
const nameOf = h => {
const ids = (h.getAttribute('aria-labelledby') || '').split(/\s+/).filter(Boolean);
const byIds = ids.map(id => document.getElementById(id)?.textContent || '').join(' ');
const alts = [...h.querySelectorAll('img[alt]')].map(img => img.alt).join(' ');
return (byIds.trim() || (h.getAttribute('aria-label') || '').trim() || h.innerText + ' ' + alts).replace(/\s+/g, ' ').trim();
};
let prev = 0;
const rows = heads.map(h => {
const level = Number(h.getAttribute('aria-level')) || Number(h.tagName[1]);
const text = nameOf(h);
const notes = [];
if (!text) notes.push('EMPTY');
if (prev && level > prev + 1) notes.push('SKIPS from H' + prev);
if (!/^H\d$/.test(h.tagName)) notes.push('role="heading"');
prev = level;
return { heading: ' '.repeat(level - 1) + 'H' + level + ' ' + text.slice(0, 60), note: notes.join(', ') };
});
console.table(rows);
const h1s = heads.filter(h => h.tagName === 'H1').length;
console.log(h1s === 0 ? 'No visible H1' : h1s > 1 ? h1s + ' visible H1s: check one is clearly the main heading' : 'One visible H1');
const hidden = all.filter(h => !shown(h) && /^H\d$/.test(h.tagName)).length;
if (hidden) console.log(hidden + ' hidden heading(s) not listed. SEOSnip leaves these out too.');
It prints every visible heading, indented by level, marks empty headings and levels that skip, and says how many H1s there are.
It lists the headings a screen reader meets: anything hidden with CSS, the hidden attribute or aria-hidden is left out, and it tells you how many it left out. SEOSnip leaves the same hidden headings out, so a hidden H1 in a closed mobile menu isn't counted as a second H1. Headings hidden only visually (a "visually hidden" class) are still listed, because screen readers read them. Headings inside shadow DOM aren't listed.
2Look at the accessibility tree 1 minute
In Chrome DevTools, open the Elements panel, then the Accessibility pane, turn on Enable full-page accessibility tree, click Reload DevTools, then click the accessibility tree button at the top right of the Elements panel. Headings show as "heading" with their level, in page order.
3Navigate by headings 3 minutes
With a screen reader on, jump from heading to heading (the H key in NVDA and JAWS; in VoiceOver on a Mac, open the rotor with VO+U and choose Headings). If the list doesn't read like a sensible table of contents, the outline needs work.
How crawlers report this
If a crawl sent you here, these are the labels to look for. Our guide to Screaming Frog and Sitebulb issues covers other common ones.
| Screaming Frog | Sitebulb | What it means |
|---|---|---|
| H1: Missing | <h1> tag is missing | The page has no main heading. |
| H1: Multiple | Multiple <h1> tags | More than one H1. Often harmless, but worth a look on key templates. |
Doing this on every page?
SEOSnip checks headings for you
Open the extension on any page and it reads every heading. You get:
- A missing H1, or an empty one. An H1 with a logo that has alt text, or an aria-label, counts as having text
- More than one H1, listed for review rather than as an error
- Each place the content's outline jumps down more than one level, with the headings around it and the jump labelled (for example H2 → H4, skips H3). Headings in the footer, menus, sidebars and popups are left out of this check, because they rarely follow the content's levels
- Headings hidden from everyone, such as an H1 in a closed menu or modal, are left out of the count and the outline
- An H1 in the server HTML that JavaScript changes, through the server vs rendered check
- A ticket with that evidence, and a "Check if fixed" button that re-runs the check
Heading hierarchy skips levels