The way most screen-reader users navigate a long page is by jumping heading to heading. Get the hierarchy right and the page becomes a table of contents you can step through with a single key; get it wrong, and the page collapses into one undifferentiated wall.
Reusable components have always made that hierarchy harder. Drop a card component into a sidebar and an <h2> inside it suddenly outranks its parent. Drop the same card under an <h3> somewhere else and the levels need to shift again. The pragmatic answer for years has been: pick a level and live with the result.
Firefox has now shipped (behind a flag, per Adrian Roselli's write-up) a small HTML attribute that takes some of that pain away — and a much larger conversation about what it does not do.
What the attribute actually does
Per the source, headingoffset lives on a container and shifts the heading levels of its descendants in the accessibility tree. Put headingoffset="2" on a wrapper and an <h1> inside that wrapper is exposed as a level-3 heading; an <h2> becomes a level-4 heading; and so on. The DOM stays as you wrote it, but the level the assistive tech announces is the offset version.
The values are constrained. Per the source, headingoffset accepts non-negative integers from 0 to 8, and any value greater than 8 is specified to max out at heading level 9.
The reusable-card case is the obvious win. You write the card once with its own internal heading order (an <h1> title, an <h2> subhead) and the page that drops the card in declares where that internal order should sit. No more shipping a component that is wrong everywhere except its first home.
The escape valve: headingreset
There is a partner attribute. Per the source, headingreset is a boolean attribute that tells the browser to ignore any inherited headingoffset for the node it sits on and that node's descendants. If a card lands inside a region that has already been offset and you do not want a second offset stacking on top, headingreset puts that subtree back to its raw DOM levels.
That is a small piece of API surface, but it matters. Without a reset, nested offsets compound, and the meaning of "level 2" on the page becomes a function of every ancestor a component happens to land under.
Why this is not the outline algorithm coming back
Here is the part worth saying out loud, because the temptation to read it that way is real.
Per the source, the proposed Document Outline Algorithm, the idea that sectioning elements would shoulder the heading hierarchy for authors automatically, was never part of a finalised HTML specification, and has been abandoned. A brief implementation in JAWS exists in the historical record; the source's verdict is that the implementation demonstrated the algorithm was unworkable.
headingoffset is not that. It does not look at structure. It does not infer. It is an explicit, author-set value that says: shift the levels under this node by this many. If you do not set it, nothing happens. The responsibility for the page's overall heading structure stays with you, exactly where it was last week.
In other words: an <h1> inside a <section> is still an <h1> unless you tell the browser otherwise.
A caveat the source flags hard
One detail not to skip. Per the source, computed heading levels above 6 still trip bugs in JAWS and TalkBack at the time of writing, and the author's advice is to avoid producing computed levels above 6 until those bugs are fixed. That is a hard constraint on how you use the attribute, not a footnote.
So headingoffset="3" on a wrapper whose internal headings start at <h4> produces a computed level of 7, and that is exactly the range the source is telling you to avoid for now. Walk that math through every component before you ship.
Where this leaves you
The judgement from the source is that headingoffset is a useful primitive for component reuse and nothing more. It does not turn <section> into a structural tool. It does not absolve the author of getting the document's heading order right.
For your next PR with this enabled:
- Treat
headingoffsetas a property of where the component lands, not of the component itself. - Use
headingresetwhenever you carry a subtree across boundaries and want its inner levels restored. - Keep computed levels at 6 or below until the screen-reader bugs the source flags are fixed.
- Leave the document's spine, the page's own
<h1>and section headings, as ordinary hand-authored HTML. The new attribute complements that work; it does not replace it.
The platform has given you a small, honest tool. Use it for the job it actually does.
Top comments (0)