Repository navigation
docs(action-bar): Expand accessibility section for AI codegen #4189
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,11 +1,13 @@ | ||||||
| import {Meta} from '@storybook/blocks'; | ||||||
|
|
||||||
| import {ExampleCodeBlock, SymbolDoc} from '@workday/canvas-kit-docs'; | ||||||
|
|
||||||
| import ActionBarStoriesMeta from './ActionBar.stories'; | ||||||
| import {Basic} from './examples/Basic'; | ||||||
| import {Icons} from './examples/Icons'; | ||||||
| import {DeleteAction} from './examples/DeleteAction'; | ||||||
| import {Icons} from './examples/Icons'; | ||||||
| import {OverflowActionBar} from './examples/OverflowActionBar'; | ||||||
| import {OverflowActionBarCustomButtonCount} from './examples/OverflowActionBarCustomButtonCount'; | ||||||
| import ActionBarStoriesMeta from './ActionBar.stories'; | ||||||
|
|
||||||
| <Meta of={ActionBarStoriesMeta} /> | ||||||
|
|
||||||
|
|
@@ -75,13 +77,144 @@ You can change it from the default of 3 to any number greater than 1 and less th | |||||
|
|
||||||
| ## Accessibility | ||||||
|
|
||||||
| Grouping the actions into an HTML `<section>` element with an `aria-label` string is recommended. | ||||||
| This can be useful for helping screen reader users quickly jump down to the actions at the bottom of | ||||||
| a page. | ||||||
| The primary accessibility goal is a clearly named group of page-level actions where every action is | ||||||
| a native, keyboard-operable button. Use Action Bar for the primary and secondary actions of a page | ||||||
| or task. For a single action, use a [Button](/components/buttons/button/#accessibility) directly. | ||||||
| For a dense set of icon or dropdown controls that behaves as one tab stop, use | ||||||
| [Toolbar](https://workday.github.io/canvas-kit/?path=/docs/components-buttons-toolbar--docs). For | ||||||
| actions that open a task flow, compose [Modal](/components/popups/modal/#accessibility) or | ||||||
| [Dialog](/components/popups/dialog/#accessibility) from an **`ActionBar.Item`**. | ||||||
|
|
||||||
| See the [Menu Button pattern (APG)](https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/), the | ||||||
| [Button pattern (APG)](https://www.w3.org/WAI/ARIA/apg/patterns/button/), and the Canvas Kit | ||||||
| [Accessibility overview](https://workday.github.io/canvas-kit/?path=/docs/guides-accessibility-overview--docs). | ||||||
|
|
||||||
| ### Minimum Accessible Structure | ||||||
|
|
||||||
| The following matches the [Basic Example](#basic-example): an **`ActionBar.List`** rendered as a | ||||||
| labelled `section`, with a primary action first and secondary actions after it. **`ActionBar.List`** | ||||||
| and **`ActionBar.Item`** should always be inside **`ActionBar`**. | ||||||
|
|
||||||
| ```tsx | ||||||
| import {ActionBar} from '@workday/canvas-kit-react/action-bar'; | ||||||
| import {PrimaryButton} from '@workday/canvas-kit-react/button'; | ||||||
|
|
||||||
| <ActionBar> | ||||||
| <ActionBar.List as="section" aria-label="Page actions"> | ||||||
| <ActionBar.Item as={PrimaryButton} onClick={() => console.log('first action')}> | ||||||
| First Action | ||||||
| </ActionBar.Item> | ||||||
| <ActionBar.Item>Second Action</ActionBar.Item> | ||||||
| </ActionBar.List> | ||||||
| </ActionBar>; | ||||||
| ``` | ||||||
|
|
||||||
| Refer to [Button](/components/buttons/button/#accessibility) and | ||||||
| [Menus](/components/popups/menu/#accessibility) for more information about accessibiliy of these | ||||||
| components in the Action Bar. | ||||||
| Provide a translated, descriptive **`aria-label`** on **`ActionBar.List`**. Every | ||||||
| **`ActionBar.Item`** needs non-empty visible text, which becomes its accessible name. | ||||||
|
|
||||||
| ### Built-in Behaviors | ||||||
|
|
||||||
| Canvas Kit applies these automatically. **Do not duplicate them** in consuming code. | ||||||
|
|
||||||
| **ARIA and DOM** (_applied by hooks/subcomponents_): | ||||||
|
|
||||||
| - **`ActionBar`**: Does not render an element. It creates the model and wraps its children in | ||||||
| **`Menu`** so the overflow menu shares state. | ||||||
| - **`ActionBar.List`**: Renders a `div` by default. It has no role of its own—use `as="section"` | ||||||
| with **`aria-label`** to expose a landmark. It is positioned fixed to the bottom of the viewport | ||||||
| unless you override `position`. | ||||||
| - **`ActionBar.Item`**: Renders a native `<button>` through **`SecondaryButton`** (or the component | ||||||
| passed to `as`). It inherits the [Button accessibility](/components/buttons/button/#accessibility) | ||||||
| behavior, including visible label wiring and decorative `icon` handling. | ||||||
| - **Overflow**: Items that do not fit, or that exceed `maximumVisible`, receive `aria-hidden`, | ||||||
| `inert`, and `disabled`, so they are removed from the tab order and the accessibility tree and | ||||||
| appear in the overflow menu instead. | ||||||
| - **`ActionBar.OverflowButton`**: Renders a **`SecondaryButton`** with the related-actions icon, | ||||||
| `aria-haspopup`, and `aria-expanded` through the **`Menu.Target`**. It is `aria-hidden` with | ||||||
| `tabIndex={-1}` while no items overflow, and gains `tabIndex={0}` once items overflow. | ||||||
| - **`ActionBar.Menu`**: Is the standard [Menu](/components/popups/menu/#accessibility). | ||||||
| **`ActionBar.Menu.List`** has `role="menu"` labelled by the overflow button, and | ||||||
| **`ActionBar.Menu.Item`** has `role="menuitem"` with roving `tabIndex`. | ||||||
|
|
||||||
| **Focus** (_applied by the model_): | ||||||
|
|
||||||
| - Opening the overflow menu moves focus to its first item. | ||||||
| - Selecting an item closes the menu. | ||||||
| - Closing the menu (Escape, outside click, or selection) returns focus to **`ActionBar.OverflowButton`**. | ||||||
|
|
||||||
| **Keyboard**: | ||||||
|
|
||||||
| | Key | Behavior | | ||||||
| | ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | ||||||
| | <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> | Moves between each visible **`ActionBar.Item`** and the overflow button. Every visible item is its own tab stop; Action Bar does not use roving tabindex | | ||||||
| | <kbd>Enter</kbd> / <kbd>Space</kbd> | Activates the focused item, or opens the overflow menu | | ||||||
| | <kbd>ArrowDown</kbd> / <kbd>ArrowUp</kbd> | Opens the overflow menu from the overflow button | | ||||||
| | <kbd>ArrowDown</kbd> / <kbd>ArrowUp</kbd> inside menu | Moves between overflow menu items | | ||||||
| | <kbd>Escape</kbd> | Closes the overflow menu and returns focus to the overflow button | | ||||||
| | <kbd>Tab</kbd> inside menu | Closes the menu and moves focus to the next focusable element on the page | | ||||||
|
|
||||||
| **Screen reader expectations** (_when built-in behaviors are used as intended_): | ||||||
|
|
||||||
| - On entering the group, the landmark is announced with the **`aria-label`** (for example, "Page | ||||||
| actions, region"). | ||||||
| - Each visible item is announced by its text and role (for example, "First Action, button"). | ||||||
| - The overflow button is announced with its **`aria-label`** and as a menu button with expanded or | ||||||
| collapsed state (for example, "More actions, menu button, collapsed"). | ||||||
| - Items moved into the overflow menu are no longer announced as buttons in the bar. They are | ||||||
| announced as menu items when the menu is open. | ||||||
|
|
||||||
| ### Accessibility Requirements | ||||||
|
|
||||||
| Required in application code for an accessible Action Bar. Rows marked _(conditional)_ apply only | ||||||
| when the situation matches—otherwise omit. | ||||||
|
|
||||||
| **If no design spec is provided:** render **`ActionBar`** → **`ActionBar.List`** with `as="section"` | ||||||
| and a translated **`aria-label`**, one primary **`ActionBar.Item`** first, followed by secondary | ||||||
| **`ActionBar.Item`** components with visible text. Omit `icon`, `disabled`, the overflow API, custom | ||||||
| `maximumVisible`, and custom `data-id` unless the spec requires them. | ||||||
|
|
||||||
| | Requirement | How to satisfy | | ||||||
| | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | ||||||
| | Group label | `as="section"` and a translated **`aria-label`** on **`ActionBar.List`** that is unique among landmarks on the page | | ||||||
| | Item accessible name | Non-empty visible text as the child of every **`ActionBar.Item`** | | ||||||
| | Composition order | **`ActionBar`** → **`ActionBar.List`** → **`ActionBar.Item`**; add **`ActionBar.Menu`** as a sibling of **`ActionBar.List`** for overflow | | ||||||
| | Primary action _(conditional)_ | **`as={PrimaryButton}`** on only the first **`ActionBar.Item`**; secondary actions use the default; do not use **`TertiaryButton`** | | ||||||
| | Destructive action _(conditional)_ | **`as={DeleteButton}`** only for destructive actions, per design | | ||||||
| | Decorative icon _(conditional)_ | **`icon`** on **`ActionBar.Item`** with visible text—no extra ARIA on the icon | | ||||||
| | Overflow button name _(conditional)_ | Translated **`aria-label`** on **`ActionBar.OverflowButton`** (required by its type), passed through **`overflowButton`** on **`ActionBar.List`** | | ||||||
| | Overflow menu _(conditional)_ | Dynamic API: `items` on **`useActionBarModel`**, render props on both **`ActionBar.List`** and **`ActionBar.Menu.List`**, and **`ActionBar.Menu.Popper`** → **`Card`** → **`List`** → **`Item`** with the same item text | | ||||||
| | Menu item activation _(conditional)_ | The same `onClick` handler on **`ActionBar.Item`** and its **`ActionBar.Menu.Item`** counterpart so an action behaves the same wherever it renders | | ||||||
| | Fixed placement _(conditional)_ | **`ActionBar.List`** is `position: fixed` at the bottom of the viewport. Reserve space so page content and focused controls are not hidden behind it, or pass `position="relative"` when the bar sits in the page flow | | ||||||
| | Disabled action _(conditional)_ | Native **`disabled`** on **`ActionBar.Item`** when the spec marks the action unavailable | | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: sed -n '165,220p' modules/react/action-bar/stories/ActionBar.mdx
sed -n '1,180p' modules/react/action-bar/lib/useActionBarModel.tsx
rg -n 'ActionBar.Menu.Item|aria-disabled|disabled' modules/react/action-bar/stories modules/react/action-bar/libRepository: Workday/canvas-kit Length of output: 11016 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- Action Bar composition and item definitions ---'
rg -n -C 8 'Menu\.Item|ActionBarItem|disabled|nonInteractiveIds|useActionBarModel' modules/react/action-bar/lib modules/react/action-bar/stories/examples
printf '%s\n' '--- Menu files ---'
git ls-files 'modules/react/menu/*' 'modules/react/menu/**/*' | head -80
printf '%s\n' '--- Menu disabled/non-interactive handling ---'
rg -n -C 8 'aria-disabled|nonInteractiveIds|disabled|onSelect|MenuItem|menuitem' modules/react/menuRepository: Workday/canvas-kit Length of output: 42461 Disable unavailable actions in the overflow menu too. When a disabled action overflows, the model does not transfer its disabled state to Suggested documentation update-| Disabled action _(conditional)_ | Native **`disabled`** on **`ActionBar.Item`** when the spec marks the action unavailable |
+| Disabled action _(conditional)_ | Native **`disabled`** on **`ActionBar.Item`** when the spec marks the action unavailable; when the action overflows, use **`aria-disabled`** on its **`ActionBar.Menu.Item`** counterpart |📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
|
|
||||||
| **Summary for code generation:** | ||||||
|
|
||||||
| - **REQUIRED:** **`ActionBar.List`** with `as="section"` and a translated **`aria-label`**; every | ||||||
| **`ActionBar.Item`** with visible text; one primary action first | ||||||
| - **CONDITIONAL:** **`icon`**; **`DeleteButton`**; **`overflowButton`** with an **`aria-label`**; | ||||||
| matching **`ActionBar.Menu`** items and handlers; `position="relative"`; **`disabled`** | ||||||
|
|
||||||
| ### Anti-Patterns | ||||||
|
|
||||||
| Do **not** generate code that does the following (see **Accessibility Requirements** above for what | ||||||
| to supply instead): | ||||||
|
|
||||||
| - Omit `as="section"` or **`aria-label`** on **`ActionBar.List`**, which leaves the group | ||||||
| unidentifiable to screen reader users | ||||||
| - Reuse the same **`aria-label`** for **`ActionBar.List`** and another landmark on the page | ||||||
| - Use more than one **`PrimaryButton`** or any **`TertiaryButton`** in an Action Bar | ||||||
| - Render **`ActionBar.Item`** without visible text or as a non-button element with `onClick` | ||||||
| - Add `role="toolbar"`, `role="group"`, roving tabindex, or arrow-key handlers to | ||||||
| **`ActionBar.List`**—each item must be a separate tab stop | ||||||
| - Render **`ActionBar.OverflowButton`** without **`aria-label`** | ||||||
| - Set `aria-haspopup`, `aria-expanded`, or `aria-hidden` on **`ActionBar.OverflowButton`** or | ||||||
| **`ActionBar.Item`**—the model sets them | ||||||
| - Hide overflowing items with CSS, `hidden`, or conditional rendering—use the dynamic API | ||||||
| and `maximumVisible` | ||||||
| - Use the overflow behavior with static children—it requires `items` on the model and render props | ||||||
| - Provide an overflow menu whose items differ in text or behavior from the action bar items | ||||||
| - Move focus manually after the overflow menu closes—**`Menu`** returns focus to the overflow button | ||||||
| - Leave the fixed **`ActionBar.List`** covering page content or focused controls | ||||||
|
|
||||||
| ## Component API | ||||||
|
|
||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: Workday/canvas-kit
Length of output: 14119
Make the no-design-spec example safe for page layout.
ActionBar.Listdefaults toposition: fixedat the viewport bottom. The minimum example and no-design-spec instruction provide no reserved space, so generated layouts can cover bottom content or focused controls. Setposition="relative"in the minimum example and require it in the no-design-spec guidance.Suggested fix
🤖 Prompt for AI Agents