# Outline Algorithm and Document Structure

> Canonical HTML version: https://rocketvalidator.com/glossary/outline-algorithm-document-structure
> Attribution: Rocket Validator (https://rocketvalidator.com)
> License: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)

The outline algorithm is a theoretical method defined in early HTML5 specifications for determining a document's hierarchical structure based on sectioning elements rather than heading levels alone. Because no browser or assistive technology ever implemented it, developers must rely on a flat heading hierarchy (`h1` through `h6`) to communicate document structure.

HTML5 introduced a set of sectioning elements (`section`, `article`, `nav`, `aside`) along with an outline algorithm that was supposed to let authors restart heading levels inside each section. Under this model, every `section` would begin a new scope, and an `h1` inside a nested `section` would automatically be treated as a lower-rank heading in the computed outline. The idea was appealing: authors could compose independent content fragments without coordinating heading levels across an entire page.

The algorithm was specified in the W3C HTML5 and WHATWG HTML standards for years, but no shipping browser engine, screen reader, or accessibility API ever adopted it. The WHATWG eventually removed the normative algorithm from the HTML specification in 2022, replacing it with guidance that warns authors not to depend on sectioning elements for heading rank. The practical consequence is clear: heading rank in every major user agent is determined solely by the `h1`–`h6` element used, regardless of nesting depth.

## Why the outline algorithm and document structure matter

Screen readers expose a headings list that lets users jump directly to sections of a page. When heading levels skip ranks or restart unexpectedly, that navigation breaks down. A visually impaired user scanning a page with a screen reader expects `h2` to follow `h1`, `h3` to follow `h2`, and so on. If a developer nests an `h1` inside a `section` believing the outline algorithm will demote it, the screen reader still announces it as a level-1 heading. The result is a flat, confusing list of headings that all appear to have equal weight.

Automated validators and WCAG auditing tools also flag heading-level violations. WCAG 1.3.1 (Info and Relationships) requires that structure conveyed visually is also programmatically determinable. A page that looks like it has a clear hierarchy but exposes broken heading levels to assistive technology fails this criterion.

Search engines parse heading elements to understand content hierarchy. Pages with a coherent heading tree tend to produce better structured data signals than pages with multiple `h1` elements scattered through nested sections.

## How the outline algorithm works

### The specified (but unimplemented) model

In the original specification, each sectioning content element (`section`, `article`, `nav`, `aside`) and each sectioning root element (`blockquote`, `body`, `details`, `dialog`, `fieldset`, `figure`, `td`) would create its own outline scope. The first heading encountered inside a scope would become its heading, and subsequent headings would be ranked relative to that scope. This meant you could theoretically write `h1` everywhere and let nesting determine rank.

### What actually happens in browsers

Browsers and assistive technologies ignore sectioning context when computing heading level. An `h1` is always level 1. An `h3` is always level 3. The nesting of `section` elements around them has no effect on the exposed accessibility tree. This behavior has been consistent across Chrome, Firefox, Safari, and every major screen reader (JAWS, NVDA, VoiceOver, TalkBack) since HTML5 was introduced.

### The correct approach

Use heading elements that reflect the true rank of each section in the document. Use `h1` once for the page title, `h2` for major sections, `h3` for subsections, and so on. Sectioning elements like `section` and `article` are still useful for grouping related content and can be paired with `aria-labelledby` pointing to their heading, but they do not change heading rank.

## Code examples

The following example shows a common mistake where a developer assumes the outline algorithm is in effect and uses `h1` inside every section:

```html
<!-- Bad: multiple h1 elements relying on the outline algorithm -->
<body>
  <h1>My Blog</h1>
  <section>
    <h1>Latest Post</h1>
    <section>
      <h1>Introduction</h1>
      <p>Some introductory text.</p>
    </section>
    <section>
      <h1>Main Argument</h1>
      <p>The core content.</p>
    </section>
  </section>
</body>
```

A screen reader will announce four level-1 headings. There is no hierarchy. Users cannot distinguish the page title from a subsection heading.

The corrected version uses descending heading levels:

```html
<!-- Good: explicit heading hierarchy -->
<body>
  <h1>My Blog</h1>
  <section aria-labelledby="latest-post">
    <h2 id="latest-post">Latest Post</h2>
    <section aria-labelledby="intro">
      <h3 id="intro">Introduction</h3>
      <p>Some introductory text.</p>
    </section>
    <section aria-labelledby="argument">
      <h3 id="argument">Main Argument</h3>
      <p>The core content.</p>
    </section>
  </section>
</body>
```

In this version, the headings list exposed to assistive technology reads:

- Level 1: My Blog
- Level 2: Latest Post
- Level 3: Introduction
- Level 3: Main Argument

Each `section` is also given an accessible name through `aria-labelledby`, which helps screen readers announce region boundaries. The heading levels alone carry the hierarchy, exactly as browsers and assistive technologies expect.

When building reusable components that might be placed at different depths in a page, you can dynamically select the correct heading level in your templating logic rather than hard-coding `h1` and hoping the outline algorithm will sort things out. Many component frameworks provide a "heading level" prop for this reason. The extra coordination is worth it: the resulting document tree is unambiguous for every user agent that consumes it.
