Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'doc/development/fe_guide')
-rw-r--r--doc/development/fe_guide/accessibility.md643
-rw-r--r--doc/development/fe_guide/accessibility/automated_testing.md125
-rw-r--r--doc/development/fe_guide/accessibility/best_practices.md512
-rw-r--r--doc/development/fe_guide/accessibility/index.md50
-rw-r--r--doc/development/fe_guide/customizable_dashboards.md4
-rw-r--r--doc/development/fe_guide/dark_mode.md2
-rw-r--r--doc/development/fe_guide/design_tokens.md4
-rw-r--r--doc/development/fe_guide/frontend_goals.md15
-rw-r--r--doc/development/fe_guide/getting_started.md4
-rw-r--r--doc/development/fe_guide/graphql.md4
-rw-r--r--doc/development/fe_guide/index.md6
-rw-r--r--doc/development/fe_guide/performance.md44
-rw-r--r--doc/development/fe_guide/sentry.md97
-rw-r--r--doc/development/fe_guide/style/html.md2
-rw-r--r--doc/development/fe_guide/vue.md27
15 files changed, 827 insertions, 712 deletions
diff --git a/doc/development/fe_guide/accessibility.md b/doc/development/fe_guide/accessibility.md
index a9f82e85493..3915f63f701 100644
--- a/doc/development/fe_guide/accessibility.md
+++ b/doc/development/fe_guide/accessibility.md
@@ -1,640 +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`.
-
-#### 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.according_to :wcag21aa
-end
-```
-
-Make sure to specify the accessibility standard as `:wcag21aa`.
-
-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.0 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).
-
-### 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 00000000000..2c0d598dc58
--- /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 00000000000..37c28f99116
--- /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 00000000000..5274fa644e1
--- /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/customizable_dashboards.md b/doc/development/fe_guide/customizable_dashboards.md
index 476a8acabd0..be0794b95d0 100644
--- a/doc/development/fe_guide/customizable_dashboards.md
+++ b/doc/development/fe_guide/customizable_dashboards.md
@@ -63,7 +63,7 @@ export const pageViewsOverTime = {
// Chart options defined by the charting library being used by the panel.
options: {
xAxis: { name: __('Time'), type: 'time' },
- yAxis: { name: __('Counts'), type: 'time' },
+ yAxis: { name: __('Counts'), type: 'value' },
},
// The data to query
data: {
@@ -100,6 +100,7 @@ To add a new visualization render type:
1. Create a new Vue component that accepts `data` and `options` properties.
See [`line_chart.vue`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/app/assets/javascripts/analytics/analytics_dashboards/components/visualizations/line_chart.vue) as an example.
1. Add your component to the list of conditional imports in [`panel_base.vue`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/app/assets/javascripts/vue_shared/components/customizable_dashboard/panels_base.vue#L13).
+1. Add your component to the schema's list of `AnalyticsVisualization` types in [`analytics_visualizations.json`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/app/validators/json_schemas/analytics_visualization.json).
#### Adding a new visualization data source
@@ -107,6 +108,7 @@ To add a new data source:
1. Create a new JavaScript module that exports a `fetch` method. See [`cube_analytics.js`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/cube_analytics.js#L122) as an example.
1. Add your module to the list exports in [`data_sources/index.js`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/index.js).
+1. Add your data source to the schema's list of `Data` types in [`analytics_visualizations.json`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/app/validators/json_schemas/analytics_visualization.json).
NOTE:
Your data source must respect the filters so that all panels show data for
diff --git a/doc/development/fe_guide/dark_mode.md b/doc/development/fe_guide/dark_mode.md
index 144418f72cd..185bd60dd9a 100644
--- a/doc/development/fe_guide/dark_mode.md
+++ b/doc/development/fe_guide/dark_mode.md
@@ -1,7 +1,7 @@
---
type: reference, dev
stage: none
-group: Development
+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
---
diff --git a/doc/development/fe_guide/design_tokens.md b/doc/development/fe_guide/design_tokens.md
index 9a1cc48c68f..b47c2661e19 100644
--- a/doc/development/fe_guide/design_tokens.md
+++ b/doc/development/fe_guide/design_tokens.md
@@ -1,7 +1,7 @@
---
type: reference, dev
stage: none
-group: Development
+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
---
@@ -95,7 +95,7 @@ The Design Tokens Format Module promotes a `*.token.json` extension standard for
### Transformations
-Our design tokens use [style-dictionary](https://amzn.github.io/style-dictionary) to convert design tokens into consumable file formats (CSS/SCSS/JavaScript/JSON).
+Our design tokens use [style-dictionary](https://amzn.github.io/style-dictionary/) to convert design tokens into consumable file formats (CSS/SCSS/JavaScript/JSON).
A parser makes [design tokens format properties](https://tr.designtokens.org/format/#design-token-properties) compatible with [style-dictionary design token attributes](https://amzn.github.io/style-dictionary/#/tokens?id=design-token-attributes).
diff --git a/doc/development/fe_guide/frontend_goals.md b/doc/development/fe_guide/frontend_goals.md
index 05e9b0df389..4f39e82c72e 100644
--- a/doc/development/fe_guide/frontend_goals.md
+++ b/doc/development/fe_guide/frontend_goals.md
@@ -16,7 +16,7 @@ Currently, GitLab mostly follows Rails architecture and Rails routing which mean
- mounting Vue applications if we have any;
- fetching data for these applications
-Ideally, we should reduce the number of times user needs to go through this long process. This would be possible with converting GitLab into a single-page application but this would require significant refactoring and is not an achieavable short/mid-term goal.
+Ideally, we should reduce the number of times user needs to go through this long process. This would be possible with converting GitLab into a single-page application but this would require significant refactoring and is not an achievable short/mid-term goal.
The realistic goal is to move to _multiple SPAs_ experience where we define the _clusters_ of pages that form the user flow, and move this cluster from Rails routing to a single-page application with client-side routing. This way, we can load all the relevant context from HAML only once, and fetch all the additional data from the API depending on the route. An example of a cluster could be the following pages:
@@ -29,3 +29,16 @@ The realistic goal is to move to _multiple SPAs_ experience where we define the
All of them have the same context (project path, current user etc.), we could easily fetch more data with issue-specific parameter (issue `iid`) and store the results on the client (so that opening the same issue won't require more API calls). This leads to a smooth user experience for navigating through issues.
For navigation between clusters, we can still rely on Rails routing. These cases should be relatively more scarce than navigation within clusters.
+
+## Reusable components
+
+Currently, we keep generically reusable components in two main places:
+
+- GitLab UI
+- `vue_shared` folder
+
+While GitLab UI is well-documented and components are abstract enough to be reused anywhere in Vue applications, our `vue_shared` components are somewhat chaotic, often can be used only in certain context (for example, they can be bound to an existing Vuex store) and have duplicates (we have multiple components for notes).
+
+We should perform an audit of `vue_shared`, find out what can and what cannot be moved to GitLab UI, and refactor existing components to remove duplicates and increase reusability. The ideal outcome would be having application-specific components moved to application folders, and keep reusable "smart" components in the shared folder/library, ensuring that every single piece of reusable functionality has _only one implementation_.
+
+This is currently under development. Follow the [GitLab Modular Monolith for FE](https://gitlab.com/gitlab-org/gitlab/-/issues/422903) for updates on how we will enforce encapsulation on top-level folders like `vue_shared`.
diff --git a/doc/development/fe_guide/getting_started.md b/doc/development/fe_guide/getting_started.md
index bb59bf7b8ee..14e704d567e 100644
--- a/doc/development/fe_guide/getting_started.md
+++ b/doc/development/fe_guide/getting_started.md
@@ -14,7 +14,7 @@ There are a lot of things to consider for a first merge request and it can feel
### Step 1: Preparing the issue
-Before tackling any work, read through the issue that has been assigned to you and make sure that all [required departments](https://about.gitlab.com/handbook/engineering/#engineering-teams) have been involved as they should. Read through the comments as needed and if unclear, post a comment in the issue summarizing **what you think the work is** and ping your Engineering or Product Manager to confirm. Then once everything is clarified, apply the correct worfklow labels to the issue and create a merge request branch. If created directly from the issue, the issue and the merge request will be linked by default.
+Before tackling any work, read through the issue that has been assigned to you and make sure that all [required departments](https://about.gitlab.com/handbook/engineering/#engineering-teams) have been involved as they should. Read through the comments as needed and if unclear, post a comment in the issue summarizing **what you think the work is** and ping your Engineering or Product Manager to confirm. Then once everything is clarified, apply the correct workflow labels to the issue and create a merge request branch. If created directly from the issue, the issue and the merge request will be linked by default.
### Step 2: Plan your implementation
@@ -23,7 +23,7 @@ Before writing code, make sure to ask yourself the following questions and have
- What API data is required? Is it already available in our API or should I ask a Backend counterpart?
- If this is GraphQL, write a query proposal and ask your BE counterpart to confirm they are in agreement.
- Can I use [GitLab UI components](https://gitlab-org.gitlab.io/gitlab-ui/?path=/docs/base-accordion--docs)? Which components are appropriate and do they have all of the functionality that I need?
-- Are there existing components or utils in the GitLab project that I could use?
+- Are there existing components or utilities in the GitLab project that I could use?
- [Should this change live behind a Feature Flag](https://about.gitlab.com/handbook/product-development-flow/feature-flag-lifecycle/#when-to-use-feature-flags)?
- In which directory should this code live?
- Should I build part of this feature as reusable? If so, where should it live in the codebase and how do I make it discoverable?
diff --git a/doc/development/fe_guide/graphql.md b/doc/development/fe_guide/graphql.md
index 62183c7b349..99070f3d31c 100644
--- a/doc/development/fe_guide/graphql.md
+++ b/doc/development/fe_guide/graphql.md
@@ -1,7 +1,7 @@
---
type: reference, dev
stage: none
-group: Development
+group: unassigned
info: "See the Technical Writers assigned to Development Guidelines: https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments-to-development-guidelines"
---
@@ -1423,7 +1423,7 @@ wrapper = mount(SomeComponent, {
#### Testing subscriptions
-When testing subscriptions, be aware that default behavior for subscription in `vue-apollo@4` is to re-subscribe and immediatelly issue new request on error (unless value of `skip` restricts us from doing that)
+When testing subscriptions, be aware that default behavior for subscription in `vue-apollo@4` is to re-subscribe and immediately issue new request on error (unless value of `skip` restricts us from doing that)
```javascript
import waitForPromises from 'helpers/wait_for_promises';
diff --git a/doc/development/fe_guide/index.md b/doc/development/fe_guide/index.md
index 70f7aad207b..6bea22bd6bf 100644
--- a/doc/development/fe_guide/index.md
+++ b/doc/development/fe_guide/index.md
@@ -39,7 +39,7 @@ Working with our frontend assets requires Node (v12.22.1 or greater) and Yarn
## Vision
-As Frontend engineers, we strive to give users **delightful experiences**. We should always think of how this applies at GitLab specifically: a great GitLab experience means helping our userbase ship **their own projects faster and with more confidence** when shipping their own software. This means that whenever confronted with a choice for the future of our department, we should remember to try to put this first.
+As Frontend engineers, we strive to give users **delightful experiences**. We should always think of how this applies at GitLab specifically: a great GitLab experience means helping our user base ship **their own projects faster and with more confidence** when shipping their own software. This means that whenever confronted with a choice for the future of our department, we should remember to try to put this first.
### Values
@@ -59,7 +59,7 @@ Additionally, we want our speed to be felt and appreciated by our developers. Th
#### Maintainability
-GitLab is now a large, enterprise-grade software and it often requires complex code to give the best possible experience. Although complexity is a necessity, we must remain vigilent to not let it grow more than it should. To minimize this, we want to focus on making our codebase maintainable by **encapsulating complexity**. This is done by:
+GitLab is now a large, enterprise-grade software and it often requires complex code to give the best possible experience. Although complexity is a necessity, we must remain vigilant to not let it grow more than it should. To minimize this, we want to focus on making our codebase maintainable by **encapsulating complexity**. This is done by:
- Building tools that solve commonly-faced problems and making them easily discoverable.
- Writing better documentation on how we solve our problems.
@@ -113,7 +113,7 @@ Reusable components with technical and usage guidelines can be found in our
#### Frontend FAQ
-Read the [frontend's FAQ](frontend_faq.md) for common small pieces of helpful information.
+Read the [frontend FAQ](frontend_faq.md) for common small pieces of helpful information.
#### [Internationalization (i18n) and Translations](../i18n/externalization.md)
diff --git a/doc/development/fe_guide/performance.md b/doc/development/fe_guide/performance.md
index 432e66bee33..5e8581663b6 100644
--- a/doc/development/fe_guide/performance.md
+++ b/doc/development/fe_guide/performance.md
@@ -366,50 +366,6 @@ browser's developer console from any page in GitLab.
parsed, `DOMContentLoaded` is not needed to bootstrap applications because all
the DOM nodes are already at our disposal.
-- **JavaScript that relies on CSS for calculations should use [`waitForCSSLoaded()`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/assets/javascripts/helpers/startup_css_helper.js#L34):**
- GitLab uses [Startup.css](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/38052)
- to improve page performance. This can cause issues if JavaScript relies on CSS
- for calculations. To fix this the JavaScript can be wrapped in the
- [`waitForCSSLoaded()`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/assets/javascripts/helpers/startup_css_helper.js#L34)
- helper function.
-
- ```javascript
- import initMyWidget from './my_widget';
- import { waitForCSSLoaded } from '~/helpers/startup_css_helper';
-
- waitForCSSLoaded(initMyWidget);
- ```
-
- Note that `waitForCSSLoaded()` methods supports receiving the action in different ways:
-
- - With a callback:
-
- ```javascript
- waitForCSSLoaded(action)
- ```
-
- - With `then()`:
-
- ```javascript
- waitForCSSLoaded().then(action);
- ```
-
- - With `await` followed by `action`:
-
- ```javascript
- await waitForCSSLoaded;
- action();
- ```
-
- For example, see how we use this in [`app/assets/javascripts/pages/projects/graphs/charts/index.js`](https://gitlab.com/gitlab-org/gitlab/-/commit/5e90885d6afd4497002df55bf015b338efcfc3c5#02e81de37f5b1716a3ef3222fa7f7edf22c40969_9_8):
-
- ```javascript
- waitForCSSLoaded(() => {
- const languagesContainer = document.getElementById('js-languages-chart');
- //...
- });
- ```
-
- **Supporting Module Placement:**
- If a class or a module is _specific to a particular route_, try to locate
it close to the entry point in which it is used. For instance, if
diff --git a/doc/development/fe_guide/sentry.md b/doc/development/fe_guide/sentry.md
index af4a006c7ea..929de1499c7 100644
--- a/doc/development/fe_guide/sentry.md
+++ b/doc/development/fe_guide/sentry.md
@@ -4,31 +4,94 @@ 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
---
-# Sentry
+# Sentry monitoring in the frontend development of GitLab
-As part of the [Frontend Observability Working Group](https://google.com) we're looking to provide documentation on how to use Sentry effectively.
-If left unchecked, Sentry can get noisy and become unreliable.
-This page aims to help guide us toward more sensible Sentry usage.
+The GitLab Frontend team uses Sentry as an observability tool to monitor how the UI performs for
+users on `gitlab.com`.
-## Which errors we should report to Sentry explicitly and which should be only shown to users (e.g. as alerts)
+GitLab.com is configured to report to our Sentry instance at **Admin > Metrics and profiling > Sentry**.
-If we send all errors to Sentry, it gets very noisy, very quickly.
-We want to filter out the errors that we either don't care about, or have no control over.
-For example, if a user fills out a form incorrectly, this is not something we want to send to Sentry.
-If that form fails because it's hitting a dead endpoint, this is an error we want Sentry to know about.
+We monitor two kinds of data: **Errors** and **Performance**.
-## How to catch errors correctly so Sentry can display them reliably
+NOTE:
+The [Frontend Observability Working Group](https://handbook.gitlab.com/handbook/company/working-groups/frontend-observability/) is looking to improve how we use Sentry. GitLab team members can provide feedback at
+[issue #427402](https://gitlab.com/gitlab-org/gitlab/-/issues/427402).
-TBD
+## Start using Sentry
-## How to catch special cases you want to track (like we did with the pipeline graph)
+Our Sentry instance is located at [https://new-sentry.gitlab.net/](https://new-sentry.gitlab.net/).
+Only GitLab team members can access Sentry.
-TBD
+After your first log-in you can join the `#gitlab` team by selecting **Join a team**. Confirm that
+`#gitlab` appears under `YOUR TEAMS` in the [teams page](https://new-sentry.gitlab.net/settings/gitlab/teams/).
-## How to navigate Sentry and find errors
+## Error reporting
-TBD
+Errors, also known as "events" in the Sentry UI, are instances of abnormal or unexpected runtime
+behavior that users experience in their browser.
-## How to debug Sentry errors effectively
+GitLab uses the [Sentry Browser SDK](https://docs.sentry.io/platforms/javascript/) to report errors
+to our Sentry instance under the project
+[`gitlabcom-clientside`](https://new-sentry.gitlab.net/organizations/gitlab/projects/gitlabcom-clientside/?project=4).
-TBD
+### Reporting known errors
+
+The most common way to report errors to Sentry is to call `captureException(error)`, for example:
+
+```javascript
+import * as Sentry from '@sentry/browser';
+
+try {
+ // Code that may fail in runtime
+} catch (error) {
+ Sentry.captureException(error)
+}
+```
+
+**When should you report an error?** We want to avoid reporting errors that we either don't care
+about, or have no control over. For example, we shouldn't report validation errors when a user fills
+out a form incorrectly. However, if that form submission fails because or a server error,
+this is an error we want Sentry to know about.
+
+### Unhandled/unknown errors
+
+Additionally, we capture unhandled errors automatically in all of our pages.
+
+## Error Monitoring
+
+Once errors are captured, they appear in Sentry. For example you can see the
+[errors reported in the last 24 hours in canary and production](https://new-sentry.gitlab.net/organizations/gitlab/issues/?environment=gprd-cny&environment=gprd&project=4&query=&referrer=issue-list&sort=freq&statsPeriod=24h).
+
+In the list, select any error to see more details... and ideally propose a solution for it!
+
+NOTE:
+We suggest filtering errors by the environments `gprd` and `gprd-cny`, as there is some spam in our
+environment data.
+
+### Exploring error data
+
+Team members can use Sentry's [Discover page](https://new-sentry.gitlab.net/organizations/gitlab/discover/homepage/?environment=gprd-cny&environment=gprd&field=title&field=event.type&field=project&field=user.display&field=timestamp&field=replayId&name=All+Events&project=4&query=&sort=-timestamp&statsPeriod=14d&yAxis=count%28%29) to find unexpected issues.
+
+Additionally, we have created [a dashboard](https://new-sentry.gitlab.net/organizations/gitlab/dashboard/3/?environment=gprd&environment=gprd-cny&project=4&statsPeriod=24h) to report which feature categories and pages produce
+most errors, among other data.
+
+Engineering team members are encouraged to explore error data and find ways to reduce errors on our
+user interface. Sentry also provides alerts for folks interested in getting notified when errors occur.
+
+### Filtering errors
+
+We receive several thousands of reports per day, so team members can filter errors based on their
+work area.
+
+We mark errors with two additional custom `tags` to help identify their source:
+
+- `feature_category`: The feature area of the page. (For example, `code_review_workflow` or `continuous_integration`.) **Source:** `gon.feature_category`
+- `page`: Identifier of method called in the controller to render the page. (For example, `projects:merge_requests:index` or `projects:pipelines:index`.) **Source:** [`body_data_page`](https://gitlab.com/gitlab-org/gitlab/blob/b2ea95b8b1f15228a2fd5fa3fbd316857d5676b8/app/helpers/application_helper.rb#L144).
+
+Frontend engineering team members can filter errors relevant to their group and/or page.
+
+## Performance Monitoring
+
+We use [BrowserTracing](https://docs.sentry.io/platforms/javascript/performance/) to report performance metrics to Sentry.
+
+You can visit [our performance data of the last 24 hours](https://new-sentry.gitlab.net/organizations/gitlab/performance/?environment=gprd-cny&environment=gprd&project=4&statsPeriod=24h) and use the filters to drill down and learn more.
diff --git a/doc/development/fe_guide/style/html.md b/doc/development/fe_guide/style/html.md
index c92f77e9033..9d8809f19c7 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/fe_guide/vue.md b/doc/development/fe_guide/vue.md
index 8230f38ad8e..cfc3ac466d5 100644
--- a/doc/development/fe_guide/vue.md
+++ b/doc/development/fe_guide/vue.md
@@ -318,11 +318,11 @@ export default {
<template>
<div>
<gl-form-group :label-for="fields.name.id" :label="__('Name')">
- <gl-form-input v-bind="fields.name" size="lg" />
+ <gl-form-input v-bind="fields.name" width="lg" />
</gl-form-group>
<gl-form-group :label-for="fields.email.id" :label="__('Email')">
- <gl-form-input v-bind="fields.email" type="email" size="lg" />
+ <gl-form-input v-bind="fields.email" type="email" width="lg" />
</gl-form-group>
<gl-button type="submit" category="primary" variant="confirm">{{ __('Update') }}</gl-button>
@@ -397,6 +397,29 @@ This approach has a few benefits:
- Accessing a global variable is not required, except in the application's
[entry point](#accessing-the-gl-object).
+#### Redirecting to page and displaying alerts
+
+If you need to redirect to another page and display alerts, you can use the [`visitUrlWithAlerts`](https://gitlab.com/gitlab-org/gitlab/-/blob/7063dce68b8231442567707024b2f29e48ce2f64/app/assets/javascripts/lib/utils/url_utility.js#L731) util.
+This can be useful when you're redirecting to a newly created resource and showing a success alert.
+
+By default the alerts will be cleared when the page is reloaded. If you need an alert to be persisted on a page you can set the
+`persistOnPages` key to an array of Rails controller actions. To find the Rails controller action run `document.body.dataset.page` in your console.
+
+Example:
+
+```javascript
+visitUrlWithAlerts('/dashboard/groups', [
+ {
+ id: 'resource-building-in-background',
+ message: 'Resource is being built in the background.',
+ variant: 'info',
+ persistOnPages: ['dashboard:groups:index'],
+ },
+])
+```
+
+If you need to manually remove a persisted alert, you can use the [`removeGlobalAlertById`](https://gitlab.com/gitlab-org/gitlab/-/blob/7063dce68b8231442567707024b2f29e48ce2f64/app/assets/javascripts/lib/utils/global_alerts.js#L31) util.
+
### A folder for Components
This folder holds all components that are specific to this new feature.