Skip to main content
Design system

Keyboard accessibility

Ensuring content is navigable without a mouse


Setup

Some browsers require setup for keyboard navigation to work as expected.

  • In Safari, the tab key does not exhibit the behavior described below by default. You can press option and tab at the same time instead, or go into your Safari preferences and turn on “Press Tab to highlight each item on a webpage” in the "Advanced" pane of the preferences menu.
  • In Firefox for MacOS, you may need to toggle the following setting on before using keyboard navigation: Apple > System Settings > Keyboard > Keyboard navigation.

Keyboard focus

Some people use the keyboard (or another controller mapped to keyboard keys) instead of a mouse in order to navigate page content.

For a user to know that they can "click" an element with their keyboard (including elements that would exhibit some behavior upon mouse hover), that element must be focusable.

To be focusable means the element can be selected individually via keyboard controls. Try opening one of our pages in Google Chrome, and repeatedly press the tab key on your keyboard. You will notice that certain elements (but not every element) become highlighted or get a ring around them as you keep pressing tab. The tab key is just one of many keyboard controls available to users.


Focus rings

Keyboard users need a visual cue that a clickable or hover-able element (whether it is a button, div, link or something else entirely) is in focus as they navigate the page. This lets them know when they can click something and what it is, since they are not using a visible mouse.

The only elements that should be highlighted or have a ring around them during keyboard navigation are the ones that are interactive. All links and buttons should be focusable, but a paragraph of plaintext without any links in it should not be focusable.

We have a className="focus-highlight" Site Components Tachyon for adding the visual cue to focusable elements.

If the CSS or component logic specifies that something should happen when an element is hovered or clicked with the mouse, then that same functionality (or an accessible version) should be achievable via pressing one or more keys on the keyboard.

<div
  role="button"
  onClick={someFunction} // allows user to achieve function by clicking div
  onKeyDown={someFunction} // allows user to achieve same function via keyboard key
  tabIndex={0} // makes the div keyboard focus-able (divs by default are not)
  className="focus-highlight" // applies highlight to div when it is focused
>
  Functional div
</div>

You may be in a hurry to implement some function on a div and only add the onClick event. This makes the element impossible to use with the keyboard. So don't forget onKeyDown. Note that onKeyPress is deprecated. There is a blog post describing why onKeyDown is best.

If you are working with an element that is non-focusable by default (like a div), then you will need to make sure the element is focusable by adding two things:

(a) tabIndex={0} to make the element tab-focusable

(c) role="button" so that screen readers let users know that it can be clicked

Elements like links and buttons should be focusable by default, but make sure to always verify since people may accidentally disable this functionality without even knowing it.


Try out the following to get a better sense for how people navigate web content without a mouse. Make sure to follow the setup instructions first if using Firefox or Safari. Note the different controls for different types of interactive elements. Buttons are clickable via either space or enter, whereas links are only clickable via enter.

  • Tab: Next focusable element
  • Shift Tab: Previous focusable element
  • Enter: "Click" currently focused link or button
  • Space: Scroll down the page or — if applicable — "click" currently focused button (includes toggling a checkbox or radio button)
  • Up/Down arrows: Scroll up/down the page or — if applicable — move between radio buttons and, in some cases, menu links
  • Right/Left arrows: Adjust sliders in audio and video players or, in some cases, move between menu links
  • Escape: Close an element that has appeared dynamically, such as a popup menu or dialog. After the dynamic element closes, focus should return to where it was before the element was opened.

Limiting keyboard events to specific keys

In almost all cases, you don't want just any key on the keyboard triggering the event you added via onKeyDown.

At The Post, we have gotten around this by using onKeyDown wrapper functions like the examples below:

/**
 * Wraps specified function with button keyboard logic
 * i.e. only "Enter" or " " (the spacebar/space key)
 * keydowns will trigger the function
 *
 * @param {object} e The keyboard event
 * @param {function} fn The function to be wrapped
 * @returns {object} The wrapped function
 */
export function keyDownButtonWrapper(e, fn) {
  if (e.key === "Enter" || e.key === " ") {
    e.preventDefault(); // space scrolls page by default
    fn();
  }
}

/**
 * Wraps specified function with link keyboard logic
 * i.e. only "Enter" keydowns will trigger the function
 *
 * @param {object} e The keyboard event
 * @param {function} fn The function to be wrapped
 * @returns {object} The wrapped function
 */
export function keyDownLinkWrapper(e, fn) {
  if (e.key === "Enter") fn();
}

When incorporated into a div with an onKeyDown function, which was the first example on this page, the resulting code looks like the following:

<div
  role="button"
  onClick={someFunction}
  onKeyDown={(e) => keyDownButtonWrapper(e, () => someFunction())}
  tabIndex={0}
  className="focus-highlight"
>
  Functional div
</div>


Skip to main content

It is The Post's practice to include a "skip to main content" link in the header nav of all pages on our website.

This is a widely used standard to help keyboard users more efficiently navigate page content. Who wants to have to tab through the same links over and over on each page of a website?

The skip link gets around this by jumping the user to the unique page content, which we mark with the dom id main-content in our code.

You have to start navigating the page with your keyboard to discover this link, which is hidden to sighted users when not in keyboard focus. Open washingtonpost.com and try hitting the tab key on your keyboard. You will get something like the following:

Screenshot of the top left corner of The Washington Post website shows a white underlined hyperlink called 'Skip to main content.' The link has a blue ring around it as it is currently in keyboard focus.

Example of what "skip to main content" link looks like on washingtonpost.com


When you click this link using enter on your keyboard, you should expect to be jumped to the first clickable element in the unique page content (excluding ads, nav menus and other boilerplate that is consistent across pages).

If nothing happens, it means your page does not have a dom element with the id main-content. This is a bug, so remember to always confirm that one and only one dom element in your code has id="main-content".

Edit this page on GitHub.