Making actions keyboard accessible by using keyboard event handlers with WAI-ARIA controls

From WCAG WG


Status

  • New technique.
  • Add this as a related technique to F42 and F59, since this technique is a way to avoid those failures.

Applicability

HTML, Client-Side Scripting, and WAI-ARIA

WCAG references

This technique relates to:

  • Success Criterion 2.1.1 (Keyboard)
    • How to Meet 2.1.1 (Keyboard)
    • Understanding Success Criterion 2.1.1 (Keyboard)
  • Success Criterion 2.1.3 (Keyboard (No Exception))
    • How to Meet 2.1.3 (Keyboard (No Exception))
    • Understanding Success Criterion 2.1.3 (Keyboard (No Exception))


User Agent and Assistive Technology Support Notes

Description

The objective of this technique is to demonstrate how to add keyboard support to WAI-ARIA controls by invoking a scripted function in a way that is keyboard accessible. The keyboard handler must be triggered by a control that is itself keyboard accessible, either because it is a natively actionable HTML element or because the tabindex property has been used to make the control focusable. The keyboard event handlers of these elements are device independent.

The simplest design is to make the interactive control itself the focusable element, and to attach the keyboard event handlers to that element. However, the eventhandler model for the DOM is rich, and it is possible to provide one keyboard event handler that handles events from multiple different controls, if it is coded correctly.

In HTML, keyboard handlers may be triggered by keydown, keyup, or keypress events. It is also helpful to provide onclick handlers, which trigger the default action for the widget. For simple widgets, keyboard support may just provide support for the Enter key; such widgets invoke the default action both for onclick and for pressing Enter. However, many WAI-ARIA controls are complex and support a variety of keyboard commands for interacting with the widget. Recommended design patterns for keyboard interaction are defined in WAI-ARIA 1.0 Authoring Practices.

Examples

Example 1: Using Space to activate a button

This example is based on Example 1 from SCR35: Making actions keyboard accessible by using the onclick event of anchors and buttons. Adding the ARIA role "button" makes it clear that this is a button control and not a link. Because we are using an anchor element, the control is focusable and the browser will automatically call the onclick handler for the Enter keystroke. Since users expect to be able to activate buttons with either Enter or Space, a keyboard handler provides the support for activating the button via the Space character.

Note that supporting SPACE in addition to ENTER is not required by WCAG 2.0; this control would still be keyboard accessible without the key event handler for SPACE. However, supporting platform keyboard conventions for controls is strongly encouraged.

Example Code:

<script> 
function doStuff()
 {
  //do stuff
    return false;
  }
function keyHandler(event)
  {
    switch (event.which) {
      case KEY_SPACE: {
        event.stopPropagation;
        return doStuff();
        break;
      }
    } //end switch
    return true;
  }
</script>
<a role="button" href="#" 
    onclick="return doStuff();"
    onkeypress="return keyHandler(e);">
  do stuff
</a>

Example 2: Keyboard support for a set of toggle buttons

This example is derived from Open Ajax Alliance Example 3 - Button role example using text-only buttons, which contains complete source code and a live example. The complete example includes mouse as well as keyboard support.

ul list mark-up is used to define the set of buttons. Each button is implemented by a li element with role "button". The "aria-pressed" attribute indicates that these are toggle buttons. To make the buttons focusable, they are given "tabindex=0" attributes. An onkeydown keyhandler has been attached to each button to toggle the button when Space or Enter is pressed.

Example Code:

<script> 
  var KEY_ENTER = 13;
  var KEY_SPACE = 32;

  function buttonKey(event) {

    var ariaControls = '#' + $(this).attr('aria-controls');

    switch (event.which) {
      case KEY_ENTER:
      case KEY_SPACE: {
        switch($(this).attr('aria-labelledby')) {
          case 'italic_label': {
            $(ariaControls).toggleClass('italic');
            break;
          }
          case 'bold_label': {
            $(ariaControls).toggleClass('bold');
            break;
          }
          case 'larger_label': {
            increaseFontSize(ariaControls);
            break;
          }
          case 'smaller_label': {
            decreaseFontSize(ariaControls);
            break;
          }
        } // end switch

        if ($(this).hasClass('toggleButton') == true) {

            // This is a toggle button: toggle aria-pressed state
            togglePressed(this);
        }
        else
        {
            // This is a momentary pushbutton: Set aria-pressed to true
            $(this).attr('aria-pressed', 'true');
        }

        event.stopPropagation();
        return false;

      } // end case
    } // end switch

    return true;
  }); // end button keydown handler

  // Attach the handler to the buttons
  var button = document.getElementById("larger1");
  button.onkeydown = buttonKey;
  var button = document.getElementById("smaller1");
  button.onkeydown = buttonKey;
  var button = document.getElementById("italic1");
  button.onkeydown = buttonKey;
  var button = document.getElementById("bold1");
  button.onkeydown = buttonKey;

</script>

<ul id="buttonset" class="buttons" title="Text Formatting Controls">

    <li id="larger1"  
        role="button"
        tabindex="0"
        aria-pressed="false"
        aria-controls="text1"
        aria-labelledby="larger_label">+</li>
            
    <li id="smaller1"  
        role="button"
        tabindex="0"
        aria-pressed="false"
        aria-controls="text1"
        aria-labelledby="smaller_label"
        onkeydown="return buttonKey(event);">-</li>
          
    <li id="italic1"  
        role="button"
        class="toggleButton italic"
        tabindex="0"
        aria-pressed="true"
        aria-controls="text1"
        aria-labelledby="italic_label"
        onkeydown="return buttonKey(event);">i</li>
        
    <li id="bold1"  
        role="button"
        class="toggleButton"
        tabindex="0"
        aria-pressed="false"
        aria-controls="text1"
        aria-labelledby="bold_label"
        onkeydown="return buttonKey(event);">B</li>
        
</ul>

Example 3: Attaching the keyboard handler to the set of toggle buttons

This example is very similar to Example 2. However, instead of attaching the onkeydown handler to each button, we attach it to the ul that encloses the set of buttons. In the handler, this refers to the element that has focus. Since the buttons still take keyboard focus, this identifies the button that should be toggled.

Example Code:

<script>
  ...

  // Attach the handler to the buttons,
  // replacing this section of Example 2
  var button = document.getElementById("buttonset");
  button.onkeydown = buttonKey;

</script>

Resources

  • HTML 4.01 Scripts
  • Document Object Model (DOM) Technical Reports

Related Techniques

  • G90: Providing keyboard-triggered event handlers
  • H91: Using HTML form controls and links
  • SCR20: Using both keyboard and other device-specific functions
  • F42: Failure of Success Criterion 1.3.1 and 2.1.1 due to using scripting events to emulate links in a way that is not programmatically determinable
  • F59: Failure of Success Criterion 4.1.2 due to using script to make div or span a user interface control in HTML

Tests

Procedure

For the interactive control:

  1. Check whether there are keyboard shortcuts for every operation of the interactive control.
  2. Check whether there are key handlers for keyboard shortcuts attached to the element or one of its ancestors.
  3. Check that there is at least one element between the control and the element with the appropriate key handler can take focus, that is,
    • the element is a native HTML control, or
    • the element has a tabindex with value of 0 or greater.
  4. Check that when focus is set to the focusable element in the ancestors of the interactive control, keyboard actions are handled by the key handler for the interactive control.
  5. Check that all key handlers execute the appropriate action for the interactive control.

Expected Results

  • Checks 1, 2, 3, 4 and 5 are true.