Nick Lemmon

Building an Accessible, Re-usable Heading Component

April 22, 2021

HTML headings are not semantically complex elements though they are frequently used inappropriately by both designers and engineers alike.

Why Use Headings?

Each heading can be specificied with a level (1 through 6) and render text content to the DOM. Generally, headings are used to group content in a hierarchical fashion, much like a newspaper or magazine article.

<h1>Man Lands on the Moon!</h1>

<h2>Who Landed?</h2>

<h3>The First Man on the Moon</h3>

<p>Neal Armstrong!</p>

<h3>The Rest</h3>

<p>Buzz Aldrin was also there!</p>

<h2>Were There Aliens?</h2>

<p>Sadly, no.</p>

In a traditional document layout, heading levels correspond naturally with the flow of content. This document flow helps users quickly scan for content visually and helps screen reader users navigate quickly around a page. WebAIM wrote a great article on the subject.

Common Pitfalls

Both visual designers and developers make some common mistakes when using headings in their work, largely stemming from the typical connection between style and semantic meaning.

For better or for worse, modern web applications do not tend to contain long blocks of structured content, making maintaining a strict heading hierachy more difficult. Visual designers may, for example, hope to use a heading that looks like a level 5 heading when, according to the document hierarchy, a level 2 heading might be more appropriate.

Similarly, developers will often (inappropriately) leverage headings as a means of styling. Without enough knowledge of the importance of the heading hierarchy of a document, an engineer may reach for an <h6 /> when a <p /> with bold styling was needed.

Abstraction to the Rescue

Fortunately, building a re-usable Heading component abstraction can help separate the visual presentation from the semantic meaning of a heading instance without relying on manual, one-off styling.

Styling

First, let's apply some styling to headings globally that will apply directly to HTML elements. Generally, styling HTML elements globally should be avoided, however, styling headings in this manner can help ensure authored content (possibly provided via a CMS) renders with the relevant hierarchical presentation.

Take the following CSS, for example, with font size values generated from modularscale.com:

h1,
h2,
h3,
h4,
h5,
h6 {
  font-weight: 700;
  font-family: serif;
}

h1 {
  font-size: 2.986rem;
}

h1 {
  font-size: 2.488rem;
}

h3 {
  font-size: 2.074rem;
}

h4 {
  font-size: 1.728rem;
}

h5 {
  font-size: 1.44rem;
}

h6 {
  font-size: 1.2rem;
}

Providing Semantic Control

With styling handled, now we need a re-usable way to generate the heading level from a single component:

import React from 'react'
import './heading-styles.css'

function Heading({ as, children }) {
  const Component = as

  return <Component>{children}</Component>
}

function MyPage() {
  return (
    <>
      <Heading as="h1">My Page</Heading>

      <Heading as="h2">More Information</Heading>
    </>
  )
}

Separate Form from function

With the DOM rendering handled, now we incorporate a new prop looksLike that helps divorce presentation from the underlying semantics. In this example, we leverage the ARIA heading role paired with the aria-level attribute in order to have more granular control over the DOM semantically:

import React from 'react'
import './heading-styles.css'

function Heading({ as, looksLike, children }) {
  const Component = looksLike ? looksLike : as
  const headingLevel = as.replace('h', '')

  return (
    <Component role="heading" aria-level={headingLevel}>
      {children}
    </Component>
  )
}

function MyPage() {
  return (
    <>
      <Heading as="h1">My Page</Heading>

      <Heading as="h2" looksLike="h3">
        More Information
      </Heading>
    </>
  )
}

Now, the "More Information" heading looks like a level 3 heading, but is semantically a level 2 heading.

Conclusion

Building a re-usable, accessible component abstraction can help improve the developer experience of a project while also improving the end-user experience. Spending a little time to introduce such an abstraction can pay off in the long run, allowing for easier feature development, all while lowering the risk of accessibility defects.

Nick Lemmon

About Me

I’m a Senior Principal Frontend Engineer working for Truist Financial in Columbia, Maryland who also happens to have an MSW!

I’m driven to design and build accessible design systems and web applications with a great underlying developer experience in mind.