Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 89 additions & 71 deletions modules/react/action-bar/stories/ActionBar.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ yarn add @workday/canvas-kit-react
### Basic Example

`ActionBar` includes a container `ActionBar` component and the following subcomponent:
`ActionBar.List` which should contains `ActionBar.Item`.
`ActionBar.List` which should contain `ActionBar.Item`.

In a basic example of an `ActionBar` there are two buttons. The primary action button should be used
only once and left aligned if content is left to right, followed by secondary buttons. Tertiary
Expand All @@ -46,8 +46,8 @@ with `ActionBar.Item` such as `icon` or `size`.

### Delete Action Example

`ActionBar.Item` is a `SecondaryButton` by default but it's posible to change it to another element,
such as `DeleteButton`, by using `as` prop.
`ActionBar.Item` is a `SecondaryButton` by default but it's possible to change it to another
element, such as `DeleteButton`, by using `as` prop.

<ExampleCodeBlock code={DeleteAction} />

Expand Down Expand Up @@ -80,13 +80,15 @@ You can change it from the default of 3 to any number greater than 1 and less th
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
Overflow in Action Bar uses [Menu](/components/popups/menu).

For actions that open a modal or dialog, compose [Modal](/components/popups/modal) or
[Dialog](/components/popups/dialog) from an **`ActionBar.Item`** (see **Dialog/Modal trigger
composition** in Accessibility Requirements below).

When actions overflow, keyboard and assistive technology behavior follow Canvas Kit
[Menu](/components/popups/menu/#accessibility). For broader guidance, see the Canvas Kit
[Accessibility overview](https://workday.github.io/canvas-kit/?path=/docs/guides-accessibility-overview--docs).

### Minimum Accessible Structure
Expand All @@ -100,7 +102,7 @@ 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.List position="relative" as="section" aria-label="Action Bar">
<ActionBar.Item as={PrimaryButton} onClick={() => console.log('first action')}>
First Action
</ActionBar.Item>
Expand All @@ -118,45 +120,34 @@ Canvas Kit applies these automatically. **Do not duplicate them** in consuming c

**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.
- **`ActionBar.Item`**: Defaults to **`SecondaryButton`**, which renders a native
`<button type="button">` with native keyboard behavior. Visible text provides its accessible name.
When overriding **`as`**, preserve the semantics required by that component.
- **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_):
- **`ActionBar.Menu`**: The standard [Menu](/components/popups/menu/#accessibility) API (`ActionBar`
shares one menu model with **`ActionBar.OverflowButton`**).

- 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** (_native `<button>` behavior_):

**Keyboard**:
Visible **`ActionBar.Item`** components use native `<button>` keyboard support: each item is its own
tab stop (<kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd>), and <kbd>Enter</kbd> or
<kbd>Space</kbd> activates it. Action Bar does not use roving tabindex. Overflow menu behavior
follows [Menu accessibility](/components/popups/menu/#accessibility); **`ActionBar.OverflowButton`**
is the menu target.

| Key | Behavior |
Comment thread
purvas12 marked this conversation as resolved.
| ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <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 |
Keyboard focus and screen reader reading order follow **DOM order** among visible
**`ActionBar.Item`** components and **`ActionBar.OverflowButton`**, not the fixed visual position of
the bar on the viewport.

**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").
- On entering the group, the landmark is announced with the **`aria-label`** (for example, "Action
Bar, 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").
Expand All @@ -168,53 +159,80 @@ Canvas Kit applies these automatically. **Do not duplicate them** in consuming c
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 |
**If no design spec is provided:** render **`ActionBar.List`** with `as="section"` and a translated
**`aria-label`**, and **`ActionBar.Item`** components with visible text. Reserve space so the fixed
bar does not cover page content or focused controls. Omit an icon-only item, a dialog or modal
trigger, an overflow button, and **`disabled`** 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`** |
| Icon-only name _(conditional)_ | When the spec requires an icon-only **`ActionBar.Item`**, wrap it in **`Tooltip`** with **`title`** and `type="label"` (preferred), or set **`aria-label`** on the item when the spec says not to use a tooltip. |
| Dialog or modal trigger _(conditional)_ | See **Dialog/Modal trigger composition** below, and the popup component’s accessibility section |
| Overflow button name _(conditional)_ | Translated **`aria-label`** on **`ActionBar.OverflowButton`**, passed through **`overflowButton`** on **`ActionBar.List`** |
| Focus not obscured | Reserve space (padding, spacer, or layout) so the fixed **`ActionBar.List`** does not cover page content or focused controls |
| Disabled action _(conditional)_ | Native **`disabled`** on **`ActionBar.Item`** when the spec marks the action unavailable |

#### Dialog/Modal trigger composition _(Conditional requirement)_

Place **`Dialog`** or **`Modal`** (whichever matches the design) inside **`ActionBar.List`**,
wrapping only the bar action that opens it. Render that action as
**`Dialog.Target as={ActionBar.Item}`** or **`Modal.Target as={ActionBar.Item}`** so the popup model
owns open, close, and focus while the item still registers with the Action Bar. Other
**`ActionBar.Item`** components stay siblings outside the popup container.

```tsx
import {ActionBar} from '@workday/canvas-kit-react/action-bar';
import {Dialog} from '@workday/canvas-kit-react/dialog';

<ActionBar>
<ActionBar.List position="relative" as="section" aria-label="Action Bar">
<Dialog>
<Dialog.Target as={ActionBar.Item}>Create</Dialog.Target>
<Dialog.Popper>
<Dialog.Card>
<Dialog.Heading>Create</Dialog.Heading>
{/* Dialog.Body, actions — see Dialog accessibility */}
</Dialog.Card>
</Dialog.Popper>
</Dialog>
<ActionBar.Item>Cancel</ActionBar.Item>
</ActionBar.List>
</ActionBar>;
```

**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`**
**`ActionBar.Item`** with visible text; reserve space so the fixed bar does not cover page content
or focused controls
- **CONDITIONAL:** icon-only **`Tooltip`** / **`aria-label`** naming; **`Dialog`** / **`Modal`**
composition; **`overflowButton`** with an **`aria-label`**; **`disabled`** when the spec marks an
action unavailable

### Anti-Patterns

Do **not** generate code that does the following (see **Accessibility Requirements** above for what
to supply instead):
to supply instead).

- Omit `as="section"` or **`aria-label`** on **`ActionBar.List`**, which leaves the group
unidentifiable to screen reader users
- Omit `as="section"` or **`aria-label`** on **`ActionBar.List`**
- Reuse the same **`aria-label`** for **`ActionBar.List`** and another landmark on the page
- Use Action Bar for a single action—use [Button](/components/buttons/button/#accessibility) instead
- Add links or URL navigation in Action Bar (for example **`Hyperlink`** or **`href`** on
**`ActionBar.Item`**)—bar actions should be buttons
- 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
**`ActionBar.List`**
- Hand-set **`aria-haspopup`**, **`aria-expanded`**, or **`aria-hidden`** on
**`ActionBar.OverflowButton`** or overflowing **`ActionBar.Item`**—the Action Bar model sets these
- 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
- Hide overflow with CSS, `hidden`, or conditional rendering—use the dynamic API and
`maximumVisible`
- Use overflow with static children—it requires `items` on the model and render props
- Mismatch overflow menu item text or behavior from the visible bar items
- Move focus manually after the overflow menu closes
- Leave fixed **`ActionBar.List`** covering page content or focused controls

## Component API

Expand Down
Loading