Support keyboard interaction

Make sure that controls can be operated using the keyboard. Native interactive HTML elements, such as links and form controls, support keyboard accessibility by default, and these should be used wherever possible. However, many custom controls and widgets use elements such as div, span, and li, which do not by default provide keyboard support. In this case, keyboard support must be supplied by the developer, using HTML and JavaScript.

  • Make sure every active control is in focus order. Ensure that custom controls can receive keyboard focus, by giving the control’s container element a tabindex attribute with the value of “0”.
  • Make sure currently inactive controls are not in focus order. If a control should not be in focus order initially but may become active, such as a dropdown menu item, it should have a tabindex value of “-1”, which can be changed using scripting.
  • Make sure that active controls are operable using the keyboard. Ensure that when a control has focus it can be activated using standard keystrokes—for example, a button can be pressed using the spacebar, a list of menu items can be traversed using the arrow keys, and a dialog can be dismissed using the Escape key. This requires that event-handling scripts listen for keypress events and, where necessary, suppress native keypress behavior.
  • Make sure that content that appears/disappears, is designed such that users can perceive the additional content and dismiss it without disrupting the page experience. When dynamically adding content, be certain that users can move the pointer over the additional content to keep it visible, that the content remains open as long as it's relevant and as long as the user is interacting with it, and that the additional content is dismissable from the keyboard without moving focus/hover. In this context, custom tooltips, sub-menus, and other nonmodal popups that display on hover and focus are examples of additional content.
  • Make certain that users are able to turn off or reconfigure keyboard shortcuts. Character key shortcuts are frustrating for speech input users, who are accustomed to use strings of characters for input, and for any keyboard user who is prone to accidentally hitting keys. For these reasons, if a keyboard shortcut is implemented, users must be able to either turn the shortcut off, remap the shortcut to one or more non-printable characters (e.g., Alt, Ctrl), or the keyboard shortcut is only active when a user interface component has focus.

Testing

Use the keyboard to tab through every element on a page. For each element, check that the answer to the following questions is “yes”:

  • Does it receive keyboard focus?
  • If so, when it has focus, can it be activated using standard keys?
  • Can dynamically added content that appears/disappears, be perceived and controlled by assistive technology users, including the ability to dismiss the additional content without disrupting the page experience?
  • Can a keyboard shortcut be turned off or reconfigured by the user?

More information

✎ Technique: Content on hover or focus

Make it easier for users to interact with added content. Tooltips, drop-down menus, and popups are examples of added content. This new content is usually made visible when you put your mouse pointer over a "trigger" item such as a button or link. It can also display when an item receives focus. Some people have difficulty keeping the mouse pointer over the trigger item. Also, the new content will sometimes obscure existing content. The three requirements of this success criterion, as outlined below, help make the added content more operable.

Dismissable

When using magnification, tooltips or added content can display on top of other information. Unless the newly visible content is displaying an error, the user must be able to dismiss that content without moving the mouse or changing focus. The easiest way to implement this is by providing a keyboard command, such as pressing the escape key, to close the content. The developer can also include a close button within the added content.

Hoverable

Often the new content only displays when the mouse pointer is over the trigger element and disappears when the mouse moves off of the trigger. It can be hard for people with tremors or limited movement to keep the mouse over the trigger element. To remedy that situation, the content must stay visible as long as the mouse remains over the trigger or the mouse moves onto and remains over the new content.

Persistent

This rule combines the previous two. The content must remain visible until the user dismisses it, the hover or focus trigger is removed, or the information is no longer valid.

  

Examples

✗ Bad example: new content is visible only for the time defined by the program.

 

New content is visible when tab key selects the link or when mouse hovers the link

New content is visible when the mouse hovers the link

New content disappears after the time defined by the program

New content is dismissed by the time set by the program

✓ Good example: new content is visible as long as the user decides to move away.

New content is visible when tab key selects the link

New content is visible when tab key selects the link

New content is visible when mouse hovers the link

New content is visible when the mouse hovers the link

New content is invisible when tab key or mouse move away

New content is invisible when tab key or the mouse are dismissed

(Source: Knowbility article on content hover focus)

✎ Technique: Character key shortcuts

If keyboard shortcuts are implemented using only a letter (including upper- and lower-case letters), punctuation, number, or symbol characters, provide a way to turn off or remap character key shortcuts:

  • Turn off: A mechanism is available to turn the shortcut off;
  • Remap: A mechanism is available to remap the shortcut to use one or more non-printable keyboard characters (e.g. Ctrl, Alt, etc);
  • Active only on focus: The keyboard shortcut for a user interface component is only active when that component has focus.

If you provide character key shortcuts, provide a way to turn them off and a way to remap them. When remapping, allow users to assign one or more characters to the shortcut.

(Source: Knowbility article on character key shortcuts)

✎ Technique: Accessible modal dialogs

Modal dialogs can enhance usability by focusing attention on a specific message that requires a user action to continue.

An accessible modal dialog is one where keyboard focus is managed properly, and the correct information is exposed to screen readers. HTML and WAI-ARIA can be used to provide the necessary semantic information, CSS the appearance and Javascript the behavior.

Example

In this modal dialog example, we’ll look at the HTML, CSS and JavaScript separately.

HTML

The dialog itself must be constructed from a combination of HTML and WAI-ARIA attributes, as in this example:


<div id="dialog" role="dialog" aria-labelledby="title" aria-describedby="description">
  <h1 id="title">Title of the dialog</h1>
  <p id="description">Information provided by the dialog.</p>
  <button id="close" aria-label="close">×</button>
</div>

Note the dialog role, which tells assistive technologies that the element is a dialog. The aria-labelledby and aria-describedby attributes are “relationship” attributes that connect the dialog to its title and description explicitly. So when focus is moved to the dialog or inside it, the text within those two elements will be read in succession. The close button has an aria-label attribute that overrides the element’s text character of “times” to read “close” when screen readers interact with it.

CSS

As well as some CSS for color and positioning, the dialog is set to display:none by default. When the custom attribute open is added to the dialog with JavaScript, the dialog is revealed.


[role="dialog"][data-open] {
  display: block;
}

Distinctive focus styles are added for the dialog’s the opening and closing buttons so that it’s clear to keyboard users which element is focused. This style is paired with the hover style so that keyboard and mouse operation look consistent.


button:focus, button:hover  {
  outline: 3px solid #d4aa00;
}

JavaScript

When the “trigger” button is pressed, the script runs the openDialog() function:


function openDialog() {
  dialog.setAttribute('data-open', '');
  close.focus();
  close.addEventListener('keydown', function(e) {
    if (e.keyCode == 9) {
      e.preventDefault();
    }
  });
  document.getElementById('cover').style.display = 'block';
  document.addEventListener('keydown', addESC);
}

First, dialog.setAttribute('data-open', '') adds the open attribute to the dialog, which sets the dialog’s CSS display value to block. Next, focus is moved to the dialog’s close button. This does two things: It triggers the announcement of the dialog's title and description in screen readers, and it makes the dialog easy to close at the press of a key.

The next block confines focus to the close button, making sure the user does not accidentally leave the dialog until it has been dismissed. That is, if the TAB key is pressed—which would move focus away from the dialog—the default behavior is suppressed with e.preventDefault();.

Note the line with "addESC" at the end. That adds a listener for the ESC key so that when it is pressed, closeDialog() is run. It is conventional to be able to close a dialog with the ESC key.


var addESC = function(e) {
  if (e.keyCode == 27) {
    closeDialog();
  } 
}

Clicking or pressing ENTER on the close button will also fire closeDialog():


function closeDialog() {
  dialog.removeAttribute('data-open');
  trigger.focus();      
  document.getElementById('cover').style.display = 'none';
  document.removeEventListener('keydown', addESC);
}

Note the trigger.focus() line that moves focus back to whichever element opened the dialog. This is important: Keyboard users should always be returned to where they were before they opened the dialog. If you don't do that, when the close button is hidden (and no longer focusable) the <body> element will be focused by default. That will force keyboard users to step through the page manually to find the spot where they left off.

Code editor

Try operating the dialog with only your keyboard in the code editor provided (external link). See how the keyboard operation is affected when you remove the JavaScript lines that manage focus!

See the Pen accessible modal dialog by HUIT - Web, UX and Digital Accessibility Services (@hwpdas) on CodePen.

✎ Technique: Expandable sections

Expandables (sometimes called “collapsible” or “disclosure widgets”) are simple interface patterns that allow you to expand and collapse content. They can be helpful accessibility aids as they give users the choice of revealing content to read it, or bypassing the content, making page navigation more efficient for screen-reader users and people using the keyboard or alternative input devices.

To ensure that they are accessible, it's important that expandable sections are coded so that their state (expanded or collapsed) and the relationship between the toggle button and the expandable content are established programmatically.

It's also important to ensure that the toggle functionality can be operated using the keyboard. This means including the toggle button in the focus order and supporting standard keyboard shortcuts (spacebar and Enter) to activate the button.

Example

This code example shows the collapsed state. Note the aria-controls relationship attribute that establishes the relationship between the toggle button and the content (through the content’s id). Also note that the aria-expanded state attribute is set to false and, correspondingly, aria-hidden is set to true.


<h3>
  <button data-action="disclosure" aria-controls="section-name" aria-expanded="false">Title of section</button> </h3> <p id="section-name" aria-hidden="true">Section content tincidunt turpis nec ultrices tristique. Aliquam bibendum metus dignissim dui placerat, sed porta urna blandit. Ut condimentum magna sit amet nulla finibus vestibulum.</p> 

This code example shows the expandable section in its expanded state. Note the change in the aria-expanded and aria-hidden values.

    
<h3>
  <button data-action="disclosure" aria-controls="section-name" aria-expanded="true">Title of section</button>
</h3>
<p id="section-name" aria-hidden="false">Section content tincidunt turpis nec ultrices tristique. Aliquam bibendum metus dignissim dui placerat, sed porta urna blandit. Ut condimentum magna sit amet nulla finibus vestibulum.</p>
    
  

✗ Bad example

In this example, the <h3> has been used as the toggle element. The <h3> element is not a natively interactive element, and so there's no way to focus it with a keyboard.

<h3 aria-controls="section-name" aria-expanded="true">Title of section</h3>
<p id="section-name" aria-hidden="false">Section content tincidunt turpis nec ultrices tristique. Aliquam bibendum metus dignissim dui placerat, sed porta urna blandit. Ut condimentum magna sit amet nulla finibus vestibulum.</p> 

Code editor

Code editor for the expandable pattern as a jQuery plugin (external link)

See the Pen expandable section with jQuery plugin by HUIT - Web, UX and Digital Accessibility Services (@hwpdas) on CodePen.

✎ Technique: Focus order and tabindex

Interactive elements should, under most circumstances, be focusable in the order that they appear in the source code. This helps people who are using the keyboard or alternative input devices to follow focus in a logical order.

The order that elements appear in the document source should reflect the order they appear visually.

Examples

Bad example

The tabindex attribute can adjust the natural focus order of interactive elements like buttons and form inputs. For instance, tabindex="1" lets you make an element the first element focusable within a page, irrespective of where it appears in the source. However, this disrupts the page’s intuitive focus order. In the following example, the third button will receive focus first, then—because the first and second buttons have no positive tabindex value set—the first button will be focused next:


<button>first button</button> 
<button>second button</button>
<button tabindex="1">third button</button>

The only way to guarantee that an interactive element receives focus after the "third button" element would be to give it tabindex="2", and then give the next element tabindex="3", and so on, incrementing the tabindex value for each successive element. This can become unmaintainable very quickly, especially when the page is likely to accumulate new active elements over time.

Good example

For buttons, links and other interactive elements that are natively focusable, the best strategy is to avoid using the tabindex attribute altogether. In the following example, the first button is focused first and then after that, the third button is focused. The reason the second button is not in the focus order is because it is disabled (through the disabled attribute).


<button>first button</button> 
<button disabled>second button</button>
<button>third button</button>

Custom button example

The tabindex attribute is helpful for focus management in some cases, however. 

When making custom interactive elements (as one would when constructing a web component), they don't always automatically have traits like focusability built into them. You have to provide these manually. To make a custom element focusable like a <button> is, you must supply it with a tabindex value of 0:


<my-button role="button" tabindex="0">first button</my-button> 
<my-button role="button" tabindex="0">second button</my-button> 
<my-button role="button" tabindex="0">third button</my-button>

In the above code example, the custom <my-button> elements will be focused logically from first to third.

Disabling with tabindex="-1"

Custom elements do not support the disabled attribute, which would normally make them unfocusable (as in the second example). To simulate this behavior, you can give the element a tabindex value of -1.


<my-button role="button" tabindex="0">first button</my-button> 
<my-button role="button" tabindex="-1">second button</my-button> 
<my-button role="button" tabindex="0">third button</my-button>

In the above code example, the second button is not focusable. So focus moves directly from the first button to the third. To make sure that the simulated disabled styles for custom elements are in keeping with the disabled styles of standard buttons, you can use attribute selectors in your CSS:


button[disabled], [role="button"][tabindex="-1"] {
  opacity: 0.6; /* fade the element out, to reduce its visual affordance */
}

Code editor

Test the tabindex values in the preceding examples using this code editor. Be sure to experiment by changing and removing the tabindex attributes before and then use your TAB key to switch focus between the elements.

See the Pen test tabindex values by HUIT - Web, UX and Digital Accessibility Services (@hwpdas) on CodePen.

✎ Technique: Keyboard operable custom controls

Sometimes, design requirements mean that you need to create an interactive element from a base <div> element or a custom element like <my-button> rather than using a native HTML element.

Your element will not be accessible by keyboard unless it is included in focus order and it can be activated using the ENTER and SPACE keys.

Example

Let's consider a <div> element with an id of "button", to which we want to attach JavaScript click events. The first thing we need to do for keyboard accessibility is make it focusable using a tabindex value of 0:


<div id="button" tabindex="0">trigger click event</div>

Now keyboard users will be able to focus it like a standard <button> control. However, the only way to fire the click event is to click the element using the mouse. Unlike <button>s, <div>s don't support keyboard activation by default, so we have to trigger the click event using a custom key binding:


var button = document.getElementById('button');
button.addEventListener('keydown', function(e) {
  if (e.keyCode === 13 || e.keyCode === 32) {
    e.preventDefault();
    this.click();
  }
});

In the above code, we listen to keydown events on our button. If the key code associated with a keydown event matches either the ENTER key (13) or SPACE key (32), we call the button's click method, which simulates a mouse click to the button. To prevent the page from scrolling when the SPACE key is pressed, the default behavior is suppressed.