kibana/style_guides/accessibility_guide.md
2018-06-27 20:50:06 -07:00

12 KiB

Accessibility (A11Y) Guide

This document provides some technical guidelines how to prevent several common accessibility issues.

Naming elements

aria-label and aria-labelledby

Every element on a page will have a name, that is read out to an assistive technology like a screen reader. This will for most elements be the content of the element. For form elements it will be the content of the associated <label> (see below).

You can overwrite that name, that is read out, by specifying a new name via the aria-label attribute. This must e.g. be done, if the element itself has no visual text representation (e.g. an icon button):

<button aria-label="Add filter"><span class="fa fa-plus"></span></button>

If you have to use a form element without a related <label> element, you can use aria-label directly on the form element to provide labeling information to screen readers.

If the actual name for that element is already present in another element, you can use aria-labelledby to reference the id of that element:

<div id="datepicker">Date Picker</div>
<button aria-labelledby="datepicker"><span class="fa fa-calendar"></span></button>

Label every form element

You should add a label for every form element:

<label for="interval">Interval</label>
<select id="interval"><!-- ... --></select>

If one label references multiple form elements, you can use the reverse logic and add aria-labelledby to all form elements:

<label id="fromLabel">From</label>
<input type="number" aria-labelledby="fromLabel">
<input type="number" aria-labelledby="fromLabel">
<input type="number" aria-labelledby="fromLabel">

You should always prefer the <label for> solution, since it also adds benefit for every user, by making the label clickable, to directly jump to the form element (or in case of checkboxes and radio buttons directly check them).

How to generate ids?

When labeling elements (and for some other accessibility tasks) you will often need ids. Ids must be unique within the page i.e. no duplicate ids in the rendered DOM at any time.

Since we have some components that are used multiple times on the page, you must make sure every instance of that component has a unique id. To make the generation of those ids easier, you can use the htmlIdGenerator service in the @kbn/ui-framework/services.

A react component could use it as follows:

import { htmlIdGenerator } from '@kbn/ui-framework/services';

render() {
  // Create a new generator that will create ids deterministic
  const htmlId = htmlIdGenerator();
  return (<div>
    <label htmlFor={htmlId('agg')}>Aggregation</label>
    <input id={htmlId('agg')}/>
  </div>);
}

Each id generator you create by calling htmlIdGenerator() will generate unique but deterministic ids. As you can see in the above example, that single generator created the same id in the label's htmlFor as well as the input's id.

A single generator instance will create the same id when passed the same argument to the function multiple times. But two different generators will produce two different ids for the same argument to the function, as you can see in the following example:

const generatorOne = htmlIdGenerator();
const generatorTwo = htmlIdGenerator();

// Those statements are always true:
// Same generator
generatorOne('foo') === generatorOne('foo');
generatorOne('foo') !== generatorOne('bar');

// Different generator
generatorOne('foo') !== generatorTwo('foo')

This allows multiple instances of a single react component to now have different ids. If you include the above react component multiple times in the same page, each component instance will have a unique id, because each render method will use a different id generator.

You can use this service of course also outside of react.

Don't use the title attribute

TL;DR Don't use the title attribute, use tooltips, aria-label, etc. instead.

The title has no clear role within the accessibility standards. See the HTML5 spec for more information.

To provide supplementary, descriptive information about a form control, button, link, or other element, that should also be visible to non vision impaired users, use a tooltip component instead.

If you need a label only for screen readers use aria-label.

Additional reading:

Interactive elements

Use <button> and <a href>

TL;DR Use <button> and <a> (with href) instead of click listeners on other elements and style it whatever way you need.

If you want to make an element clickable, use a <button> or <a href> element for it. Use a <button> whenever it causes an action on the current page, and an <a href> if it navigates to a different page. You can use click listeners just fine on these elements.

An <a> element must have an href attribute, so that (a) it will be correctly perceived as a link by a screen reader and (b) that registered click listener will correctly trigger on pressing Enter. If you don't need it, make it href="#" and call preventDefault() on the click event.

Why not use other elements?

If you create e.g. a <div> with a click listener (like <div ng-click="ctrl.doSomething()">...</div>) you will create multiple accessibility issues:

  • The element is not keyboard accessible, meaning:
    • You cannot focus it by pressing tab. You can add this behavior by adding tabindex="0" to the element.
    • You cannot trigger the click by pressing Enter or Space. We have a kbn-accessible-click directive for AngularJS and a KuiKeyboardAccessible React component to add that behavior.
  • Even if you make it keyboard accessible, a user using a screen reader won't recognize, that the div is actually an interactive element, the screen reader will just announce something like: "Sort". You would need to add role="button" or role="link" to it, so that the screen reader would actually announce something like "Sort, button" and give the user the required information, that this element is interactive.

You will need quite some work just to rebuild native logic of a button (and we haven't even touched disabled states, etc.). It is most of the time easier to just use a button or an a and style this, whatever way you want it to look (even if you don't want it to look like a button at all).

tabindex and id

TL;DR Only use tabindex="0" and tabindex="-1" and no values above 0. Always add an id to an element with tabindex.

If you want to make an element focusable, that doesn't offer this feature by default (i.e. isn't an form element or a link), you can add tabindex with a value >= 0 to it.

Why shouldn't you use values above 0?

Setting tabindex="0" will add the element to the tabbing order at the position it is in the DOM. Setting it to something above 0 will create an explicit order. Tabbing will always focus all elements with an tabindex > 0, starting from the smallest number. After all those elements has been tabbed, it will continue with all tabindex="0" or implicit tabable elements. These values are not scoped to a subtree of the DOM, but are global values. Maintaining a global order is nearly impossible and considered a serious issue by automated accessibility testing frameworks.

tabindex="-1" will remove an element from tab order, that would be focusable otherwise. You won't need this often.

Why should you add an id?

Due to some bugs in some common screen readers, you will always need to add an id to an element with tabindex, since they wouldn't pick up the tabindex correctly otherwise.

Tooltips

TL;DR Add role="tooltip" and aria-describedby to elements for accessible tooltips.

Elements which act as tooltips should have the role="tooltip" attribute and an ID to which the described element can point to with the aria-describedby attribute. For example:

<div
  class="kuiTooltip"
  role="tooltip"
  id="visualizationsTooltip"
>
  Visualizations help you make sense of your data.
</div>

<button aria-describedby="visualizationsTooltip">
 Visualizations
</button>

Don't create keyboard traps

TL;DR If you can't leave an element with Tab again, it needs a special interaction model.

If an interactive element consumes the Tab key (e.g. a code editor to create an actual tabular indentation) it will prevent a keyboard user to leave that element again. Also see WCAG 2.1.2.

Those kind of elements, require a special interaction model. A code editor could require an Enter keypress before starting editing mode, and could leave that mode on Escape again.

Unfortunately there is no universal solution for this problem, so be aware when creating such elements, that would consume tabbing, to think about an accessible interaction model.

Hint: If you create that kind of interactive elements role="application" might be a good role (also see below) for that element. It is meant for elements providing their own interaction schemes.

Roles

Each DOM element has an implicit role in the accessibility tree (that assistive technologies use). The mapping of elements to default roles can be found in the Accessibility API Mappings. You can overwrite this role via the role attribute on an element, and the assistive technology will now behave the same like any other element with that role (e.g. behave like it is a button when it has role="button").

Landmark roles

Some roles can be used to declare so called landmarks. These landmarks tag important parts of a web page. Screen readers offer a quick way to jump to these parts of the page (landmark navigation).

role=main

The main role (or equivalent using the <main> tag) declares the main part of a page. This can be used in the landmark navigation to quickly jump to the actual main area of the page (and skip all headers, navigations, etc.).

<section>

The <section> element, can be used to mark a region on the page, so that it appears in the landmark navigation. The section element therefore needs to have an accessible name, i.e. you should add an aria-label, that gives a short title to that section of the page.

TL;DR Place role="search" neither on the <input> nor the <form>, but some div in between.

Role search can be used to mark a region as used for searching. This can be used by assistive technologies to quickly find and navigate to this section.

If you place it on the input you will overwrite the implicit textbox or searchbox role, and as such confuse the user, since it loses it meaning as in input element. If you place it on the form element you will also overwrite its role and remove it from a quick jump navigation to all forms.

That's why it should be placed to an div (or any other container) between the form and the input. In most cases we already have a div there that you can easily put this role to.

Related Links: