The empty-heading rule flags a heading that has no text a screen reader can announce – an empty <h2>, or one that holds only a hidden icon. Screen reader users hear "heading level 2" and nothing else. The fix is to give the heading real text, or to remove the heading element if it is only there for spacing.
What the rule means
Every heading – h1 to h6, or an element with role="heading" – needs an accessible name. axe accepts any of these:
- text inside the heading, including text that is visually hidden but available to screen readers,
- the
alttext of an image inside the heading, aria-labeloraria-labelledby,- a
titleattribute, as a last resort.
A heading that contains only whitespace, a non-breaking space, an icon marked aria-hidden="true" or an image with alt="" fails. Headings hidden entirely – with display: none or the hidden attribute – are not checked, because nobody reaches them.
Note: this is a best practice, not a WCAG failure in itself. It relates to 1.3.1 Info and Relationships, because an empty heading claims a structure that is not there, and to 2.4.6 Headings and Labels, because a heading should describe what follows.
Who is affected
Mainly blind and partially sighted people using a screen reader. They move through a page by headings or open a list of all headings, and an empty one appears there as a blank entry or as "heading level 2" with no words. It costs a keystroke, suggests missing content and makes the rest of the outline harder to trust. Voice control users who say a heading's name to jump to it also have nothing to say.
Why the check fails
- Spacing with headings: editors press Enter in a heading style and leave an empty
<h2> </h2>behind in the rich-text editor. - Icon-only headings: a card title that is just an icon font or SVG with
aria-hidden="true". - Empty template slots: a teaser or accordion component renders its
<h3>even when the editor left the title field empty. - Headings filled by JavaScript that stay empty when the data does not load.
- Logos in headings with
alt=""because the image was treated as decorative.
How to fix it
Decide first whether it is a heading at all. If it is, give it text; if it only makes space, replace it with CSS.
<!-- Before: an empty h2 used as a spacer, and a heading with only an icon -->
<h2 class="section-spacer"></h2>
<div class="card">
<h3 class="card__title"><span class="icon icon-star" aria-hidden="true"></span></h3>
<p>Our most popular products this month.</p>
</div>
<!-- After: spacing is a div with CSS; the heading has real text -->
<div class="section-spacer"></div>
<div class="card">
<h3 class="card__title"><span class="icon icon-star" aria-hidden="true"></span> Customer favourites</h3>
<p>Our most popular products this month.</p>
</div>
For components, do not render the heading element when its title field is empty. In a CMS, clean up empty headings in the editor's source view, and check whether the editor's paste handling creates them.
How to test it manually
- Open the heading list: the free HeadingsMap extension, the WAVE extension's structure view, or a screen reader (NVDA: Insert + F7; VoiceOver: VO + U).
- Look for blank entries or entries that only say "heading".
- For each heading, read the announced text on its own. Does it say what the section is about?
- The rule only checks that some text exists. A heading reading "More" or "Section 1" passes, and so does one whose visible text differs from its
aria-label– a person has to judge both.
Related WCAG criterion
None directly – empty-heading is a best practice. It relates to 1.3.1 Info and Relationships and 2.4.6 Headings and Labels. Whether headings describe their content (2.4.6) is a judgement no automated check makes; in Reviseberg that criterion stays "untested" until a person records a manual result.
How Reviseberg reports it
Reviseberg runs empty-heading on every crawled page. Each empty heading is one occurrence, so an empty slot in a shared component shows up on every page that uses it. The issue list shows the rule as a best practice with its severity, the number of elements and pages, and the points a fix gets you back; it never counts towards a WCAG criterion or appears in a compliance report. The detail view shows the selector and HTML snippet of each heading and every affected page. You can mark a finding as "ignore" or "false positive" with a reason; every decision is logged.