Heading structures are tables of contents

The heading structure of a web page is like its table of contents. It gives people who can’t see your page a way to navigate it without reading everything.

To be clear, by ‘heading structure’ in this post, I mean the heading elements in your HTML: <h1> to <h6> . These elements can be strongly intertwined with what they look like, but for our purposes it is the markup that matters.

The best analogy I’ve been able to come up with for heading structures, is the feature in Microsoft Word that lets users generate a table of contents automatically. It is used a lot by all kinds of people, in all kinds of environments (in long corporate documents, but also in academia). If you’ve set document headings correctly, it lists all sections and subsections. It even remembers, and updates, which page they are on.

Screenshot of Pages application with table of content settings open Example of the automagic table of contents feature in Pages. It even lets you select which heading levels to include!

All websites have this too, as a similar feature is built into most screenreaders. In VoiceOver, for example, users can press a button to see a list of all headings and use this to navigate a page. In fact, this is a common way for screenreader users to get around your page without reading everything.

Wikipedia page about assistive technologies with voiceover rotor headings open The headings feature in action on Wikipedia. Note that Wikipedia also lists the headings explicitly, with section numbering.

Only use headings to identify sections

To let users get the best navigate-by-headings experience, only use heading elements for content that actually identifies a section. Ask ‘would this be useful in my table of contents?’, and if the answer is no, best choose a different HTML element. This way, only table of contents material makes it into your heading structure.

For this reason, by the way, I recommend to avoid having headings be part of user generated content (as in: content added not by content specialist, but by users of your site). If you offer Markdown for comments, for example, headings in those comments could mess with the usability of your heading structure.

If you choose something to be a header, make sure it describes the section underneath it. This can be hard to get right, you might have great puns in your headings, or maybe they were written by a SEO expert.

Visually hidden headings

Not all sections have headings, often the visual design makes it clear that something is a distinct piece of content. That’s great, but it doesn’t have to stop a section from also showing up in your table of contents. Hidden headings to the rescue!

A hidden heading is one that is ‘visually hidden’, this is content that is not visual on screen, but it exists in your markup and screenreaders can use it:

<h2 class="visually-hidden">Contact information</h2>
<!-- some content that looks visually a lot like contact
     information, with icons and a receptionist stock
     photo that makes it all very obvious-->

(More about CSS to visually hide)

The heading goes into your virtual table of contents, but it is not visible on screen.

Note that visible headings are much preferred to hidden headings. There are two problems in particular with hidden headings:

  • Like with other hidden content, it can easily be forgotten about by your future self or the next team member. Hidden content that is not up to date with the visible content is unhelpful, so it is a bit of a risk.
  • Serving slightly different content may cause confusing conversations, for example if an AT user and a sighted user discuss a page and only one of them knows that there is a heading.

‘Don’t skip heading levels’

Although WCAG 2 does not explicitly forbid skipping heading levels, and this is controversial, I would say it is best not to skip heading levels.

If a contract has clause 2.4.2, it would be weird for there not to be a 2.4 — the former is a subclause of the latter. It would be weird for the subclause to exist without the main clause.

The most common reason why people skip headings is for styling purposes: they want the style that comes with a certain heading and that happens to be the wrong level for structural purposes. There are two strategies to avoid this:

  • have agreement across the team about how heading levels work
  • use .h1, .h2, .h3 classes, so that you can have correct heading levels, but style them however you like

The former is what I prefer, on many levels, but if it is a choice between weird CSS and happy users, that’s an easy one to make.

Automatically correct headings

The outline algorithm mentioned in HTML specifications is a clever idea in theory. It would let you use any headings you’d like within a section of content; the browser would take care of assigning the correct levels. Browsers would determine these levels by looking at ‘sectioning elements’. A sectioning element would open a new section, browsers would assume sections in sections to be subsections, and headings within them lower level headings.

There is no browser implementing the outline algorithm in a way that the above works. One could theoretically have automated heading levels with clever React components. I like that idea, although I would hesitate adding it into my codebases. That leaves us with manually choosing plain old headings level, from 1 to 6.


Heading structures give screenreader users and others a table of contents for our sites. By being conscious of that, we can make better choices about heading levels and their contents.

Thanks Matijs and Léonie for their great feedback on earlier drafts of this post.

Comments, likes & shares (108)

@timsev@mastodon.social wrote on 3 September 2018:
RT @Hdv@toot.cafe
✏️ Heading structures are tables of contents hiddedevries.nl/en/blog/2018-0…
@baldur@toot.cafe wrote on 3 September 2018:
“Heading structures are tables of contents” hiddedevries.nl/en/blog/2018-0…
Deborah Edwards-Onoro wrote on 4 September 2018:
Heading structures are tables of contents hiddedevries.nl/en/blog/2018-0… #HTML #a11y
Johan Ramon wrote on 5 September 2018:
Heading structures are tables of contents: hiddedevries.nl/en/blog/2018-0…
#accessibility #a11y
Web Axe wrote on 5 September 2018:
"Heading structures are tables of contents "—the why and how to implement hiddedevries.nl/en/blog/2018-0… by @hdv #webdev #a11y
Web Axe wrote on 5 September 2018:
"Heading structures are tables of contents "—the why and how to implement hiddedevries.nl/en/blog/2018-0… by @hdv #webdev #a11y
Masao YOMODA wrote on 5 September 2018:
>Heading structures give screenreader users and others a table of contents for our sites. By being conscious of that, we can make better choices about heading levels and their contents.
Benno Löwenberg wrote on 5 September 2018:
#accessibility & #usability are determined by the shape of the buidling blocks that serve as foundation for #interaction too:

Instead of looking at whole pages only, can #PatternLibraries already be tested for #A11y & how? — by @hdv :


Benno Löwenberg wrote on 5 September 2018:
#accessibility & #usability are determined by the shape of the buidling blocks that serve as foundation for #interaction too:

Instead of looking at whole pages only, can #PatternLibraries already be tested for #A11y & how? — by @hdv :


GravityDrive wrote on 8 September 2018:
The heading structure of a web page is like its table of contents. It gives people who can’t see your page a way to navigate it without reading everything.
Wes Oudshoorn wrote on 7 July 2020:
In that case, I’d give the "eyebrow" an <h1> and the “headline” an <h2>. This makes it easiest for a table of contents to find what you’re looking for. It feels a bit weird to reverse the visual weight from the HTML structure though…
Hidde de Vries (@hdv) is a freelance front-end and accessibility specialist in Rotterdam (NL), conference speaker and workshop teacher. Currently, he works for the W3C in the WAI team (views are his own). Previously he was at Mozilla, the Dutch government wrote on 25 November 2020:

This week, somebody proposed to replace HTML, CSS and JavaScript with just one language, arguing “they heavily overlap each other”. They wrote the separation between structure, styles and interactivity is based on a “false premise“. I don’t think it is. In this post, we’ll look at why it is good that HTML, CSS and JS are separate languages.

I’m not here to make fun of the proposal, anyone is welcome to suggest ideas for the web platform. I do want to give an overview of why the current state of things works satisfactorily. Because, as journalist Zeynep Tefepkçi said, quoted by Jeremy Keith:

If you have something wonderful, if you do not defend it, you will lose it.

On a sidenote: the separation between structure, style and interactivity goes all the way back to the web’s first proposal. At the start, there was only structure. The platform was for scientists to exchange documents. After the initial idea, a bunch of smart minds worked years on making the platform to what it is and what it is used for today. This still goes on. Find out more about web history in my talk On the origin of cascades (video), or Jeremy Keith and Remy Sharpe’s awesome How we built the World Wide Web in 5 days.

Some user needs

Users need structure separated out

The interesting thing about the web is that you never know who you’re building stuff for exactly. Even if you keep statistics. There are so many different users consuming web content. They all have different devices, OSes, screen sizes, default languages, assistive technologies, user preferences… Because of this huge variety, having the structure of web pages (or apps) expressed in a language that is just for structure is essential.

We need shared structure so that:

All of these users rely on us writing HTML (headings, semantic structure, autocomplete atributes, lang attributes, respectively). Would we want to break the web for those users? Or, if we use the JSON abstraction suggested in the aforementioned proposal, and generate DOM from that, would it be worth breaking the way developers are currently used to making accessible experiences? This stuff is hard to teach as it is.

Even if we would time travel back to the nineties and could invent the web from scratch, we’d still need to express semantics. Abstracting semantics to JSON may solve some problems and make some live’s easier, but having seen some attempts to that, it usually removes the simplicity and flexibility that HTML offers.

Users need style separated out

Like it is important to have structure separated out, users also need us to have style as a separate thing.

We need style separated out, so that:

  • people with low vision can use high contrast mode; a WebAIM survey showed 51.4% do (see also Melanie Richard’s awesome The Tailored Web: Effectively Honoring Visual Preferences)
  • people who only have a mobile device can access the same website, but on a smaller screen (responsive design worked, because CSS allowed HTML to be device-agnostic)
  • people with dyslexia may want to override some styles use a dyslexia friendly typeface (see Dyslexic Fonts by Seren Davies)
  • users of Twitter may want to use a custom style sheet to turn off the Trending panel (it me 🙄)

Users need interactivity separated out

Some users might even want (or have) interactivity separated out, for instance if the IT department of their organisation turned the feature company-wide. Some users have JavaScript turned off manually. These days, neither are common at all, but there are still good reasons to think about what your website is without JavaScript, because JavaScript loss can happen accidentally.

We need interactivity separated out, because:

  • some people use a browser with advanced tracking protection, like Safari or Firefox (see my post On the importance of testing with content blockers)
  • some people may be on low bandwidth
  • some people may be going to your site while you’ve just deployed a script that breaks in some browsers, but you’ve not found out during testing, because it is obscure

Existing abstractions

None of this is not to say it can’t be useful to abstract some parts of the web stack, for some teams. And people already do this, en masse, there is plenty of choice.

On the markup end of things, there are solutions like Sanity’s Portable Text that defines content in JSON, so that it can be reused across many different “channels”. This is a format for storing and transferring data, not for displaying it on a site. Because before you display it anywhere, you’d write a template to do that, in HTML.

For CSS there are extensions like Sass and Less, utility-first approaches like Tailwind.

As for JavaScript: there are numerous abstractions that make some of the syntax of JavaScript easier (jQuery, it its time), that help developers write components with less boilerplate (like Svelte and Vue) and that help teams make less programmatically avoidable mistakes (TypeScript).

I don’t use any of these abstractions for this site, or most others I work on, but many are popular with teams building complex websites happily do.

We’re very lucky that all of these abstract things that are themselves simple (ish) building stones: HTML, CSS and (to a different extent) JavaScript. With abstractions, individual teams and organisations can separate their concerns differently as they please, without changing the building blocks that web users rely on.


The separation between HTML, CSS and JavaScript as it currently is benefits web users. It does this in many ways that sometimes only become apparent after years (CSS was invented 25 year ago, when phones with browsers did not yet exist, but different media were already taken into account). It’s exciting to abstract parts of the web and remodel things for your own use case, but I can’t emphasise enough that the web is for people. Well written and well separated HTML and CSS is important to their experience of it.

Very good one, thanks @hdv. This the kind of article I love to share with colleagues and frontend people because it explains things very well. The reader imho get a good idea about the "why". 🙏 hidde.blog/heading-struct…