[7.x] Remove Kibana a11y guide in favor of EUI (#57021) (#57134)

* Remove Kibana a11y styleguide in favor of EUI and moving auxilary content to CONTRIBUTING.md and STYLEGUIDE.md

Co-Authored-By: Tim Roes <mail@timroes.de>
This commit is contained in:
Michail Yasonik 2020-02-07 14:45:01 -05:00 committed by GitHub
parent f6b43b46a8
commit 850e520072
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 101 additions and 314 deletions

View file

@ -27,6 +27,7 @@ A high level overview of our contributing guidelines.
- [Instrumenting with Elastic APM](#instrumenting-with-elastic-apm)
- [Debugging Unit Tests](#debugging-unit-tests)
- [Unit Testing Plugins](#unit-testing-plugins)
- [Automated Accessibility Testing](#automated-accessibility-testing)
- [Cross-browser compatibility](#cross-browser-compatibility)
- [Testing compatibility locally](#testing-compatibility-locally)
- [Running Browser Automation Tests](#running-browser-automation-tests)
@ -542,6 +543,23 @@ yarn test:mocha
yarn test:browser --dev # remove the --dev flag to run them once and close
```
### Automated Accessibility Testing
To run the tests locally:
1. In one terminal window run `node scripts/functional_tests_server --config test/accessibility/config.ts`
2. In another terminal window run `node scripts/functional_test_runner.js --config test/accessibility/config.ts`
To run the x-pack tests, swap the config file out for `x-pack/test/accessibility/config.ts`.
After the server is up, you can go to this instance of Kibana at `localhost:5620`.
The testing is done using [axe](https://github.com/dequelabs/axe-core). The same thing that runs in CI,
can be run locally using their browser plugins:
- [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)
- [Firefox](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/)
### Cross-browser Compatibility
#### Testing Compatibility Locally

View file

@ -6,7 +6,7 @@ recommended for the development of all Kibana plugins.
Besides the content in this style guide, the following style guides may also apply
to all development within the Kibana project. Please make sure to also read them:
- [Accessibility style guide](style_guides/accessibility_guide.md)
- [Accessibility style guide](https://elastic.github.io/eui/#/guidelines/accessibility)
- [SASS style guide](https://elastic.github.io/eui/#/guidelines/sass)
## General
@ -45,10 +45,7 @@ This part contains style guide rules around general (framework agnostic) HTML us
Use camel case for the values of attributes such as `id` and `data-test-subj` selectors.
```html
<button
id="veryImportantButton"
data-test-subj="clickMeButton"
>
<button id="veryImportantButton" data-test-subj="clickMeButton">
Click me
</button>
```
@ -74,6 +71,59 @@ It's important that when you write CSS/SASS selectors using classes, IDs, and at
capitalization in the CSS matches that used in the HTML. HTML and CSS follow different case sensitivity rules, and we can avoid subtle gotchas by ensuring we use the
same capitalization in both of 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 `id`s easier, you can use the `htmlIdGenerator` service in the `@elastic/eui`.
A React component could use it as follows:
```jsx
import { htmlIdGenerator } from '@elastic/eui';
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:
```js
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 also use this service outside of React.
## API endpoints
The following style guide rules are targeting development of server side API endpoints.
@ -90,7 +140,8 @@ API routes must start with the `/api/` path segment, and should be followed by t
Kibana uses `snake_case` for the entire API, just like Elasticsearch. All urls, paths, query string parameters, values, and bodies should be `snake_case` formatted.
*Right:*
_Right:_
```
POST /api/kibana/index_patterns
{
@ -108,19 +159,19 @@ The following style guide rules apply for working with TypeScript/JavaScript fil
### TypeScript vs. JavaScript
Whenever possible, write code in TypeScript instead of JavaScript, especially if it's new code.
Whenever possible, write code in TypeScript instead of JavaScript, especially if it's new code.
Check out [TYPESCRIPT.md](TYPESCRIPT.md) for help with this process.
### Prefer modern JavaScript/TypeScript syntax
You should prefer modern language features in a lot of cases, e.g.:
* Prefer `class` over `prototype` inheritance
* Prefer arrow function over function expressions
* Prefer arrow function over storing `this` (no `const self = this;`)
* Prefer template strings over string concatenation
* Prefer the spread operator for copying arrays (`[...arr]`) over `arr.slice()`
* Use optional chaining (`?.`) and nullish Coalescing (`??`) over `lodash.get` (and similar utilities)
- Prefer `class` over `prototype` inheritance
- Prefer arrow function over function expressions
- Prefer arrow function over storing `this` (no `const self = this;`)
- Prefer template strings over string concatenation
- Prefer the spread operator for copying arrays (`[...arr]`) over `arr.slice()`
- Use optional chaining (`?.`) and nullish Coalescing (`??`) over `lodash.get` (and similar utilities)
### Avoid mutability and state
@ -131,7 +182,7 @@ Instead, create new variables, and shallow copies of objects and arrays:
```js
// good
function addBar(foos, foo) {
const newFoo = {...foo, name: 'bar'};
const newFoo = { ...foo, name: 'bar' };
return [...foos, newFoo];
}
@ -250,8 +301,8 @@ const second = arr[1];
### Magic numbers/strings
These are numbers (or other values) simply used in line in your code. *Do not
use these*, give them a variable name so they can be understood and changed
These are numbers (or other values) simply used in line in your code. _Do not
use these_, give them a variable name so they can be understood and changed
easily.
```js
@ -325,19 +376,18 @@ import inSibling from '../foo/child';
Don't do this. Everything should be wrapped in a module that can be depended on
by other modules. Even things as simple as a single value should be a module.
### Only use ternary operators for small, simple code
And *never* use multiple ternaries together, because they make it more
And _never_ use multiple ternaries together, because they make it more
difficult to reason about how different values flow through the conditions
involved. Instead, structure the logic for maximum readability.
```js
// good, a situation where only 1 ternary is needed
const foo = (a === b) ? 1 : 2;
const foo = a === b ? 1 : 2;
// bad
const foo = (a === b) ? 1 : (a === c) ? 2 : 3;
const foo = a === b ? 1 : a === c ? 2 : 3;
```
### Use descriptive conditions
@ -475,13 +525,12 @@ setTimeout(() => {
Use slashes for both single line and multi line comments. Try to write
comments that explain higher level mechanisms or clarify difficult
segments of your code. *Don't use comments to restate trivial things*.
segments of your code. _Don't use comments to restate trivial things_.
*Exception:* Comment blocks describing a function and its arguments
_Exception:_ Comment blocks describing a function and its arguments
(docblock) should start with `/**`, contain a single `*` at the beginning of
each line, and end with `*/`.
```js
// good
@ -546,11 +595,17 @@ You can read more about these two ngReact methods [here](https://github.com/ngRe
Using `react-component` means adding a bunch of components into angular, while `reactDirective` keeps them isolated, and is also a more succinct syntax.
**Good:**
```html
<hello-component fname="person.fname" lname="person.lname" watch-depth="reference"></hello-component>
<hello-component
fname="person.fname"
lname="person.lname"
watch-depth="reference"
></hello-component>
```
**Bad:**
```html
<react-component name="HelloComponent" props="person" watch-depth="reference" />
```
@ -564,9 +619,9 @@ Name action functions in the form of a strong verb and passed properties in the
<pagerButton onPageNext={action.turnToNextPage} />
```
## Attribution
## Attribution
Parts of the JavaScript style guide were initially forked from the
[node style guide](https://github.com/felixge/node-style-guide) created by [Felix Geisendörfer](http://felixge.de/) which is
licensed under the [CC BY-SA 3.0](http://creativecommons.org/licenses/by-sa/3.0/)
Parts of the JavaScript style guide were initially forked from the
[node style guide](https://github.com/felixge/node-style-guide) created by [Felix Geisendörfer](http://felixge.de/) which is
licensed under the [CC BY-SA 3.0](http://creativecommons.org/licenses/by-sa/3.0/)
license.

View file

@ -1,286 +0,0 @@
# 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):
```html
<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:
```html
<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:
```html
<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:
```html
<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 `id`s easier, you can use the `htmlIdGenerator` service in the `@kbn/ui-framework/services`.
A react component could use it as follows:
```jsx
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:
```js
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](http://w3c.github.io/html/dom.html#the-title-attribute) 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:**
* https://www.paciellogroup.com/blog/2010/11/using-the-html-title-attribute/
* https://www.paciellogroup.com/blog/2012/01/html5-accessibility-chops-title-attribute-use-and-abuse/
* https://www.deque.com/blog/text-links-practices-screen-readers/
## Interactive elements
### Use `<button>` and `<a href>`
**TL;DR** *Use `<button>` or `<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 <kbd>Enter</kbd>. 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 <kbd>tab</kbd>. You can add this behavior by
adding `tabindex="0"` to the element.
* You cannot trigger the click by pressing <kbd>Enter</kbd> or <kbd>Space</kbd>.
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](https://dequeuniversity.com/rules/axe/1.1/tabindex)
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:
```html
<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 <kbd>Tab</kbd> again, it needs a special interaction model.*
If an interactive element consumes the <kbd>Tab</kbd> 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](https://www.w3.org/TR/WCAG20/#keyboard-operation-trapping).
Those kind of elements, require a special interaction model. A [code editor](https://github.com/elastic/kibana/pull/13339)
could require an <kbd>Enter</kbd> keypress before starting editing mode, and
could leave that mode on <kbd>Escape</kbd> 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](https://www.w3.org/TR/html-aam-1.0/#html-element-role-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.
### role=search
**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:**
* [Where to put your search role?](http://adrianroselli.com/2015/08/where-to-put-your-search-role.html)
* Discussions about making `search` role inherit the `form` role:
[wcag/113](https://github.com/w3c/wcag/issues/113),
[html-aria/118](https://github.com/w3c/html-aria/issues/18),
[aria/85](https://github.com/w3c/aria/issues/85)