Skip to content

Instantly share code, notes, and snippets.


Heydon/ Secret

Created Apr 21, 2018
What would you like to do?

One thing that keeps coming back to me, in research, testing, and everyday conversation with colleagues and friends, is just how important headings are. For screen reader users, headings describe the relationships between sections and subsections and — where used correctly — provide both an outline and a means of navigation. Headings are infrastructure.

Applying the correct heading level is a question of context. For example, two successive headings of the same level describe two sibling sections in the document outline. Where the second of the successive headings is a higher level, it describes a subsection belonging to the first heading's section.

  • <h2> — A section
  • <h2> — A sibling section
    • <h3> — A subsection

HTML5 promised to automate these relationships by deferring the nesting algorithm to <section>s (or other sectioning elements). The idea was that, by literally nesting the parent <section> elements in the DOM, their heading levels would be adjusted in the accessibility tree for free. (The accessibility tree is the interpretation of the DOM communicated to software such as screen readers.)

Unfortunately, to date, no major browser vendor has meaningfully implemented this so-called "document outline algorithm". And if the browser doesn't expose it, screen readers can't communicate it. The last code example just wouldn't work.

Those of us interested in creating accessible document outlines are therefore stuck choosing heading levels (<h1> to <h6>) regardless of the <section>s we might be employing.

This poses a particular problem when developing design systems. While individual components within a design system can — and should — use headings, it's difficult to know which heading levels they should take. As isolated modules within a pattern library, the context of the component within a page is indeterminate. For reusable components the context will change, along with the level required in some cases.


The level prop

APIs like React's and Vue's let us manually apply properties (or 'props') to our components on instantiation. These allow us to apply certain settings to the component's instance, to be used internally.

By supporting a level prop, we can allow authors to adjust the heading level of the component at the time of instantiation, when they are aware of its surrounding context. This is how it would be applied:

Internally, we need to take the prescribed level and use it to augment the component's principle heading. There are a couple of ways to do this. The first way would be to employ some logic that chooses the correct heading element (<h1> to <h6>) for us. A React example:

This will quickly become unwieldy when maintaining a structure of multiple headings within the component (see the ensuing section). Instead, we can change the heading element's level in the accessibility tree directly, via the aria-level property.

Two things to note:

  • An <h2> element is used as a "sensible default". All first-tier subsections following the page's principle <h1> heading would be of the second (<h2>) level. Where the level prop is not provided, aria-level is not applied and the implicit second level acts as a default.
  • The <h2> already has the implicit heading role, so the use of role="heading" would be redundant. Had we used a <div> to define our heading, role="heading" would be needed alongside aria-level. This is not recommended, because the support is relatively scant. At least where aria-level is not supported, we still have an <h2> heading — even if it's not the correct level. Headings, of any level, can be navigated between in screen readers like NVDA or JAWS using the h key.

Multiple headings

In some cases, the subtree that makes up our component may include multiple headings, describing a microstructure within the document. Hard coding each level via its own prop would be verbose, and prone to error on the part of the author.

Instead, we can maintain the same structure by applying some simple arithmetic.

Now the author still needs only to apply one level at the time of instantiation (if needed!) and the whole structure shifts accordingly. Simple, but effective.


How you apply the styling depends on your strategy. In most cases, the font-size of the heading should reflect its position in the hierarchy, with the <h1> as the largest, or most prominent. In which case, you would need to couple the elements and attributes according to level:

Where you wish the visual appearance of the component's heading to be unaffected by context, while still maintaining an accessible hierarchy, you might apply a class.

Using a library like styled components, you would create new heading components and style them directly.

These components would take the aria-level attribute. For example:

Headings still matter

Some web application developers have a habit of dispensing with anything they consider "from an era of simple documents". It's true that headings originate in a tradition of marking up static, prosaic documents like those you might write in MS Word or similar. But to think a sound heading structure is not necessary in an application interface is to misapprehend what headings really are.

Headings are labels for sections (or 'areas' if you prefer) that make up an interface. They can label a section of information, or a set of interactive controls. It's all the same. Labeling the sections of your interface is just as important as labeling your individual controls. Without labels, folks simply don't know what anything is for.

For screen reader users, headings are not just labels but a 'bypass' mechanism that allows them to navigate between different areas. Without headings, they have to step through every single element in turn, to get from one place to another. Arduous and off-putting.

Keyboard users not running a screen reader supporting these 'bypass' shortcuts may find their experience pretty arduous — especially where there are huge numbers of interactive controls to step through. Fortunately, a browser extension from Matt Atkinson is available to help. It provides shortcuts not to headings, but landmark elements (<header>, <main>, <footer> etc) but headings support is also being considered.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment