Heading Outline

Shows the live heading outline of a Markdown draft or page region and flags skipped levels and fake headings that confuse screen reader users.

StopsSkipped heading levels confuse screen readers

Installation

pnpm dlx shadcn@latest add https://bollard-ui.vercel.app/r/heading-outline.json

The files land in your project. They're yours to edit.

Usage

heading-outline-usage.tsx
import { HeadingOutline, type OutlineHeading } from "@/components/ui/heading-outline"

<HeadingOutline markdown={markdown} onMarkdownChange={setMarkdown} onJump={jump} />

What it catches

  • Skipped heading levels (H2 to H4)
  • More than one H1, or none
  • Empty headings
  • Bold lines that look like headings but aren't
  • Headings long enough to be paragraphs

Props

HeadingOutline props
PropTypeDefault
markdown

Markdown to outline. With `onMarkdownChange`, problems get one-tap fixes.

string—
onMarkdownChange

Called with the fixed Markdown when a fix is tapped.

(markdown: string) => void—
target

Live mode: outline the headings inside this element, updating as it changes. Rows jump to the heading.

React.RefObject<HTMLElement | null>—
onJump

Called when a row is chosen (markdown mode), e.g. to move the editor's cursor there.

(heading: OutlineHeading) => void—
startLevel

Level the content starts at. Use 2 when the page template already renders the H1.

number1
maxLength

Headings longer than this are flagged.

number90
label

Accessible name of the outline.

string"Heading outline"
title

Heading shown above the outline.

string"Outline"
summaryText

`{count}` is filled in.

string"{count} to fix"
allGoodText

Shown when no issues are found.

string"Structure looks right"
emptyText

Shown when there are no headings.

string"No headings yet."
emptyHeadingText

Row text for an empty heading.

string"Empty heading"
skippedRowText

`{level}` is filled in.

string"H{level} skipped"
fixLabel

`{level}` is filled in.

string"Make it H{level}"
removeLabel

Label for the button that removes an empty heading.

string"Remove it"
messages

Override any issue message.

Partial<typeof HEADING_OUTLINE_MESSAGES>—

* required. Also accepts the native props of <nav> (className, aria-*, data-*…), passed through to it.

Accessibility

  • Rendered as a labelled nav
  • Summary line is a polite live region
  • Jump focuses the heading (adding tabindex=-1 when needed); smooth scroll is skipped under reduced motion
  • Focus returns to a nearby row after a fix

Only what the source does. Test with your own screen reader and content before shipping.

Limitations

  • DOM mode reads headings and role=heading; it skips hidden content but cannot judge whether heading text is meaningful
  • Markdown mode parses ATX, setext and single-line HTML headings, not every Markdown dialect
  • One outline check, not a full WCAG audit

What it stops

Skipped heading levels confuse screen readers

Also known as

headings · h1 · a11y · accessibility · wcag · markdown editor · document outline · screen reader

Field type

Dependencies

Pairs well with