Heading for Greatness — A React Developer's Guide to HTML Headings
Moritz Jacobs
January 29, 2024
Introduction ☝️
The HTML headings h1 through h6 are among the most basic building blocks for structuring content in a browser. They have been around since all the way back in 1991, when Tim Berners-Lee added them to his first draft of "HTML". A few years later, Berners-Lee and his colleague Dan Connolly wrote the first proposal for an official HTML specification. It already mentions a practice that every web developer should know but that is too often not followed:
If you stop reading here, take this message home with you: Use exactly one <h1> per page and do not skip heading levels!
Why heading order matters
Typically these headings are styled with a descending typographic emphasis: h1 is large and bold (very prominent), h2 a little less and so on. If your content is like this article or a scientific text, the reason for this is obvious: it helps the reader to understand the hierarchical structure of your content.
The second reason is machine readability: properly set headings describe the outline of your document in a way that is suitable for …
- Assistive technology (e.g. screen readers for visually impaired users) and
- Crawlers such as the Google bot. Fortunately, good accessibility practices and good SEO often go hand in hand 💪
- Automatic Table Of Contents generation
- Use in popular testing frameworks, for example page.getByRole("heading", ...) in Playwright
In such documents, the DOM element used (e.g. <h2>) and the styling applied to it (e.g., bold, large text, perhaps in a different color) are tightly coupled. The outline of the document manifests itself both on the semantic level (the DOM, the "hidden outline" so to speak) and on the visual level (what you can actually see on the screen).
So what is the problem? 😱
Problems tend to occur on pages that are not purely text driven. For example, marketing pages, e-commerce pages, landing pages, … These pages are typically design-oriented with just enough text to sell something. They need to be aesthetically pleasing and concise. As a result, they're often not easily consumable by screen readers, for example.
Most of the web is made to appeal to sighted users, who are rarely affected by the semantics of HTML. If we as web developers don't actively try to keep assistive technology users in mind when implementing a design, no one else will. This should concern designers as well!
There are several types of problems, and each has a solution:
#1: »It looks like a heading but it's not semantically suited to be one! 😖«
This is the most common mistake devs make: a typographic choice does not automatically translate to a heading level. Just because some text on a page is emphasized that way, it's not necessarily a particular type of heading. In other words: don't reach for that <h2> just because this text is big and bold!
When working on the look and feel of a project, designers often come up with typographic styles and scales. These are often also named "h1", "h2" and so on — in my personal opinion, this is a mistake. We attach too much semantic meaning to the name of a particular style. I would rather find more abstract names for these styles, like "text-xl".
Implementation-wise, that would mean that you should use a CSS reset to level the playing field and then only apply styling based on classes, not on element names:
Most UI libraries that provide typography components do this as well:
#2: »The design doesn't want a heading here, but the structure needs one 🥺«
Not a problem, you can always add an invisible heading:
Again, the UI libraries know this too:
#3 »I'm not sure which heading level to use in my component 😐«
This is a tricky one. When you decide which heading level to use within a component, you are always restricting the component to a particular context within the document outline. But components are often used in multiple places and would require a more dynamic handling of their heading levels. Like this example:
This works, but it feels a bit clumsy.
Tenon-UI had this great concept of heading level boundaries where you don't have to worry about using actual levels 1 through 6, but instead structure your component tree into semantic sections. Since this concept is baked into Tenon-UI (which seems to be dead) we decided to make this idea usable in any React-based application with our own package:
Meet uberschrift 🧢
uberschrift (named after the German word for "headline") gives you two components: one we call <Hx> (as in "heading level x"), which you can use for your headings without even thinking about context and level; and a <HxBoundary>, which you use to structure your document. Within these boundaries, <Hx> always chooses the correct heading level. If you need to structure your document one level deeper, you wrap your subcomponents in a new <HxBoundary>. The setup is straight forward:
… will magically render like this 🪄
It is entirely powered by React Context, ready for React Server Components, zero dependencies, under 1kb and tree-shakeable! It's also usable with most popular UI libraries, check our documentation page for an explanation or take a look at our examples.
Conclusion
When it comes to web development, as with all programming, the devil is in the details. There are many different requirements for the implementation of the simplest things: SEO, accessibility, code quality, ... The ever-increasing complexity of the web is embedded in even its smallest elements. Even headings.
We hope this article and package will help you manage this complexity a little better.
HTML Headings
Best Practices
Uberschrift
Accessibility
SEO
Read also
Leonhard, 10/22/2024
Strategies to Quickly Explore a New Codebase
Web App Development
Consulting
Audit
Leonhard, 07/15/2024
User Input Considered Harmful
TypeScript
Web App Development
Best Practices
Full-Stack
Validation
Irena, 07/14/2024
Why flatMap() is easier than filter() in TypeScript apps
Typescript 5.5
Array Methods
flatMap
filter
map