Headings & Accessibility
Alexandru Bereghici / December 28, 2021
3 min read • 26 views
Headings are used to organize content in a web page by separating it into meaningful sections. Well-written headings help users to scan quickly through the page and get a sense of whether the page contains the information they are looking for. Headings are critical for accessibility, the adaptive technology users rely on formatted headings to understand and navigate through the page. Without a good heading, the screen reader software read the entire content as a single section.
Generally, it's considered bad practice to skip heading levels, for example
having a <h4>
without a <h3>
. You can not only confuse screen readers but
all readers when you don't follow a consistent pattern for your content.
From practice, I've noticed that the developers are using the wrong element just because of style. You should NOT use different heading tags just for styling purposes.
Also, in React it's very easy to end up with a wrong structure, especially when
you move components with headings around. You have to check the levels if still
make sense and adjust them if needed, so most of the time developers ends up
with using only a <h1>
element or with a wrong structure.
A very interesting solution for this problem I found in baseweb, a component library created by Uber.
Instead of worrying about what element you have to use, you can have a React
Context that handles the document outline algorithm for you. Here is the
HeadingLevel
component that's used to track the heading levels.
export const HeadingLevel = ({children}: Props) => {
const level = React.useContext(LevelContext)
return (
<LevelContext.Provider value={level + 1}>{children}</LevelContext.Provider>
)
}
Now the Heading
component can consume the level and render the correct
element. It contains validations to make sure you cannot have more than 6 levels
deep. Also, it solves the styling problem. If you need to have a <h2>
element
but styled as a <h4>
, you can use the styleLevel
prop to specify it.
import { LevelContext } from "./heading-level";
interface Props {
styleLevel?: number;
children: React.ReactNode;
}
const STYLES = ["", "h1", "h2", "h3", "h4", "h5", "h6"];
const Heading = ({ styleLevel, children }: Props) => {
const level = React.useContext(LevelContext);
if (level === 0) {
throw new Error(
"Heading component must be a descendant of HeadingLevel component."
);
}
if (level > 6) {
throw new Error(
`HeadingLevel cannot be nested ${level} times. The maximum is 6 levels.`
);
}
if (typeof styleLevel !== "undefined" && (styleLevel < 1 || styleLevel > 6)) {
throw new Error(`styleLevel = ${styleLevel} is out of 1-6 range.`);
}
const Element = `h${level}` as React.ElementType;
const classes = styleLevel ? STYLES[styleLevel] : STYLES[level];
return <Element className={classes}>{children}</Element>;
};
It might look a bit verbose, but now you don't have to worry about what element you should use, you just care about the levels. If you want to play around with this solution, you can use the sandbox below.
Discussion
good article
Daniel Laera
/14 Jan 2022 at 4:15 PM