-
Notifications
You must be signed in to change notification settings - Fork 34
docs(Tag, TagList): audit accessibility #6843
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| import { Meta } from '@storybook/addon-docs/blocks' | ||
|
|
||
| <Meta title="UI/Badges/Tag/A11y" /> | ||
|
|
||
| # Tag - Accessibility | ||
|
|
||
| ## Description | ||
|
|
||
| A metadata component that displays a short text description of an item (category, label, key-value). It supports several variants: `copiable` (renders a button that copies its text to the clipboard), `closable` (renders a close button via `onClose`), `disabled`, `isLoading`, and a `keyValue` layout, all available in 7 color sentiments. | ||
|
|
||
| ## Summary | ||
|
|
||
| 1. ✅ **Perceivable**: decorative icons are `aria-hidden` (resolved by the Icon component fix); sentiment contrast is correct. | ||
| 2. ❌ **Operable**: the disabled copiable button remains focusable even though it performs no action. | ||
| 3. ❌ **Understandable**: the copiable button lacks `type="button"` and may unexpectedly submit a parent form. | ||
| 4. ❌ **Robust**: `aria-disabled` is placed on a decorative element instead of the interactive button; the "Copied!" status message is not announced (4.1.3). | ||
|
|
||
| ## Rules | ||
|
|
||
| Enforced by the component: | ||
|
|
||
| - ✅ [WCAG 4.1.2 - Name, Role, Value (Level A)](https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html) - A non-copiable tag renders as a `<span>` and a copiable/removable tag uses a native `<button>`. | ||
| - ❌ [WCAG 2.1.1 - Keyboard (Level A)](https://www.w3.org/WAI/WCAG22/Understanding/keyboard.html) - The copiable button in a `disabled` state remains keyboard-focusable even though it performs no action. | ||
| - ✅ [WCAG 2.4.7 - Focus Visible (Level AA)](https://www.w3.org/WAI/WCAG22/Understanding/focus-visible.html) - Both the **copiable** button and the **close (closable)** button keep the browser-default focus ring, which is a valid, visible focus indicator (passes). | ||
| - ❌ [WCAG 3.2.2 - On Input (Level A)](https://www.w3.org/WAI/WCAG22/Understanding/on-input.html) - The copiable `<button>` has no `type` attribute (defaults to `submit`) and can trigger an unexpected form submission when placed inside a form. | ||
| - ❌ [WCAG 4.1.2 - Name, Role, Value (Level A)](https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html) - `aria-disabled` is set on the inner text `<span>` instead of the actual interactive button, so the disabled state is not programmatically conveyed. | ||
| - ❌ [WCAG 4.1.2 - Name, Role, Value (Level A)](https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html) - The removable (close) button's `aria-label` is a generic "Close tag" that does not include the visible tag text. A removable tag's button label should contain the visible label of the tag. | ||
| - ⚠️ [WCAG 4.1.2 - Name, Role, Value (Level A)](https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html) - The copiable button's accessible name is the tag text, and the tooltip is wired as its accessible description. On focus, the action is conveyed as a description, so the button's purpose is programmatically determinable. Caveat: because announcement relies on the tooltip description, it may vary across screen readers; a visually hidden "Copy" in the name would make it fully robust (see Low below). | ||
| - ❌ [WCAG 4.1.3 - Status Messages (Level AA)](https://www.w3.org/WAI/WCAG22/Understanding/status-messages.html) - The "Copied!" feedback is only shown via a tooltip, which is not announced by screen readers. | ||
|
|
||
| To apply when using the component: | ||
|
|
||
| - [WCAG 1.4.1 - Use of Color (Level A)](https://www.w3.org/WAI/WCAG22/Understanding/use-of-color.html) - The `sentiment` is conveyed only through color; ensure the meaning is also present in the tag text where the sentiment carries information. | ||
| - [WCAG 2.4.3 - Focus Order (Level A)](https://www.w3.org/WAI/WCAG22/Understanding/focus-order.html) - On tag removal, focus should be repositioned to a relevant location. `onClose` is external, so this is the consumer's responsibility. | ||
|
|
||
| ## Accessibility issues | ||
|
|
||
| **High:** | ||
|
|
||
| - Missing `type="button"` on the copiable button: `TagInner` renders `<button>` with no `type`, which defaults to `submit`. Inside a form, clicking a copiable Tag submits the form unexpectedly (WCAG 3.2.2). This also affects the `keyValue + copiable` combination. | ||
| - When `copiable` and `disabled` are both set, the element renders as a `<button>` without the `disabled` attribute, so it remains in the tab order and is announced as an enabled button even though it does nothing (WCAG 2.1.1, 4.1.2). | ||
| - `aria-disabled` is applied to the inner text `<span>`, not to the interactive copiable button, so assistive technology never learns the button is disabled (WCAG 4.1.2). | ||
| - Hardcoded English close label: `aria-label="Close tag"` is not localized and does not include the visible tag text. A removable tag's button label should contain the visible tag label (e.g. `Remove {label}`) so screen reader users know which tag they are removing. Prefer visually hidden text per RFC #6585 or an `accessibleLabel` (WCAG 4.1.2). | ||
| - Copy success is not announced to screen readers: the "Copied!" feedback is only shown through a Tooltip (`role="tooltip"`), which is not read by screen readers, so screen reader users get no confirmation the copy succeeded (WCAG 4.1.3). Use a status (`role="status"`) or live region (`aria-live="assertive"`) to announce the feedback. | ||
|
|
||
| **Medium:** | ||
|
|
||
| - Loader inside a copiable button: `isLoading` renders a `role="progressbar"` labeled "Loading" inside the button, which adds "Loading 20%" to the button's accessible output and may be noisy. | ||
|
|
||
| **Low:** | ||
|
|
||
| - Copiable button's purpose is conveyed only via the tooltip description (`aria-describedby`). However, because this relies on tooltip-description announcement, it may be inconsistent across screen readers. For full robustness, add a visually hidden "Copy" indication to the accessible name (prefer `VisuallyHidden` text over `aria-label`). | ||
| - Investigate if the use of an `aria-atomic` attribute would make screen readers announce a more relevant message after a status update (copy/copied) | ||
| - KeyValue separator: the vertical `<Separator>` between key and value is presentational; consider `aria-hidden` so it is not announced. | ||
|
|
||
| ## Dependencies | ||
|
|
||
| - `Icon` - Icons (CloseIcon, CopyContentIcon, and child icons) were previously exposed with their technical name as the accessible name (WCAG 1.1.1 / 4.1.2). This is being resolved by the Icon component fix (`aria-hidden` by default), which will automatically fix it for Tag. No further action required on Tag. | ||
| - `Tooltip` - The copy-feedback mechanism relies on tooltip announcement behavior. | ||
| - `Loader` - The loading state renders the Loader component inside the copiable button, which adds "Loading 20%" to the button's accessible output. Any adjustment to how the loading state is announced must be coordinated with the Loader component's implementation. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| import { Meta } from '@storybook/addon-docs/blocks' | ||
|
|
||
| <Meta title="UI/Data Display/TagList/A11y" /> | ||
|
|
||
| # TagList - Accessibility | ||
|
|
||
| ## Description | ||
|
|
||
| A layout component that displays a list of `Tag`s. When tags exceed a `threshold`, `maxLength`, or the available container width, the overflow is hidden behind a `+N` counter button that opens a `Popover` (a non-modal dialog) listing the remaining tags. It supports `multiline`, `copiable`, `keyValue`, icon tags, and per-sentiment styling. | ||
|
|
||
| ## Summary | ||
|
|
||
| 1. ❌ **Perceivable**: the collection of tags is not conveyed as a list — it is rendered as plain `<div>`s, so assistive technology has no indication the tags form a cohesive set (WCAG 1.3.1). | ||
| 2. ✅ **Operable**: the counter is keyboard operable; focus management and the redundant wrapper tab stop will be handled by the Popover/Popup fix. | ||
| 3. ✅ **Understandable**: opening the popover happens on explicit activation of the trigger; no unexpected context change. | ||
| 4. ❌ **Robust**: the `+N` counter button has a non-descriptive accessible name and does not expose `aria-haspopup`/`aria-expanded` to reflect the popover's state. | ||
|
|
||
| ## Rules | ||
|
|
||
| Enforced by the component: | ||
|
|
||
| - ❌ [WCAG 1.3.1 - Info and Relationships (Level A)](https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html) - The tags are rendered as plain `<div>`s, not as a list. Because a `TagList` is a collection of related items, the visible tags should be a `<ul>` with each tag in an `<li>`, and the hidden tags inside the popover should be list items too, so screen readers can convey the grouping and count. | ||
| - ❌ [WCAG 4.1.2 - Name, Role, Value (Level A)](https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html) - The `+N` counter button's accessible name is derived solely from its `+N` text (e.g. "+3") and does not describe that it reveals hidden tags. | ||
| - ❌ [WCAG 4.1.2 - Name, Role, Value (Level A)](https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html) - The counter button exposes neither `aria-haspopup="dialog"` nor `aria-expanded`, so assistive technology is not told that activating it opens a dialog or whether it is currently open. TagList owns both the button and the `isPopoverVisible` state, so it can set `aria-haspopup="dialog"` and `aria-expanded={isPopoverVisible}` directly on its own button. | ||
| - ✅ [WCAG 2.1.1 - Keyboard (Level A)](https://www.w3.org/WAI/WCAG22/Understanding/keyboard.html) - The counter is a native `<button>` and opens the popover with Enter/Space and click; Escape closes the popover (handled by Popup). | ||
| - ✅ [WCAG 1.1.1 - Non-text Content (Level A)](https://www.w3.org/WAI/WCAG22/Understanding/non-text-content.html) / [WCAG 4.1.2 - Name, Role, Value (Level A)](https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html) - The measurement container is correctly hidden from assistive technology. It combines `aria-hidden="true"` (removes the subtree from the accessibility tree), `visibility: hidden` (prevents rendering and focusability), and `pointer-events: none` (no pointer interaction). Because the duplicated tags and the `+N` counter button inside it are not focusable, it introduces no extra tab stops and no duplicate announced controls. | ||
|
|
||
| Delegated to the `Popover` / `Popup` components (see the Popover audit): | ||
|
|
||
| - [WCAG 4.1.2 / 1.3.1 - Dialog accessible name](https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html) - the `role="dialog"` must be named via `aria-labelledby` pointing at its visible title. | ||
| - [WCAG 2.4.3 - Focus Order](https://www.w3.org/WAI/WCAG22/Understanding/focus-order.html) - focus must move into the dialog on open and be returned to the trigger on close. | ||
| - [WCAG 4.1.2 - Trigger announcement](https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html) - the trigger should expose `aria-haspopup="dialog"` (the Popover `aria-haspopup` prop is not forwarded). | ||
|
|
||
| To apply when using the component: | ||
|
|
||
| - [WCAG 1.4.1 - Use of Color (Level A)](https://www.w3.org/WAI/WCAG22/Understanding/use-of-color.html) - The `sentiment` is conveyed only through color; ensure the meaning is also present in the tag text where it carries information. | ||
| - [WCAG 1.1.1 - Non-text Content (Level A)](https://www.w3.org/WAI/WCAG22/Understanding/non-text-content.html) - Icon tags (`{ icon, label }`) must remain decorative/hidden; the label provides the accessible name (resolved by the Icon component fix). | ||
|
|
||
| ## Accessibility issues | ||
|
|
||
| **High:** | ||
|
|
||
| - The visible tags and the hidden tags inside the popover are plain `<div>`s, so the collection is not conveyed to assistive technology (WCAG 1.3.1). Use a `<ul>` with each tag in an `<li>` for the visible container and for the popover content. The `+N` counter should remain a sibling of the list, not a list item. The measurement container is a separate, `aria-hidden` element used only for sizing; it is not affected by wrapping the visible/popover tags and can stay as-is. | ||
| - Counter button lacks a descriptive accessible name: the `+N` button is announced as "+3" with no indication it reveals the hidden tags. Provide a localizable accessible name such as `Show {hiddenTags.length} hidden tags` (e.g. visually hidden text per RFC #6585, or an `aria-label`) (WCAG 4.1.2). | ||
|
|
||
| **Medium:** | ||
|
|
||
| - Counter button does not expose its disclosure state: no `aria-haspopup="dialog"` and no `aria-expanded` reflecting `isPopoverVisible`, so screen reader users are not told the button opens a dialog nor whether it is open. Since TagList controls the button and the open state, it can set these directly on the button (WCAG 4.1.2). | ||
| - Redundant `onKeyDown` handler for Space/Enter: native `<button>` activation already triggers `onClick`; the extra handler is unnecessary and can cause the popover to open twice (harmless but should be removed). | ||
|
|
||
| ## Dependencies | ||
|
|
||
| - `Popover` / `Popup` - Provide the dialog's accessible name (`aria-labelledby`), focus management on open/close, `aria-haspopup` on the trigger, and a redundant focusable wrapper around the trigger. These are tracked in the Popover audit and must be fixed there before TagList can fully comply with WCAG 2.4.3 and 4.1.2. | ||
| - `Icon` - The tag icon exposure issue is being resolved by the Icon component fix (icons `aria-hidden` by default). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.