Headings headings headings!
General accessibility
Heading markup (e.g. native hN elements) is utilized by Assistive Technologies (AT) to construct a table of contents for a document. This provides an accessible method of in-page navigation.
AT will also be able to provide an appropriate alternate visual display that can be identified by heading markup. Screen readers use the headings to jump quickly around the page and understand its content.
You can quickly review a page’s heading hierarchy with the a11y-outline bookmarklet at https://xi.github.io/a11y-outline/ . Just drag the link that is on that page to your Bookmarks bar, and then click it to see a list of Headings, Landmarks, or Links. Note that it doesn’t work with iframes, though, and learning unit content is in an iframe in the Learning MFE.
Nesting headings properly ensures that a screen reader can pick up on the hierarchy of the content and generate an ordered list for the user to browse.
Generally headings should be used for chunking related information. There are some occasions when we use headings for other purposes, but there are exceptions to the rule / usability compromises. Start with the assumption that headings should be chunking hierarchical info, and check with the Accessibility team if you think there is a good exception.
When to use which hN
tag
Native heading elements are NOT intended to be used as a way to style or resize text. See below section “hN
tags as CSS classes” for Paragon classes that DO resize text to match heading levels.
Avoid skipping heading levels (h1
followed by h2
, h2
followed by h3
,...):
Levels should only be skipped when closing subsections (i.e. an h4 tag followed by an h2 tag to start a new section)
If a heading level is skipped, this could provide a negative experience to users as they may feel compelled to go back and look for the “missing” heading
An exception to this rule is headings for fixed content (e.g. sidebars) where the rankings should not change depending on the ranks of the content area. Consistency across pages is more important
There should ONE h1
per page/view and it should describe the overall purpose of the content.
If there is no
h1
in a mockup, you can add one withclassName="sr-only"
so only screen readers “see” it.Example:
<h1 className="sr-only">Hello world</h1>
Will be hidden from the view but AT will still pick up on it
This doesn’t have to be the first heading element in the document but it usually is (see this example)
The page title attribute (in the
<head>
element) should generally reflect or be a superset of theh1
content. Example:<h1>Notes</h1>
has<title>Notes | edx.org</title>
Change the
<title>
whenever the H1 changes.If possible, also push a new route onto the Back stack when the
h1
changes
Aria attributes
aria-level
Defines the hierarchical level of an element within a structure; when combined with role="heading"
will tell AT that the element is a heading. The “heading” role requires use of the aria-level
attribute to indicate its position in the hierarchy.
role="heading"
without anaria-level
attribute will default to anh2
, but you should include thearia-level
attribute for clarity anyway.This can even be done to
hN
tags if necessaryExample:
<div role="heading" aria-level="3">Hello World</div>
is the same as using anh3
This also enables devs to level headings beyond what is provided natively (
h1
-h6
)We could theoretically create an
h7
this way usingaria-level
All this being said, you should still prioritize using native hN elements over aria-level.
When this might be useful:
The main header for dialogs is, by convention, an
h2
. If there is a request to use a different tag as the top-level in the dialog, then addrole="heading"
andaria-level=N
attributes to all headers as appropriate to start from level 2
aria-labelledby
Can be used to associate headings with their page region
This can be useful when there is more than one of the same sectioning element on a page (e.g.
<nav> <main> <header> <footer> <article>
, full list here)The
id
of thehN
tag will need match the value of the parent element’saria-labelledby
attribute, and must be unique to the page.See this example of
aria-labelledby
being used to differentiate two different <nav> elementsYou should generally use
aria-labelledby
to name all<section>
and<nav>
elements in this way when appropriate visible DOM elements are present (i.e. headings). If they're not present, then you can either usearia-labelledby
with sr-only elements, or usearia-label
(which takes a human-readable string, not a DOM ID)Dialogs (
div role="dialog"
or<dialog>`
) need an accessible name, which generally comes from anaria-label
attribute oraria-labelledby
(pointing to the main header in the dialog).
hN
tags as CSS classes
Paragon provides a CSS class for each hN
element (.h1, .h2, .h3,...). These do not provide any information for screen readers or accessibility. They are intended to change font-size and styling.
Because of this, you may encounter instances such as an <h3>
tag being styled with an .h4
class:
<h3 className="h4">Hello world</h3>
There is also a .heading-label
class. Worth noting that this doesn’t effect accessibility (has no relation to aria-label
) and is primarily for styling purposes.
See Paragon https://paragon-openedx.netlify.app/foundations/typography for more information on which properties are provided in each class and how they can be implemented.
Resources
https://www.tpgi.com/heading-off-confusion-when-do-headings-fail-wcag/
https://www.w3.org/WAI/tutorials/page-structure/headings/
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Heading_Elements#nesting