Skip to main content
Design system

Semantic HTML and ARIA

Heading order, tables, labels and screen reader support


Heading order and page landmarks

Heading order motivation

Someone who is using a screen reader may jump around the headings on a page to quickly scan content. Headings have a hierarchy: A page should have only one h1 corresponding to its title, and h2s are used for major subsections, h3s are for subsections of h2s and so on.

If headings are used our of order, such as an h2 being placed below an h3 despite the h2 content being a subsection of h3, then the user may be confused. Skipping heading levels entirely, such as going from an h2 to an h4, can also cause issues.

It may be tempting to use headings out of order based on their styles, but this should not be done. You can add a class to a heading to provide extra styles, or rework your heading styles entirely because they may not make sense in the first place if you are being tempted to use headings out of order.

Heading order solution

There must be only one h1 on any given page, as this corresponds to the page title or headline.

After that, the heading level represents the "scope" of the subsection. For instance, if you are working with an election results page showing the results of Democratic and Republican primaries in the state of Missouri, it would make sense to have the title of an individual office seat (such as governor) be an h2, while the Democratic and Republican primaries for that seat are each headed with an h3.

Page landmarks

Our Washington Post article pages contain landmarks that screen reader users can quickly access via the rotor to jump around a page and hear different types of content.

We use the following landmarks as documented by W3C: main, nav, aside, header and footer. We also use the article landmark defined by MDN to denote self-contained content such as an article or user-submitted comment.

In addition to the above, we can use form and section tags to define other key regions on a page, but we need to include an aria-labelledby, aria-label or title attribute on these elements so screen readers know what they contain.

Similarly, if the same type of landmark is used on a page multiple times (for example, two navs or two asides), then we should add an aria-label to each duplicate landmark to differentiate them.

Inspect one of our articles with the Chrome developer tools to find the landmarks. You can also open a screen reader rotor to view a list of landmarks.

The example code below demonstrates one way we can arrange landmarks on article pages. There are other possible arrangements. Your main focus in using the landmarks should be that they contain appropriate content. Otherwise screen reader users may be confused.

<nav>
   <p>Header navigation links, etc.</p>
</nav>
<main>
   <header>
      <p>Article headline</p>
   </header>
   <article>
      <p>Article content</p>
   </article>
</main>
<aside>
   <p>Right rail content</p>
</aside>
<footer>
   <p>Footer navigation links</p>
</footer>

Labeling interactive elements

Our docs address the need for alt text when a reader cannot see an image.

The aria-label and aria-labelledby conventions are for interactive elements that are not images. See the ARIA guide for more context.

aria-label

You might use aria-label on a clickable div on a page to clarify for the user its purpose:

<div
  aria-label="Submit form" // text that will be read aloud by screen reader
  role="button" // also read by screen reader to say div behaves like a button
  onClick={someFunction} // allows user to achieve function by clicking div
  onKeyDown={someFunction} // allows user to achieve same function via keyboard
  tabIndex={0} // makes the div keyboard focus-able (divs by default are not)
  className="focus-highlight" // applies highlight to div when it is focused
/>

Notice that the aria-label attribute is passed a string value to be read by a screen reader.


An aria-label can also be useful when you want to have two versions of the same text shown. For instance, in many tables on our live elections results pages we include abbreviations of certain words, such as "Unc." for "Uncontested," "Pct." for "Percent," etc.

We can use aria-label to write text that is only seen by screen readers, while the standard component text remains seen by non-screen-reader users.

// Full word read by screen reader, but abbreviation seen on the visible page
<span aria-label="Uncontested">Unc.</span>

aria-labelledby

You might use aria-labelledby to clarify for a screen reader the connection between an input field and its purpose:

// copied from https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-labelledby_attribute
<div id="myBillingId">Billing</div>

<div>
    <div id="myNameId">Name</div>
    <input type="text" aria-labelledby="myBillingId myNameId"/>
</div>
<div>
    <div id="myAddressId">Address</div>
    <input type="text" aria-labelledby="myBillingId myAddressId"/>
</div>

Note that the aria-labelledby is passed a string containing an element id or multiple ids separated by spaces. The text content of the elements with these ids is what will be read by the screen reader so that the user understands what each input field is for (in this case, "Billing Name" and "Billing Address").

There is also an aria-describedby property meant to connect elements with relevant (but not essential information). This is not used as commonly, and aria-labelledby should be used regardless of whether a value for aria-describedby is also present on a given element.


It is important that hyperlink text is meaningful even out of context. This is because many screen reader users jump between links on a page without hearing the text surrounding each one. Read our screen reader guide for more info.

There are some key things to watch for when writing link text:

  • Avoid using the URL as the link text.
    • URLs are often read by screen readers in a disjointed manner, and they can also be confusing and less meaningful for sighted users.
  • Keep link text concise, but don't sacrifice meaning. This speeds up the process of scanning links as a user, especially as a screen reader user.
    • For example, "Subscribe" is better than "Subscribe for more content from us in the future."
  • Don't include words like "link" or "click here" in your link text.
    • This is redundant. Links should be evident in their styling and structure. If you feel the need to specify "click here," then your link probably isn't styled correctly for keyboard accessibility.
  • Ensure each unique link has unique link text.
    • If multiple links have different text but the same value, then users may waste their time clicking them all. This is a frustrating user experience.
    • Similarly, if multiple links have the same text but go to different sites, then users may miss out because they assumed the links were the same.

Hiding content from screen readers

There are often ads on our products. When third-party ads are used, we don't have full control over their accessibility. For screen reader users, inaccessible ads hinder page navigation.

Our practice is to apply aria-hidden="true" on our ad wrapper elements so that they are ignored by screen readers and thus do not clutter the page. The guide to ARIA attributes has more context.

We use aria-hidden="true" on non-ad content if and only if it is useless to screen readers (it is purely decorative or it is innaccessible content that already has a screen reader accessible alternative elsewhere on the page).


Content for screen readers only

sr-only class

Try searching "sr-only" on our Tachyons search page. You should find the following class:

.sr-only {/* all screens */
  clip: rect(0,0,0,0);
  border: 0;
  height: 1px;
  margin: -1px;
  overflow: hidden;
  padding: 0;
  position: absolute;
  width: 1px;
}

This is a best practice for adding elements to a page that we want available only to screen reader users. For instance, we might have a table on a page and want a caption describing that table for screen readers without taking up space on the visible page layout.

You can add that caption to the table in the HTML and apply the sr-only class to ensure the text is available to screen readers but not visible.

A link with the sr-only class applied will stay hidden to sighted users at all times, evem when it receives keyboard focus. This can cause confusion when sighted users navigate a page with keyboard controls. For this reason, sr-only is generally not appropriate when working with interactive elements like links, buttons, etc. Use Visually Hidden (below) instead.

Visually Hidden component

See our Visually Hidden component docs for more information. It functions similarly to sr-only, but they are not quite the same. For example, a Visually Hidden link will appear to sighted users when in focus. On the other hand, a link with the sr-only class applied will stay hidden to sighted users at all times. For this reason, sr-only is generally not appropriate when working with interactive elements like links, buttons, etc. Use Visually Hidden instead.


Non-English language support

Content in non-English languages, such as Arabic or Spanish, must have the lang="CODE" attribute set, where CODE is the language code such as “ar” for Arabic or “es” for Spanish. There’s a list of language codes.

Additionally, Arabic and some other languages are read right-to-left. This means we need to set an additional attribute in the HTML for Arabic text. Add dir="rtl to the DOM elements with lang="ar".

Below is an example of how this looks:

<p dir="rtl" lang="ar">
  نجم هذا القرار عن دعوى قضائية في محكمة جزئية فدرالية في واشنطن ضد ابن سلمان
  وحوالي 20 متهمًا آخر رفعتها خطيبة جمال خاشقجي، كاتب الأعمدة المساهم في صحيفة
  البوست الذي قُتِل على يد عملاء سعوديين في إسطنبول يوم 2 أكتوبر/تشرين الأول
  2018. وتزعم الدعوى أنّ ولي العهد والمتهمين الآخرين مسؤولون عن جريمة القتل تلك.
</p>

It is very important that this is done correctly. A screen reader user may not hear content in non-English languages correctly if it is not tagged correctly. For example, if Arabic is not tagged correctly, then it is not read by the built-in MacOS & iOS screen reader VoiceOver. Instead, each character is read individually: “Arabic character X, Arabic Character Y,” etc. VoiceOver also includes different voices that pronounce the words from different languages. If lang is not set on non-English text, then VoiceOver will not use the correct voice.


Tables

W3C provides more guidance for making tables more accessible to screen readers.

The caption tag

The caption tag is an important component of tables that is used by screen readers to provide an overview of a table when it is first reached. If we don't want the caption to be shown on the visible page (in some cases, we might), then we can hide it using the sr-only className.

<table>
   <caption className="sr-only">Total votes for Biden, Sanders and Warren in Super Tuesday states</caption>
   <thead>

The thead tag

For a table with only column headers, the thead tag contains the headers for each column. Even though the tag is called thead, the header elements must still be th tags, not td tags, in order for the screen reader to read the contents of the table correctly.

This is because screen readers read the header of the column for every cell within that column as a reminder to the reader of what is being read aloud. For instance, if our table contains vote totals and we have:

<thead>
    <tr>
        <td>State</td>
        <td>Votes for Biden</td>
        <td>Votes for Sanders</td>
        <td>Votes for Warren</td>
    </tr>
</thead>
<tbody>
    <tr>
        <td>Alabama</td>
        <td>15,000</td>
        <td>7,000</td>
    <td>2,000</td>
    </tr>
    ... // table rows and data for a bunch of other states
    <tr>
        <td>Virginia</td>
        <td>13,000</td>
        <td>8,000</td>
    <td>3,000</td>
    </tr>
<tbody

Then the screen reader will only read the headings once, at the top of the table, and the listener has to remember the order of (1) Biden, (2) Sanders and (3) Warren for the rest of their time reading the table, as they will only be read a bunch of state names and numbers sequentially.

On the contrary, if we have:

<thead>
    <tr>
        <th>State</th>
        <th>Votes for Biden</th>
        <th>Votes for Sanders</th>
        <th>Votes for Warren</th>
    </tr>
</thead>
<tbody>
    <tr>
        <td>Alabama</td>
        <td>15,000</td>
        <td>7,000</td>
    <td>2,000</td>
    </tr>
    ... // table rows and data for a bunch of other states
    <tr>
        <td>Virginia</td>
        <td>13,000</td>
        <td>8,000</td>
    <td>3,000</td>
    </tr>
<tbody

Then the screen reader user will hear "Votes for Warren - 3,000" when they get to the last cell in the table. This makes for a much more user-friendly experience, and this is why we should always make sure to use the th tag in our thead elements.


Alerting screen readers of live updates

aria-live is an attribute used to notify people using screen readers of dynamic changes in the content of a live region of a page. See the ARIA documentation on aria-live for more details.


While this attribute can serve a purpose in terms of accessibility, it should be used extremely cautiously. For instance, a screen reader user may be bothered repeatedly if aria-live is applied for something like new comments on an article.

We almost always want to use aria-live="polite" when we do use aria-live. This makes it so that updates do not interrupt the screen reader user and instead waits til they idle.

Edit this page on GitHub.