diff --git a/doc/development/contributing/design.md b/doc/development/contributing/design.md index 59e35e658e70828c4812f5c1ec9854651e3bd37d..bc6ec96cf4996a5008c2f3721b3ce61bc65245a5 100644 --- a/doc/development/contributing/design.md +++ b/doc/development/contributing/design.md @@ -105,8 +105,8 @@ Check accessibility using your browser's _accessibility inspector_ ([Chrome](htt - Conform to level AA of the World Wide Web Consortium (W3C) [Web Content Accessibility Guidelines 2.1](https://www.w3.org/TR/WCAG21/), according to our [statement of compliance](https://design.gitlab.com/accessibility/a11y/). -- Follow accessibility [best practices](https://design.gitlab.com/accessibility/best-practices/) - and [checklist](../fe_guide/accessibility.md#quick-checklist). +- Follow accessibility [Pajamas' best practices](https://design.gitlab.com/accessibility/best-practices/) + and read the accessibility developer documentation's [checklist](../fe_guide/accessibility/best_practices.md#quick-checklist). ### Handoff diff --git a/doc/development/fe_guide/accessibility.md b/doc/development/fe_guide/accessibility.md index 7ad0239d7266e7ee45e676128a35e6776a38f578..3915f63f701b16f731f4fb27dce0f7930a1ee9a2 100644 --- a/doc/development/fe_guide/accessibility.md +++ b/doc/development/fe_guide/accessibility.md @@ -1,657 +1,11 @@ --- -stage: none -group: unassigned -info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments +redirect_to: 'accessibility/index.md' +remove_date: '2024-01-12' --- -# Accessibility +This document was moved to [another location](accessibility/index.md). -Accessibility is important for users who use screen readers or rely on keyboard-only functionality -to ensure they have an equivalent experience to sighted mouse users. - -This page contains guidelines we should follow. - -## Quick summary - -Since [no ARIA is better than bad ARIA](https://w3c.github.io/aria-practices/#no_aria_better_bad_aria), -review the following recommendations before using `aria-*`, `role`, and `tabindex`. -Use semantic HTML, which has accessibility semantics baked in, and ideally test with -[relevant combinations of screen readers and browsers](https://www.accessibility-developer-guide.com/knowledge/screen-readers/relevant-combinations/). - -In [WebAIM's accessibility analysis of the top million home pages](https://webaim.org/projects/million/#aria), -they found that "ARIA correlated to higher detectable errors". -It is likely that *misuse* of ARIA is a big cause of increased errors, -so when in doubt don't use `aria-*`, `role`, and `tabindex` and stick with semantic HTML. - -## Enable keyboard navigation on macOS - -By default, macOS limits the <kbd>tab</kbd> key to **Text boxes and lists only**. To enable full keyboard navigation: - -1. Open **System Preferences**. -1. Select **Keyboard**. -1. Open the **Shortcuts** tab. -1. Enable the setting **Use keyboard navigation to move focus between controls**. - -You can read more about enabling browser-specific keyboard navigation on [a11yproject](https://www.a11yproject.com/posts/macos-browser-keyboard-navigation/). - -## Quick checklist - -- [Text](#text-inputs-with-accessible-names), - [select](#select-inputs-with-accessible-names), - [checkbox](#checkbox-inputs-with-accessible-names), - [radio](#radio-inputs-with-accessible-names), - [file](#file-inputs-with-accessible-names), - and [toggle](#gltoggle-components-with-an-accessible-names) inputs have accessible names. -- [Buttons](#buttons-and-links-with-descriptive-accessible-names), - [links](#buttons-and-links-with-descriptive-accessible-names), - and [images](#images-with-accessible-names) have descriptive accessible names. -- Icons - - [Non-decorative icons](#icons-that-convey-information) have an `aria-label`. - - [Clickable icons](#icons-that-are-clickable) are buttons, that is, `<gl-button icon="close" />` is used and not `<gl-icon />`. - - Icon-only buttons have an `aria-label`. -- Interactive elements can be [accessed with the Tab key](#support-keyboard-only-use) and have a visible focus state. -- Elements with [tooltips](#tooltips) are focusable using the Tab key. -- Are any `role`, `tabindex` or `aria-*` attributes unnecessary? -- Can any `div` or `span` elements be replaced with a more semantic [HTML element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element) like `p`, `button`, or `time`? - -## Provide a good document outline - -[Headings are the primary mechanism used by screen reader users to navigate content](https://webaim.org/projects/screenreadersurvey8/#finding). -Therefore, the structure of headings on a page should make sense, like a good table of contents. -We should ensure that: - -- There is only one `h1` element on the page. -- Heading levels are not skipped. -- Heading levels are nested correctly. - -## Provide accessible names for screen readers - -To provide markup with accessible names, ensure every: - -- input has an [associated `label`](#examples-of-providing-accessible-names). -- button and link have [visible text](#buttons-and-links-with-descriptive-accessible-names), or `aria-label` when there is no visible text, such as for an icon button with no content. -- image has an [`alt` attribute](#images-with-accessible-names). -- `fieldset` has `legend` as its first child. -- `figure` has `figcaption` as its first child. -- `table` has `caption` as its first child. - -Groups of checkboxes and radio inputs should be grouped together in a `fieldset` with a `legend`. -`legend` gives the group of checkboxes and radio inputs a label. - -If the `label`, child text, or child element is not visually desired, -use `.gl-sr-only` to hide the element from everything but screen readers. - -### Examples of providing accessible names - -The following subsections contain examples of markup that render HTML elements with accessible names. - -Note that [when using `GlFormGroup`](https://bootstrap-vue.org/docs/components/form-group#accessibility): - -- Passing only a `label` prop renders a `fieldset` with a `legend` containing the `label` value. -- Passing both a `label` and a `label-for` prop renders a `label` that points to the form input with the same `label-for` ID. - -#### Text inputs with accessible names - -When using `GlFormGroup`, the `label` prop alone does not give the input an accessible name. -The `label-for` prop must also be provided to give the input an accessible name. - -Text input examples: - -```html -<!-- Input with label --> -<gl-form-group :label="__('Issue title')" label-for="issue-title"> - <gl-form-input id="issue-title" v-model="title" /> -</gl-form-group> - -<!-- Input with hidden label --> -<gl-form-group :label="__('Issue title')" label-for="issue-title" label-sr-only> - <gl-form-input id="issue-title" v-model="title" /> -</gl-form-group> -``` - -`textarea` examples: - -```html -<!-- textarea with label --> -<gl-form-group :label="__('Issue description')" label-for="issue-description"> - <gl-form-textarea id="issue-description" v-model="description" /> -</gl-form-group> - -<!-- textarea with hidden label --> -<gl-form-group :label="__('Issue description')" label-for="issue-description" label-sr-only> - <gl-form-textarea id="issue-description" v-model="description" /> -</gl-form-group> -``` - -Alternatively, you can use a plain `label` element: - -```html -<!-- Input with label using `label` --> -<label for="issue-title">{{ __('Issue title') }}</label> -<gl-form-input id="issue-title" v-model="title" /> - -<!-- Input with hidden label using `label` --> -<label for="issue-title" class="gl-sr-only">{{ __('Issue title') }}</label> -<gl-form-input id="issue-title" v-model="title" /> -``` - -#### Select inputs with accessible names - -Select input examples: - -```html -<!-- Select input with label --> -<gl-form-group :label="__('Issue status')" label-for="issue-status"> - <gl-form-select id="issue-status" v-model="status" :options="options" /> -</gl-form-group> - -<!-- Select input with hidden label --> -<gl-form-group :label="__('Issue status')" label-for="issue-status" label-sr-only> - <gl-form-select id="issue-status" v-model="status" :options="options" /> -</gl-form-group> -``` - -#### Checkbox inputs with accessible names - -Single checkbox: - -```html -<!-- Single checkbox with label --> -<gl-form-checkbox v-model="status" value="task-complete"> - {{ __('Task complete') }} -</gl-form-checkbox> - -<!-- Single checkbox with hidden label --> -<gl-form-checkbox v-model="status" value="task-complete"> - <span class="gl-sr-only">{{ __('Task complete') }}</span> -</gl-form-checkbox> -``` - -Multiple checkboxes: - -```html -<!-- Multiple labeled checkboxes grouped within a fieldset --> -<gl-form-group :label="__('Task list')"> - <gl-form-checkbox value="task-1">{{ __('Task 1') }}</gl-form-checkbox> - <gl-form-checkbox value="task-2">{{ __('Task 2') }}</gl-form-checkbox> -</gl-form-group> - -<!-- Or --> -<gl-form-group :label="__('Task list')"> - <gl-form-checkbox-group v-model="selected" :options="options" /> -</gl-form-group> - -<!-- Multiple labeled checkboxes grouped within a fieldset with hidden legend --> -<gl-form-group :label="__('Task list')" label-sr-only> - <gl-form-checkbox value="task-1">{{ __('Task 1') }}</gl-form-checkbox> - <gl-form-checkbox value="task-2">{{ __('Task 2') }}</gl-form-checkbox> -</gl-form-group> - -<!-- Or --> -<gl-form-group :label="__('Task list')" label-sr-only> - <gl-form-checkbox-group v-model="selected" :options="options" /> -</gl-form-group> -``` - -#### Radio inputs with accessible names - -Single radio input: - -```html -<!-- Single radio with a label --> -<gl-form-radio v-model="status" value="opened"> - {{ __('Opened') }} -</gl-form-radio> - -<!-- Single radio with a hidden label --> -<gl-form-radio v-model="status" value="opened"> - <span class="gl-sr-only">{{ __('Opened') }}</span> -</gl-form-radio> -``` - -Multiple radio inputs: - -```html -<!-- Multiple labeled radio inputs grouped within a fieldset --> -<gl-form-group :label="__('Issue status')"> - <gl-form-radio value="opened">{{ __('Opened') }}</gl-form-radio> - <gl-form-radio value="closed">{{ __('Closed') }}</gl-form-radio> -</gl-form-group> - -<!-- Or --> -<gl-form-group :label="__('Issue status')"> - <gl-form-radio-group v-model="selected" :options="options" /> -</gl-form-group> - -<!-- Multiple labeled radio inputs grouped within a fieldset with hidden legend --> -<gl-form-group :label="__('Issue status')" label-sr-only> - <gl-form-radio value="opened">{{ __('Opened') }}</gl-form-radio> - <gl-form-radio value="closed">{{ __('Closed') }}</gl-form-radio> -</gl-form-group> - -<!-- Or --> -<gl-form-group :label="__('Issue status')" label-sr-only> - <gl-form-radio-group v-model="selected" :options="options" /> -</gl-form-group> -``` - -#### File inputs with accessible names - -File input examples: - -```html -<!-- File input with a label --> -<label for="attach-file">{{ __('Attach a file') }}</label> -<input id="attach-file" type="file" /> - -<!-- File input with a hidden label --> -<label for="attach-file" class="gl-sr-only">{{ __('Attach a file') }}</label> -<input id="attach-file" type="file" /> -``` - -#### GlToggle components with an accessible names - -`GlToggle` examples: - -```html -<!-- GlToggle with label --> -<gl-toggle v-model="notifications" :label="__('Notifications')" /> - -<!-- GlToggle with hidden label --> -<gl-toggle v-model="notifications" :label="__('Notifications')" label-position="hidden" /> -``` - -#### GlFormCombobox components with an accessible names - -`GlFormCombobox` examples: - -```html -<!-- GlFormCombobox with label --> -<gl-form-combobox :label-text="__('Key')" :token-list="$options.tokenList" /> -``` - -#### Images with accessible names - -Image examples: - -```html -<img :src="imagePath" :alt="__('A description of the image')" /> - -<!-- SVGs implicitly have a graphics role so if it is semantically an image we should apply `role="img"` --> -<svg role="img" :alt="__('A description of the image')" /> - -<!-- A decorative image, hidden from screen readers --> -<img :src="imagePath" :alt="" /> -``` - -#### Buttons and links with descriptive accessible names - -Buttons and links should have accessible names that are descriptive enough to be understood in isolation. - -```html -<!-- bad --> -<gl-button @click="handleClick">{{ __('Submit') }}</gl-button> - -<gl-link :href="url">{{ __('page') }}</gl-link> - -<!-- good --> -<gl-button @click="handleClick">{{ __('Submit review') }}</gl-button> - -<gl-link :href="url">{{ __("GitLab's accessibility page") }}</gl-link> -``` - -#### Links styled like buttons - -Links can be styled like buttons using `GlButton`. - -```html - <gl-button :href="url">{{ __('Link styled as a button') }}</gl-button> -``` - -## Role - -In general, avoid using `role`. -Use semantic HTML elements that implicitly have a `role` instead. - -| Bad | Good | -| --- | --- | -| `<div role="button">` | `<button>` | -| `<div role="img">` | `<img>` | -| `<div role="link">` | `<a>` | -| `<div role="header">` | `<h1>` to `<h6>` | -| `<div role="textbox">` | `<input>` or `<textarea>` | -| `<div role="article">` | `<article>` | -| `<div role="list">` | `<ol>` or `<ul>` | -| `<div role="listitem">` | `<li>` | -| `<div role="table">` | `<table>` | -| `<div role="rowgroup">` | `<thead>`, `<tbody>`, or `<tfoot>` | -| `<div role="row">` | `<tr>` | -| `<div role="columnheader">` | `<th>` | -| `<div role="cell">` | `<td>` | - -## Support keyboard-only use - -Keyboard users rely on focus outlines to understand where they are on the page. Therefore, if an -element is interactive you must ensure: - -- It can receive keyboard focus. -- It has a visible focus state. - -Use semantic HTML, such as `a` (`GlLink`) and `button` (`GlButton`), which provides these behaviours by default. - -Keep in mind that: - -- <kbd>Tab</kbd> and <kbd>Shift-Tab</kbd> should only move between interactive elements, not static content. -- When you add `:hover` styles, in most cases you should add `:focus` styles too so that the styling is applied for both mouse **and** keyboard users. -- If you remove an interactive element's `outline`, make sure you maintain visual focus state in another way such as with `box-shadow`. - -See the [Pajamas Keyboard-only page](https://design.gitlab.com/accessibility/keyboard-only) for more detail. - -## `tabindex` - -Prefer **no** `tabindex` to using `tabindex`, since: - -- Using semantic HTML such as `button` (`GlButton`) implicitly provides `tabindex="0"`. -- Tabbing order should match the visual reading order and positive `tabindex`s interfere with this. - -### Avoid using `tabindex="0"` to make an element interactive - -Use interactive elements instead of `div` and `span` tags. -For example: - -- If the element should be clickable, use a `button` (`GlButton`). -- If the element should be text editable, use an [`input` or `textarea`](#text-inputs-with-accessible-names). - -Once the markup is semantically complete, use CSS to update it to its desired visual state. - -```html -<!-- bad --> -<div role="button" tabindex="0" @click="expand">Expand</div> - -<!-- good --> -<gl-button class="gl-p-0!" category="tertiary" @click="expand">Expand</gl-button> -``` - -### Do not use `tabindex="0"` on interactive elements - -Interactive elements are already tab accessible so adding `tabindex` is redundant. - -```html -<!-- bad --> -<gl-link href="help" tabindex="0">Help</gl-link> -<gl-button tabindex="0">Submit</gl-button> - -<!-- good --> -<gl-link href="help">Help</gl-link> -<gl-button>Submit</gl-button> -``` - -### Do not use `tabindex="0"` on elements for screen readers to read - -Screen readers can read text that is not tab accessible. -The use of `tabindex="0"` is unnecessary and can cause problems, -as screen reader users then expect to be able to interact with it. - -```html -<!-- bad --> -<p tabindex="0" :aria-label="message">{{ message }}</p> - -<!-- good --> -<p>{{ message }}</p> -``` - -### Do not use a positive `tabindex` - -[Always avoid using `tabindex="1"`](https://webaim.org/techniques/keyboard/tabindex#overview) -or greater. - -## Icons - -Icons can be split into three different types: - -- Icons that are decorative -- Icons that convey meaning -- Icons that are clickable - -### Icons that are decorative - -Icons are decorative when there's no loss of information to the user when they are removed from the UI. - -As the majority of icons within GitLab are decorative, `GlIcon` automatically hides its rendered icons from screen readers. -Therefore, you do not need to add `aria-hidden="true"` to `GlIcon`, as this is redundant. - -```html -<!-- unnecessary — gl-icon hides icons from screen readers by default --> -<gl-icon name="rocket" aria-hidden="true" />` - -<!-- good --> -<gl-icon name="rocket" />` -``` - -### Icons that convey information - -Icons convey information if there is loss of information to the user when they are removed from the UI. - -An example is a confidential icon that conveys the issue is confidential, and does not have the text "Confidential" next to it. - -Icons that convey information must have an accessible name so that the information is conveyed to screen reader users too. - -```html -<!-- bad --> -<gl-icon name="eye-slash" />` - -<!-- good --> -<gl-icon name="eye-slash" :aria-label="__('Confidential issue')" />` -``` - -### Icons that are clickable - -Icons that are clickable are semantically buttons, so they should be rendered as buttons, with an accessible name. - -```html -<!-- bad --> -<gl-icon name="close" :aria-label="__('Close')" @click="handleClick" /> - -<!-- good --> -<gl-button icon="close" category="tertiary" :aria-label="__('Close')" @click="handleClick" /> -``` - -## Tooltips - -When adding tooltips, we must ensure that the element with the tooltip can receive focus so keyboard users can see the tooltip. -If the element is a static one, such as an icon, we can enclose it in a button, which already is -focusable, so we don't have to add `tabindex=0` to the icon. - -The following code snippet is a good example of an icon with a tooltip. - -- It is automatically focusable, as it is a button. -- It is given an accessible name with `aria-label`, as it is a button with no text. -- We can use the `gl-hover-bg-transparent!` class if we don't want the button's background to become gray on hover. -- We can use the `gl-p-0!` class to remove the button padding, if needed. - -```html -<gl-button - v-gl-tooltip - class="gl-hover-bg-transparent! gl-p-0!" - icon="warning" - category="tertiary" - :title="tooltipText" - :aria-label="__('Warning')" -/> -``` - -## Hiding elements - -Use the following table to hide elements from users, when appropriate. - -| Hide from sighted users | Hide from screen readers | Hide from both sighted and screen reader users | -| --- | --- | --- | -| `.gl-sr-only` | `aria-hidden="true"` | `display: none`, `visibility: hidden`, or `hidden` attribute | - -### Hide decorative images from screen readers - -To reduce noise for screen reader users, hide decorative images using `alt=""`. -If the image is not an `img` element, such as an inline SVG, you can hide it by adding both `role="img"` and `alt=""`. - -`gl-icon` components automatically hide their icons from screen readers so `aria-hidden="true"` is -unnecessary when using `gl-icon`. - -```html -<!-- good - decorative images hidden from screen readers --> - -<img src="decorative.jpg" alt=""> - -<svg role="img" alt="" /> - -<gl-icon name="epic" /> -``` - -## When to use ARIA - -No ARIA is required when using semantic HTML, because it already incorporates accessibility. - -However, there are some UI patterns that do not have semantic HTML equivalents. -General examples of these are dialogs (modals) and tabs. -GitLab-specific examples are assignee and label dropdowns. -Building such widgets require ARIA to make them understandable to screen readers. -Proper research and testing should be done to ensure compliance with [WCAG](https://www.w3.org/WAI/standards-guidelines/wcag/). - -## Automated accessibility testing with axe - -We use [axe-core](https://github.com/dequelabs/axe-core) [gems](https://github.com/dequelabs/axe-core-gems) -to run automated accessibility tests in feature tests. - -[We aim to conform to level AA of the World Wide Web Consortium (W3C) Web Content Accessibility Guidelines 2.1](https://design.gitlab.com/accessibility/a11y). - -### When to add accessibility tests - -When adding a new view to the application, make sure to include the accessibility check in your feature test. -We aim to have full coverage for all the views. - -One of the advantages of testing in feature tests is that we can check different states, not only -single components in isolation. - -You can find some examples on how to approach accessibility checks below. - -#### Empty state - -Some views have an empty state that result in a page structure that's different from the default view. -They may also offer some actions, for example to create a first issue or to enable a feature. -In this case, add assertions for both an empty state and a default view. - -#### Ensure compliance before user interactions - -Often we test against a number of steps we expect our users to perform. -In this case, make sure to include the check early on, before any of them has been simulated. -This way we ensure there are no barriers to what we expect of users. - -#### Ensure compliance after changed page structure - -User interactions may result in significant changes in page structure. For example, a modal is shown, or a new section is rendered. -In that case, add an assertion after any such change. -We want to make sure that users are able to interact with all available components. - -#### Separate file for extensive test suites - -For some views, feature tests span multiple files. -Take a look at our [feature tests for a merge request](https://gitlab.com/gitlab-org/gitlab/-/tree/master/spec/features/merge_request). -The number of user interactions that needs to be covered is too big to fit into one test file. -As a result, multiple feature tests cover one view, with different user privileges, or data sets. -If we were to include accessibility checks in all of them, there is a chance we would cover the same states of a view multiple times and significantly increase the run time. -It would also make it harder to determine the coverage for accessibility, if assertions would be scattered across many files. - -In that case, consider creating one test file dedicated to accessibility. -Place it in the same directory and name it `accessibility_spec.rb`, for example `spec/features/merge_request/accessibility_spec.rb`. -Make it explicit that a feature test has accessibility coverage in a separate file, and -doesn't need additional assertions. Include this comment below the opening of the -top-level block: - -```ruby -# spec/features/merge_request/user_approves_spec.rb - -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'Merge request > User approves', :js, feature_category: :code_review_workflow do -# covered by ./accessibility_spec.rb -``` - -#### Shared examples - -Often feature tests include shared examples for a number of scenarios. -If they differ only by provided data, but are based on the same user interaction, you can check for accessibility compliance outside the shared examples. -This way we only run the check once and save resources. - -### How to add accessibility tests - -Axe provides the custom matcher `be_axe_clean`, which can be used like the following: - -```ruby -# spec/features/settings_spec.rb -it 'passes axe automated accessibility testing', :js do - visit_settings_page - - wait_for_requests # ensures page is fully loaded - - expect(page).to be_axe_clean -end -``` - -If needed, you can scope testing to a specific area of the page by using `within`. - -Axe also provides specific [clauses](https://github.com/dequelabs/axe-core-gems/blob/develop/packages/axe-core-rspec/README.md#clauses), -for example: - -```ruby -expect(page).to be_axe_clean.within '[data-testid="element"]' - -# run only WCAG 2.1 Level AA rules -expect(page).to be_axe_clean.according_to :wcag21aa - -# specifies which rule to skip -expect(page).to be_axe_clean.skipping :'link-in-text-block' - -# clauses can be chained -expect(page).to be_axe_clean.within('[data-testid="element"]') - .according_to(:wcag21aa) -``` - -Axe does not test hidden regions, such as inactive menus or modal windows. To test -hidden regions for accessibility, write tests that activate or render the regions visible -and run the matcher again. - -You can run accessibility tests locally in the same way as you [run any feature tests](../testing_guide/frontend_testing.md#how-to-run-a-feature-test). - -After adding accessibility tests, make sure to fix all possible errors. -For help on how to do it, refer to [this guide](#quick-checklist). -You can also check accessibility sections in [Pajamas components' documentation](https://design.gitlab.com/components/overview). -If any of the errors require global changes, create a follow-up issue and assign these labels: `accessability`, `WG::product accessibility`. - -### Known accessibility violations - -This section documents violations where a recommendation differs with the [design system](https://design.gitlab.com/): - -- `link-in-text-block`: For now, use the `skipping` clause to skip `:'link-in-text-block'` - rule to fix the violation. After this is fixed as part of [issue 1444](https://gitlab.com/gitlab-org/gitlab-services/design.gitlab.com/-/issues/1444) - and underline is added to the `GlLink` component, this clause can be removed. - -## Resources - -### Viewing the browser accessibility tree - -- [Firefox DevTools guide](https://developer.mozilla.org/en-US/docs/Tools/Accessibility_inspector#accessing_the_accessibility_inspector) -- [Chrome DevTools guide](https://developer.chrome.com/docs/devtools/accessibility/reference/#pane) - -### Browser extensions - -We have two options for Web accessibility testing: - -- [axe](https://www.deque.com/axe/) for [Firefox](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/) -- [axe](https://www.deque.com/axe/) for [Chrome](https://chrome.google.com/webstore/detail/axe-devtools-web-accessib/lhdoppojpmngadmnindnejefpokejbdd) - -### Other links - -- [The A11Y Project](https://www.a11yproject.com/) is a good resource for accessibility -- [Awesome Accessibility](https://github.com/brunopulis/awesome-a11y) - is a compilation of accessibility-related material +<!-- This redirect file can be deleted after <2024-01-12>. --> +<!-- Redirects that point to other docs in the same project expire in three months. --> +<!-- Redirects that point to docs in a different project or site (link is not relative and starts with `https:`) expire in one year. --> +<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html --> diff --git a/doc/development/fe_guide/accessibility/automated_testing.md b/doc/development/fe_guide/accessibility/automated_testing.md new file mode 100644 index 0000000000000000000000000000000000000000..2c0d598dc580e50122a347394809d279053fd72e --- /dev/null +++ b/doc/development/fe_guide/accessibility/automated_testing.md @@ -0,0 +1,125 @@ +--- +stage: none +group: unassigned +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments +--- + +# Automated accessibility testing + +We use [axe-core](https://github.com/dequelabs/axe-core) [gems](https://github.com/dequelabs/axe-core-gems) +to run automated accessibility tests in feature tests. + +[We aim to conform to level AA of the World Wide Web Consortium (W3C) Web Content Accessibility Guidelines 2.1](https://design.gitlab.com/accessibility/a11y). + +## When to add accessibility tests + +When adding a new view to the application, make sure to include the accessibility check in your feature test. +We aim to have full coverage for all the views. + +One of the advantages of testing in feature tests is that we can check different states, not only +single components in isolation. + +You can find some examples on how to approach accessibility checks below. + +### Empty state + +Some views have an empty state that result in a page structure that's different from the default view. +They may also offer some actions, for example to create a first issue or to enable a feature. +In this case, add assertions for both an empty state and a default view. + +### Ensure compliance before user interactions + +Often we test against a number of steps we expect our users to perform. +In this case, make sure to include the check early on, before any of them has been simulated. +This way we ensure there are no barriers to what we expect of users. + +### Ensure compliance after changed page structure + +User interactions may result in significant changes in page structure. For example, a modal is shown, or a new section is rendered. +In that case, add an assertion after any such change. +We want to make sure that users are able to interact with all available components. + +### Separate file for extensive test suites + +For some views, feature tests span multiple files. +Take a look at our [feature tests for a merge request](https://gitlab.com/gitlab-org/gitlab/-/tree/master/spec/features/merge_request). +The number of user interactions that needs to be covered is too big to fit into one test file. +As a result, multiple feature tests cover one view, with different user privileges, or data sets. +If we were to include accessibility checks in all of them, there is a chance we would cover the same states of a view multiple times and significantly increase the run time. +It would also make it harder to determine the coverage for accessibility, if assertions would be scattered across many files. + +In that case, consider creating one test file dedicated to accessibility. +Place it in the same directory and name it `accessibility_spec.rb`, for example `spec/features/merge_request/accessibility_spec.rb`. +Make it explicit that a feature test has accessibility coverage in a separate file, and +doesn't need additional assertions. Include this comment below the opening of the +top-level block: + +```ruby +# spec/features/merge_request/user_approves_spec.rb + +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Merge request > User approves', :js, feature_category: :code_review_workflow do +# covered by ./accessibility_spec.rb +``` + +### Shared examples + +Often feature tests include shared examples for a number of scenarios. +If they differ only by provided data, but are based on the same user interaction, you can check for accessibility compliance outside the shared examples. +This way we only run the check once and save resources. + +## How to add accessibility tests + +Axe provides the custom matcher `be_axe_clean`, which can be used like the following: + +```ruby +# spec/features/settings_spec.rb +it 'passes axe automated accessibility testing', :js do + visit_settings_page + + wait_for_requests # ensures page is fully loaded + + expect(page).to be_axe_clean +end +``` + +If needed, you can scope testing to a specific area of the page by using `within`. + +Axe also provides specific [clauses](https://github.com/dequelabs/axe-core-gems/blob/develop/packages/axe-core-rspec/README.md#clauses), +for example: + +```ruby +expect(page).to be_axe_clean.within '[data-testid="element"]' + +# run only WCAG 2.1 Level AA rules +expect(page).to be_axe_clean.according_to :wcag21aa + +# specifies which rule to skip +expect(page).to be_axe_clean.skipping :'link-in-text-block' + +# clauses can be chained +expect(page).to be_axe_clean.within('[data-testid="element"]') + .according_to(:wcag21aa) +``` + +Axe does not test hidden regions, such as inactive menus or modal windows. To test +hidden regions for accessibility, write tests that activate or render the regions visible +and run the matcher again. + +You can run accessibility tests locally in the same way as you [run any feature tests](../../testing_guide/frontend_testing.md#how-to-run-a-feature-test). + +After adding accessibility tests, make sure to fix all possible errors. +For help on how to do it, refer to [this guide](best_practices.md#quick-checklist). +You can also check accessibility sections in [Pajamas components' documentation](https://design.gitlab.com/components/overview). +If any of the errors require global changes, create a follow-up issue and assign these labels: `accessability`, `WG::product accessibility`. + +### Known accessibility violations + +This section documents violations where a recommendation differs with the [design system](https://design.gitlab.com/): + +- `link-in-text-block`: For now, use the `skipping` clause to skip `:'link-in-text-block'` + rule to fix the violation. After this is fixed as part of [issue 1444](https://gitlab.com/gitlab-org/gitlab-services/design.gitlab.com/-/issues/1444) + and underline is added to the `GlLink` component, this clause can be removed. diff --git a/doc/development/fe_guide/accessibility/best_practices.md b/doc/development/fe_guide/accessibility/best_practices.md new file mode 100644 index 0000000000000000000000000000000000000000..37c28f991160e628b4a83d1804c4881ec475f551 --- /dev/null +++ b/doc/development/fe_guide/accessibility/best_practices.md @@ -0,0 +1,512 @@ +--- +stage: none +group: unassigned +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments +--- + +# Accessibility best practices + +## Quick summary + +Since [no ARIA is better than bad ARIA](https://w3c.github.io/aria-practices/#no_aria_better_bad_aria), +review the following recommendations before using `aria-*`, `role`, and `tabindex`. +Use semantic HTML, which has accessibility semantics baked in, and ideally test with +[relevant combinations of screen readers and browsers](https://www.accessibility-developer-guide.com/knowledge/screen-readers/relevant-combinations/). + +In [WebAIM's accessibility analysis of the top million home pages](https://webaim.org/projects/million/#aria), +they found that "ARIA correlated to higher detectable errors". +It is likely that *misuse* of ARIA is a big cause of increased errors, +so when in doubt don't use `aria-*`, `role`, and `tabindex` and stick with semantic HTML. + +## Enable keyboard navigation on macOS + +By default, macOS limits the <kbd>tab</kbd> key to **Text boxes and lists only**. To enable full keyboard navigation: + +1. Open **System Preferences**. +1. Select **Keyboard**. +1. Open the **Shortcuts** tab. +1. Enable the setting **Use keyboard navigation to move focus between controls**. + +You can read more about enabling browser-specific keyboard navigation on [a11yproject](https://www.a11yproject.com/posts/macos-browser-keyboard-navigation/). + +## Quick checklist + +- [Text](#text-inputs-with-accessible-names), + [select](#select-inputs-with-accessible-names), + [checkbox](#checkbox-inputs-with-accessible-names), + [radio](#radio-inputs-with-accessible-names), + [file](#file-inputs-with-accessible-names), + and [toggle](#gltoggle-components-with-an-accessible-names) inputs have accessible names. +- [Buttons](#buttons-and-links-with-descriptive-accessible-names), + [links](#buttons-and-links-with-descriptive-accessible-names), + and [images](#images-with-accessible-names) have descriptive accessible names. +- Icons + - [Non-decorative icons](#icons-that-convey-information) have an `aria-label`. + - [Clickable icons](#icons-that-are-clickable) are buttons, that is, `<gl-button icon="close" />` is used and not `<gl-icon />`. + - Icon-only buttons have an `aria-label`. +- Interactive elements can be [accessed with the Tab key](#support-keyboard-only-use) and have a visible focus state. +- Elements with [tooltips](#tooltips) are focusable using the Tab key. +- Are any `role`, `tabindex` or `aria-*` attributes unnecessary? +- Can any `div` or `span` elements be replaced with a more semantic [HTML element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element) like `p`, `button`, or `time`? + +## Provide a good document outline + +[Headings are the primary mechanism used by screen reader users to navigate content](https://webaim.org/projects/screenreadersurvey8/#finding). +Therefore, the structure of headings on a page should make sense, like a good table of contents. +We should ensure that: + +- There is only one `h1` element on the page. +- Heading levels are not skipped. +- Heading levels are nested correctly. + +## Provide accessible names for screen readers + +To provide markup with accessible names, ensure every: + +- input has an [associated `label`](#examples-of-providing-accessible-names). +- button and link have [visible text](#buttons-and-links-with-descriptive-accessible-names), or `aria-label` when there is no visible text, such as for an icon button with no content. +- image has an [`alt` attribute](#images-with-accessible-names). +- `fieldset` has `legend` as its first child. +- `figure` has `figcaption` as its first child. +- `table` has `caption` as its first child. + +Groups of checkboxes and radio inputs should be grouped together in a `fieldset` with a `legend`. +`legend` gives the group of checkboxes and radio inputs a label. + +If the `label`, child text, or child element is not visually desired, +use `.gl-sr-only` to hide the element from everything but screen readers. + +### Examples of providing accessible names + +The following subsections contain examples of markup that render HTML elements with accessible names. + +Note that [when using `GlFormGroup`](https://bootstrap-vue.org/docs/components/form-group#accessibility): + +- Passing only a `label` prop renders a `fieldset` with a `legend` containing the `label` value. +- Passing both a `label` and a `label-for` prop renders a `label` that points to the form input with the same `label-for` ID. + +#### Text inputs with accessible names + +When using `GlFormGroup`, the `label` prop alone does not give the input an accessible name. +The `label-for` prop must also be provided to give the input an accessible name. + +Text input examples: + +```html +<!-- Input with label --> +<gl-form-group :label="__('Issue title')" label-for="issue-title"> + <gl-form-input id="issue-title" v-model="title" /> +</gl-form-group> + +<!-- Input with hidden label --> +<gl-form-group :label="__('Issue title')" label-for="issue-title" label-sr-only> + <gl-form-input id="issue-title" v-model="title" /> +</gl-form-group> +``` + +`textarea` examples: + +```html +<!-- textarea with label --> +<gl-form-group :label="__('Issue description')" label-for="issue-description"> + <gl-form-textarea id="issue-description" v-model="description" /> +</gl-form-group> + +<!-- textarea with hidden label --> +<gl-form-group :label="__('Issue description')" label-for="issue-description" label-sr-only> + <gl-form-textarea id="issue-description" v-model="description" /> +</gl-form-group> +``` + +Alternatively, you can use a plain `label` element: + +```html +<!-- Input with label using `label` --> +<label for="issue-title">{{ __('Issue title') }}</label> +<gl-form-input id="issue-title" v-model="title" /> + +<!-- Input with hidden label using `label` --> +<label for="issue-title" class="gl-sr-only">{{ __('Issue title') }}</label> +<gl-form-input id="issue-title" v-model="title" /> +``` + +#### Select inputs with accessible names + +Select input examples: + +```html +<!-- Select input with label --> +<gl-form-group :label="__('Issue status')" label-for="issue-status"> + <gl-form-select id="issue-status" v-model="status" :options="options" /> +</gl-form-group> + +<!-- Select input with hidden label --> +<gl-form-group :label="__('Issue status')" label-for="issue-status" label-sr-only> + <gl-form-select id="issue-status" v-model="status" :options="options" /> +</gl-form-group> +``` + +#### Checkbox inputs with accessible names + +Single checkbox: + +```html +<!-- Single checkbox with label --> +<gl-form-checkbox v-model="status" value="task-complete"> + {{ __('Task complete') }} +</gl-form-checkbox> + +<!-- Single checkbox with hidden label --> +<gl-form-checkbox v-model="status" value="task-complete"> + <span class="gl-sr-only">{{ __('Task complete') }}</span> +</gl-form-checkbox> +``` + +Multiple checkboxes: + +```html +<!-- Multiple labeled checkboxes grouped within a fieldset --> +<gl-form-group :label="__('Task list')"> + <gl-form-checkbox value="task-1">{{ __('Task 1') }}</gl-form-checkbox> + <gl-form-checkbox value="task-2">{{ __('Task 2') }}</gl-form-checkbox> +</gl-form-group> + +<!-- Or --> +<gl-form-group :label="__('Task list')"> + <gl-form-checkbox-group v-model="selected" :options="options" /> +</gl-form-group> + +<!-- Multiple labeled checkboxes grouped within a fieldset with hidden legend --> +<gl-form-group :label="__('Task list')" label-sr-only> + <gl-form-checkbox value="task-1">{{ __('Task 1') }}</gl-form-checkbox> + <gl-form-checkbox value="task-2">{{ __('Task 2') }}</gl-form-checkbox> +</gl-form-group> + +<!-- Or --> +<gl-form-group :label="__('Task list')" label-sr-only> + <gl-form-checkbox-group v-model="selected" :options="options" /> +</gl-form-group> +``` + +#### Radio inputs with accessible names + +Single radio input: + +```html +<!-- Single radio with a label --> +<gl-form-radio v-model="status" value="opened"> + {{ __('Opened') }} +</gl-form-radio> + +<!-- Single radio with a hidden label --> +<gl-form-radio v-model="status" value="opened"> + <span class="gl-sr-only">{{ __('Opened') }}</span> +</gl-form-radio> +``` + +Multiple radio inputs: + +```html +<!-- Multiple labeled radio inputs grouped within a fieldset --> +<gl-form-group :label="__('Issue status')"> + <gl-form-radio value="opened">{{ __('Opened') }}</gl-form-radio> + <gl-form-radio value="closed">{{ __('Closed') }}</gl-form-radio> +</gl-form-group> + +<!-- Or --> +<gl-form-group :label="__('Issue status')"> + <gl-form-radio-group v-model="selected" :options="options" /> +</gl-form-group> + +<!-- Multiple labeled radio inputs grouped within a fieldset with hidden legend --> +<gl-form-group :label="__('Issue status')" label-sr-only> + <gl-form-radio value="opened">{{ __('Opened') }}</gl-form-radio> + <gl-form-radio value="closed">{{ __('Closed') }}</gl-form-radio> +</gl-form-group> + +<!-- Or --> +<gl-form-group :label="__('Issue status')" label-sr-only> + <gl-form-radio-group v-model="selected" :options="options" /> +</gl-form-group> +``` + +#### File inputs with accessible names + +File input examples: + +```html +<!-- File input with a label --> +<label for="attach-file">{{ __('Attach a file') }}</label> +<input id="attach-file" type="file" /> + +<!-- File input with a hidden label --> +<label for="attach-file" class="gl-sr-only">{{ __('Attach a file') }}</label> +<input id="attach-file" type="file" /> +``` + +#### GlToggle components with an accessible names + +`GlToggle` examples: + +```html +<!-- GlToggle with label --> +<gl-toggle v-model="notifications" :label="__('Notifications')" /> + +<!-- GlToggle with hidden label --> +<gl-toggle v-model="notifications" :label="__('Notifications')" label-position="hidden" /> +``` + +#### GlFormCombobox components with an accessible names + +`GlFormCombobox` examples: + +```html +<!-- GlFormCombobox with label --> +<gl-form-combobox :label-text="__('Key')" :token-list="$options.tokenList" /> +``` + +#### Images with accessible names + +Image examples: + +```html +<img :src="imagePath" :alt="__('A description of the image')" /> + +<!-- SVGs implicitly have a graphics role so if it is semantically an image we should apply `role="img"` --> +<svg role="img" :alt="__('A description of the image')" /> + +<!-- A decorative image, hidden from screen readers --> +<img :src="imagePath" :alt="" /> +``` + +#### Buttons and links with descriptive accessible names + +Buttons and links should have accessible names that are descriptive enough to be understood in isolation. + +```html +<!-- bad --> +<gl-button @click="handleClick">{{ __('Submit') }}</gl-button> + +<gl-link :href="url">{{ __('page') }}</gl-link> + +<!-- good --> +<gl-button @click="handleClick">{{ __('Submit review') }}</gl-button> + +<gl-link :href="url">{{ __("GitLab's accessibility page") }}</gl-link> +``` + +#### Links styled like buttons + +Links can be styled like buttons using `GlButton`. + +```html + <gl-button :href="url">{{ __('Link styled as a button') }}</gl-button> +``` + +## Role + +In general, avoid using `role`. +Use semantic HTML elements that implicitly have a `role` instead. + +| Bad | Good | +| --- | --- | +| `<div role="button">` | `<button>` | +| `<div role="img">` | `<img>` | +| `<div role="link">` | `<a>` | +| `<div role="header">` | `<h1>` to `<h6>` | +| `<div role="textbox">` | `<input>` or `<textarea>` | +| `<div role="article">` | `<article>` | +| `<div role="list">` | `<ol>` or `<ul>` | +| `<div role="listitem">` | `<li>` | +| `<div role="table">` | `<table>` | +| `<div role="rowgroup">` | `<thead>`, `<tbody>`, or `<tfoot>` | +| `<div role="row">` | `<tr>` | +| `<div role="columnheader">` | `<th>` | +| `<div role="cell">` | `<td>` | + +## Support keyboard-only use + +Keyboard users rely on focus outlines to understand where they are on the page. Therefore, if an +element is interactive you must ensure: + +- It can receive keyboard focus. +- It has a visible focus state. + +Use semantic HTML, such as `a` (`GlLink`) and `button` (`GlButton`), which provides these behaviours by default. + +Keep in mind that: + +- <kbd>Tab</kbd> and <kbd>Shift-Tab</kbd> should only move between interactive elements, not static content. +- When you add `:hover` styles, in most cases you should add `:focus` styles too so that the styling is applied for both mouse **and** keyboard users. +- If you remove an interactive element's `outline`, make sure you maintain visual focus state in another way such as with `box-shadow`. + +See the [Pajamas Keyboard-only page](https://design.gitlab.com/accessibility/keyboard-only) for more detail. + +## `tabindex` + +Prefer **no** `tabindex` to using `tabindex`, since: + +- Using semantic HTML such as `button` (`GlButton`) implicitly provides `tabindex="0"`. +- Tabbing order should match the visual reading order and positive `tabindex`s interfere with this. + +### Avoid using `tabindex="0"` to make an element interactive + +Use interactive elements instead of `div` and `span` tags. +For example: + +- If the element should be clickable, use a `button` (`GlButton`). +- If the element should be text editable, use an [`input` or `textarea`](#text-inputs-with-accessible-names). + +Once the markup is semantically complete, use CSS to update it to its desired visual state. + +```html +<!-- bad --> +<div role="button" tabindex="0" @click="expand">Expand</div> + +<!-- good --> +<gl-button class="gl-p-0!" category="tertiary" @click="expand">Expand</gl-button> +``` + +### Do not use `tabindex="0"` on interactive elements + +Interactive elements are already tab accessible so adding `tabindex` is redundant. + +```html +<!-- bad --> +<gl-link href="help" tabindex="0">Help</gl-link> +<gl-button tabindex="0">Submit</gl-button> + +<!-- good --> +<gl-link href="help">Help</gl-link> +<gl-button>Submit</gl-button> +``` + +### Do not use `tabindex="0"` on elements for screen readers to read + +Screen readers can read text that is not tab accessible. +The use of `tabindex="0"` is unnecessary and can cause problems, +as screen reader users then expect to be able to interact with it. + +```html +<!-- bad --> +<p tabindex="0" :aria-label="message">{{ message }}</p> + +<!-- good --> +<p>{{ message }}</p> +``` + +### Do not use a positive `tabindex` + +[Always avoid using `tabindex="1"`](https://webaim.org/techniques/keyboard/tabindex#overview) +or greater. + +## Icons + +Icons can be split into three different types: + +- Icons that are decorative +- Icons that convey meaning +- Icons that are clickable + +### Icons that are decorative + +Icons are decorative when there's no loss of information to the user when they are removed from the UI. + +As the majority of icons within GitLab are decorative, `GlIcon` automatically hides its rendered icons from screen readers. +Therefore, you do not need to add `aria-hidden="true"` to `GlIcon`, as this is redundant. + +```html +<!-- unnecessary — gl-icon hides icons from screen readers by default --> +<gl-icon name="rocket" aria-hidden="true" />` + +<!-- good --> +<gl-icon name="rocket" />` +``` + +### Icons that convey information + +Icons convey information if there is loss of information to the user when they are removed from the UI. + +An example is a confidential icon that conveys the issue is confidential, and does not have the text "Confidential" next to it. + +Icons that convey information must have an accessible name so that the information is conveyed to screen reader users too. + +```html +<!-- bad --> +<gl-icon name="eye-slash" />` + +<!-- good --> +<gl-icon name="eye-slash" :aria-label="__('Confidential issue')" />` +``` + +### Icons that are clickable + +Icons that are clickable are semantically buttons, so they should be rendered as buttons, with an accessible name. + +```html +<!-- bad --> +<gl-icon name="close" :aria-label="__('Close')" @click="handleClick" /> + +<!-- good --> +<gl-button icon="close" category="tertiary" :aria-label="__('Close')" @click="handleClick" /> +``` + +## Tooltips + +When adding tooltips, we must ensure that the element with the tooltip can receive focus so keyboard users can see the tooltip. +If the element is a static one, such as an icon, we can enclose it in a button, which already is +focusable, so we don't have to add `tabindex=0` to the icon. + +The following code snippet is a good example of an icon with a tooltip. + +- It is automatically focusable, as it is a button. +- It is given an accessible name with `aria-label`, as it is a button with no text. +- We can use the `gl-hover-bg-transparent!` class if we don't want the button's background to become gray on hover. +- We can use the `gl-p-0!` class to remove the button padding, if needed. + +```html +<gl-button + v-gl-tooltip + class="gl-hover-bg-transparent! gl-p-0!" + icon="warning" + category="tertiary" + :title="tooltipText" + :aria-label="__('Warning')" +/> +``` + +## Hiding elements + +Use the following table to hide elements from users, when appropriate. + +| Hide from sighted users | Hide from screen readers | Hide from both sighted and screen reader users | +| --- | --- | --- | +| `.gl-sr-only` | `aria-hidden="true"` | `display: none`, `visibility: hidden`, or `hidden` attribute | + +### Hide decorative images from screen readers + +To reduce noise for screen reader users, hide decorative images using `alt=""`. +If the image is not an `img` element, such as an inline SVG, you can hide it by adding both `role="img"` and `alt=""`. + +`gl-icon` components automatically hide their icons from screen readers so `aria-hidden="true"` is +unnecessary when using `gl-icon`. + +```html +<!-- good - decorative images hidden from screen readers --> + +<img src="decorative.jpg" alt=""> + +<svg role="img" alt="" /> + +<gl-icon name="epic" /> +``` + +## When to use ARIA + +No ARIA is required when using semantic HTML, because it already incorporates accessibility. + +However, there are some UI patterns that do not have semantic HTML equivalents. +General examples of these are dialogs (modals) and tabs. +GitLab-specific examples are assignee and label dropdowns. +Building such widgets require ARIA to make them understandable to screen readers. +Proper research and testing should be done to ensure compliance with [WCAG](https://www.w3.org/WAI/standards-guidelines/wcag/). diff --git a/doc/development/fe_guide/accessibility/index.md b/doc/development/fe_guide/accessibility/index.md new file mode 100644 index 0000000000000000000000000000000000000000..5274fa644e16ff7e83ee00d9f96c302d4d57190d --- /dev/null +++ b/doc/development/fe_guide/accessibility/index.md @@ -0,0 +1,50 @@ +--- +stage: none +group: unassigned +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments +--- + +# Accessibility + +Accessibility is important for users who use screen readers or rely on keyboard-only functionality +to ensure they have an equivalent experience to sighted mouse users. + +## Accessibility best practices + +Follow these [best practices](best_practices.md) to implement accessible web applications. These are +some of the topics covered in that guide: + +- [Quick checklist](best_practices.md#quick-checklist) +- [Accessible names for screen readers](best_practices.md#provide-accessible-names-for-screen-readers) +- [Icons](best_practices.md#icons) +- [When to use ARIA](best_practices.md#when-to-use-aria) + +## Automated accessibility testing + +Uncover accessibility problems and ensure that your features stay accessible over time by +[implementing automated A11Y tests](automated_testing.md). + +- [When to add accessibility tests](automated_testing.md#when-to-add-accessibility-tests) +- [How to add accessibility tests](automated_testing.md#how-to-add-accessibility-tests) + +## Other resources + +Use these tools and learning resources to improve your web accessibility workflow and skills. + +### Viewing the browser accessibility tree + +- [Firefox DevTools guide](https://developer.mozilla.org/en-US/docs/Tools/Accessibility_inspector#accessing_the_accessibility_inspector) +- [Chrome DevTools guide](https://developer.chrome.com/docs/devtools/accessibility/reference/#pane) + +### Browser extensions + +We have two options for Web accessibility testing: + +- [axe](https://www.deque.com/axe/) for [Firefox](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/) +- [axe](https://www.deque.com/axe/) for [Chrome](https://chrome.google.com/webstore/detail/axe-devtools-web-accessib/lhdoppojpmngadmnindnejefpokejbdd) + +### Other links + +- [The A11Y Project](https://www.a11yproject.com/) is a good resource for accessibility +- [Awesome Accessibility](https://github.com/brunopulis/awesome-a11y) + is a compilation of accessibility-related material diff --git a/doc/development/fe_guide/style/html.md b/doc/development/fe_guide/style/html.md index c92f77e90332a7cbfd68dcc80579366cae847075..9d8809f19c70c0e5b9110251d4ea5bd586f82135 100644 --- a/doc/development/fe_guide/style/html.md +++ b/doc/development/fe_guide/style/html.md @@ -6,7 +6,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w # HTML style guide -See also our [accessibility page](../accessibility.md). +See also our [accessibility best practices](../accessibility/best_practices.md). ## Semantic elements diff --git a/doc/development/testing_guide/best_practices.md b/doc/development/testing_guide/best_practices.md index 04699becd336fefee6aabd8529a76319df715726..89e2442e33c872279b501baad175ac879e68e31c 100644 --- a/doc/development/testing_guide/best_practices.md +++ b/doc/development/testing_guide/best_practices.md @@ -607,7 +607,7 @@ This means preferring Capybara's semantic methods and avoiding querying by IDs, The benefits of testing in this way are that: -- It ensures all interactive elements have an [accessible name](../fe_guide/accessibility.md#provide-accessible-names-for-screen-readers). +- It ensures all interactive elements have an [accessible name](../fe_guide/accessibility/best_practices.md#provide-accessible-names-for-screen-readers). - It is more readable, as it uses more natural language. - It is less brittle, as it avoids querying by IDs, classes, and attributes, which are not visible to the user. @@ -617,7 +617,7 @@ If needed, you can scope interactions within a specific area of the page by usin As you will likely be scoping to an element such as a `div`, which typically does not have a label, you may use a `data-testid` selector in this case. -You can use the `be_axe_clean` matcher to run [axe automated accessibility testing](../fe_guide/accessibility.md#automated-accessibility-testing-with-axe) in feature tests. +You can use the `be_axe_clean` matcher to run [axe automated accessibility testing](../fe_guide/accessibility/automated_testing.md) in feature tests. ##### Externalized contents