# Headings must have discernible text.

> Canonical HTML version: https://rocketvalidator.com/accessibility-validation/accesslint/0.16/navigable/empty-heading
> Attribution: Rocket Validator (https://rocketvalidator.com)
> License: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)

Headings give structure to a page. Screen reader users rely on them to navigate between sections quickly, often jumping from heading to heading to find the content they need. When a heading element has no accessible name (no visible text, no `alt` text on images inside it, and no ARIA label), it appears in the heading list as a blank entry. This creates a dead-end navigation point that provides no information about the content that follows.

Empty headings also affect sighted users. A heading element with no text content can still affect page layout by breaking the flow of content or rendering as an empty block with margins and padding. This can introduce unexpected whitespace or visual gaps.

This rule checks that every heading element (`<h1>` through `<h6>`, and elements with `role="heading"`) that is included in the accessibility tree has a non-empty accessible name. The accessible name can come from the heading's text content, from an `aria-label` attribute, from an `aria-labelledby` reference, or from alternative text on child elements like images.

## Why this matters

WCAG Success Criterion 2.4.6 (Headings and Labels) requires that headings describe the topic or purpose of the content they introduce. A heading with no accessible name fails to describe anything. It is present in the document outline but communicates nothing, which defeats the purpose of using heading markup.

Screen reader users are the most directly affected. When they open a list of headings on a page, empty entries appear as unnamed items. There is no way to tell what section an empty heading belongs to, so users must navigate to it and read the surrounding content to understand the page structure. This is slow and frustrating.

## How to fix it

The fix depends on why the heading is empty:

- If the heading should contain text, add visible text content to it.
- If the heading contains only an image, add an `alt` attribute with a descriptive text value to that image.
- If the heading is used purely for visual spacing or styling (not to introduce a section), remove the heading element and use CSS to achieve the visual effect instead. A `<div>` or `<span>` with appropriate styles does not appear in the heading navigation list.
- If the heading's content is generated dynamically and may sometimes be empty, make sure the heading element itself is not rendered when there is no content to display.
- If the heading contains only visually hidden text using `aria-label` or `aria-labelledby`, make sure the referenced text is not empty.

## Examples

### Empty heading with no content

This heading has no text and no accessible name. Screen readers will announce it as an empty heading.

```html
<h2></h2>
```

### Heading with an image that has no alt text

The `<img>` element has no `alt` attribute, so the heading's accessible name is empty.

```html
<h1>
  <img src="logo.png" />
</h1>
```

### Heading used only for spacing

A heading element is used to add vertical space between sections. It has no content.

```html
<h3>&nbsp;</h3>
```

### Fixed: heading with visible text

```html
<h2>Contact information</h2>
```

### Fixed: heading with an image that has alt text

The `alt` attribute provides the heading's accessible name.

```html
<h1>
  <img src="logo.png" alt="Acme Company" />
</h1>
```

### Fixed: heading with aria-labelledby

The heading gets its accessible name from a referenced element.

```html
<span id="section-title" hidden>Order summary</span>
<h2 aria-labelledby="section-title"></h2>
```

### Fixed: visual spacing without heading markup

Instead of an empty heading, use a `<div>` with CSS for spacing.

```html
<div style="margin-top: 2rem;"></div>
```

### Fixed: div with role="heading" and text content

Custom heading elements need text content too.

```html
<div role="heading" aria-level="2">
  Shipping details
</div>
```
