(This post was originally posted on Up Your A11y - Heading Levels in Reusable Components)
Reusability is Key
One of the key reasons React is so popular is the ability to define a component, pass it some simple props, and then re-use it in a variety of places without having to write duplicate HTML throughout your app.
When creating a re-usable component, there are always a few things to consider, e.g. what should be customisable via props, and what should be an integral part of the component itself.
The problem with inflexible heading levels
Consider a simple card component which renders a title and description, which might look something like this:
class SimpleCard extends React.Component {
render() {
const { title, description } = this.props;
return (
<div className='card'>
<h2>{title}</h2>
<p>{description}</p>
</div>
);
}
}
At first glance we have a nice reusable component that I can start placing throughout my app. However, we have one limiting problem - the 'h2' element.
Headings Have Semantic Value, Especially for Screen Readers
It's important to understand that heading levels in HTML are not simply about sizing and styling your header text; they provide semantic information about the organisation and importance of your content.
In particular, they are interpreted by screen readers so that users can jump directly to top level headings, next level headings and so on.
For this reason, heading levels should always increase in a logical order, and only by 1 step at a time. This allows users of assistive technology to skim and scan through your content as well as sighted users.
The Problem With Our SimpleCard Example
The SimpleCard component above defines an h2 element which will appear wherever I re-use this component. This means I can only use it on a page where there is already an 'h1' title, and where being an 'h2' makes logical sense for the flow of my page.
Given the power of React is flexible re-use of components, some refactoring would be beneficial.
Passing a Heading Level in Props
The problem can be easily solved with a simple trick that allows you to dynamically set the heading level according to the props passed in. See the upgraded version of the SimpleCard:
class SimpleCard extends React.Component {
render() {
const { title, description, headingLevel } = this.props;
const Title = headingLevel;
return (
<div className='card'>
<Title>{title}</Title>
<p>{description}</p>
</div>
);
}
}
As you can see, the component now receives the heading level as a string (e.g. 'h1') in props and dynamically creates the correct heading element to render in the card. Note in the example above that:
- The 'Title' value could be named anything, I've just chosen 'Title' as it made sense in the context. The example would still work if the value was called 'Banana' and we rendered out
<Banana>{title}</Banana>
- 'Title' is title-cased - this is essential otherwise React will not recognise it as a DOM element.
Final Tweaks and Considerations
While dynamically creating DOM elements based on string props like this is very powerful, it could also yield some unwanted behaviour if the expected prop types aren't passed in.
I'd recommend when using this approach to also make sure you complete some props validation before attempting to create the Title element. There's a variety of ways to achieve this, but a very simple implementation could be:
class SimpleCard extends React.Component {
render() {
const { title, description, headingLevel } = this.props;
const validHeadingLevels = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
const safeHeading = headingLevel ? headingLevel.toLowerCase() : '';
const Title = validHeadingLevels.includes(safeHeading) ? safeHeading : 'p';
return (
<div className='card'>
<Title>{title}</Title>
<p>{description}</p>
</div>
);
}
}
Now The Component Can Be Used Anywhere!
If an appropriate heading level isn't passed in props, we default to creating a basic paragraph element instead.
Great! So now I can use my SimpleCard in a variety of locations in my app without breaking any semantic HTML conventions or degrading the experience for screen reader users.
Did you find this post useful? Please consider buying me a coffee so I can keep making content 🙂
Top comments (6)
This is less about react and more about html in general. I am an advocate of accessibility best practices which are really just best practices in general. But there is one thing I dont ever remember, how do I actually use heading levels, do I need 5 of them, can I use h1 and skip to h3. And so on.
I think it's something we lose sight of so easily - e.g. if the design we've been handed doesn't look like it has very distinct heading levels. I really like WebAIMs section on this in terms of semantic content.
The tl;dr of it is every page should have a single
<h1>
element - the main heading of the page (which describes not just to the user but also to search engines what the page is about). Beyond that heading levels should only ever increase by one, and usually appear nested in sections (by which I just mean parts of the page - I've used section below but it could bediv
or anything) e.g.I think the key is we have to get away from thinking of heading tags as a visual style feature, and understand it's about the core structure of the page - just like any other semantic element.
Once we start to appreciate h1/h2 and so on as 'importance order of title' rather than "make it look X size" then it becomes a bit clearer (or at least to me it does!)
Your last point about size, I often wonder if browsers shipped all heading levels at the same size that would provoke more thought into thier usage. As a beginner you are taught a misnomer that headings denotes size, then frameworks basically affirm this.
That's so true! I guess then I wonder if people would give up on header tags completely in many cases and just style spans or p tags.
At least some headers are better than none! 😁
I guess it's like the italic tag, it's NOT an icon people!
We try hard but there's only so much space in our brains 😂