Skip to content
Draft
Show file tree
Hide file tree
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
22 changes: 11 additions & 11 deletions 2nd-gen/packages/core/components/action-group/ActionGroup.base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ import {
} from './ActionGroup.types.js';

/**
* An action group clusters related action buttons together with composite
* keyboard navigation: one Tab stop into the strip, arrow keys move among
* `swc-action-button` and `swc-action-menu` children.
* An action group clusters related actions together with consistent
* spacing, sizing, and orientation. It treats the whole strip as a single
* stop in the keyboard sequence, with arrow keys moving between children.
*
* This base class owns the accessibility semantics, `accessible-label` →
* `aria-label` management, `disabled` state contract, and child collection logic.
Expand Down Expand Up @@ -76,12 +76,12 @@ export abstract class ActionGroupBase extends SizedMixin(SpectrumElement, {
* `FocusgroupNavigationController` moves focus with the Up/Down arrow
* keys instead of Left/Right.
*
* Note: this property does NOT set `aria-orientation` on the host.
* `aria-orientation` is only a supported ARIA attribute on roles that
* expose it (`toolbar`, `listbox`, `menu`, etc.) — `role="group"` does
* not, regardless of the roving-tabindex keyboard model implemented in
* JS. Setting it fails axe's `aria-allowed-attr` rule. Matches the same
* decision already made for `swc-button-group`.
* Note: this property does NOT set `aria-orientation` on the host. The
* host's role is fixed to `role="group"`, and `aria-orientation` is only
* a supported ARIA attribute on roles that expose it (`toolbar`,
* `listbox`, `menu`, etc.), not `group`, regardless of this component's
* roving-tabindex keyboard model. Setting it anyway fails axe's
* `aria-allowed-attr` rule.
*
* @default horizontal
*/
Expand All @@ -92,7 +92,7 @@ export abstract class ActionGroupBase extends SizedMixin(SpectrumElement, {
* Accessible label for the group. Reflected to `aria-label` on the host.
*
* Providing a label is recommended whenever the strip has a distinct
* purpose (e.g. "Text formatting" or "Alignment"). An empty value removes
* purpose (e.g., "Image adjustments" or "Edit"). An empty value removes
* the `aria-label` attribute.
*/
@property({ type: String, attribute: 'accessible-label' })
Expand All @@ -103,7 +103,7 @@ export abstract class ActionGroupBase extends SizedMixin(SpectrumElement, {
*
* Sets `aria-disabled="true"` on the host and propagates `aria-disabled`
* to each managed child. Children remain keyboard-reachable so that screen
* reader users can still discover the group — native `disabled` is not
* reader users can still discover the group. Native `disabled` is not
* applied to children.
*/
@property({ type: Boolean, reflect: true })
Expand Down
28 changes: 14 additions & 14 deletions 2nd-gen/packages/swc/components/action-group/ActionGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,31 +22,31 @@ import { FocusgroupNavigationController } from '@adobe/spectrum-wc-core/controll
import styles from './action-group.css';

/**
* An action group clusters related action buttons together with composite
* keyboard navigation: one Tab stop into the strip, arrow keys move among
* `swc-action-button` and `swc-action-menu` children.
* An action group clusters related actions together with consistent
* spacing, sizing, and orientation.
*
* Unlike `swc-button-group`, which lets Tab reach each button independently,
* action group owns composite navigation (one Tab stop; arrow keys move
* among items).
* Unlike `swc-button-group`, where each button is reachable independently
* via the keyboard, action group treats the whole strip as a single stop,
* with arrow keys moving between its children. See the Keyboard navigation
* section for the full key list.
*
* @element swc-action-group
* @since 2.0.0
*
* @slot - One or more `swc-action-button` or `swc-action-menu` elements.
*
* @cssprop --swc-action-group-gap - Space between children. Defaults to the medium spacing token (small spacing token at `xs`/`s` sizes).
* @cssprop --swc-action-group-gap - Space between children. Scales with the `size` attribute across all five sizes. Defaults to the medium spacing token when no size is set.
*
* @example
* <swc-action-group accessible-label="Text formatting">
* <swc-action-button>Bold</swc-action-button>
* <swc-action-button>Italic</swc-action-button>
* <swc-action-group accessible-label="Image adjustments">
* <swc-action-button>Crop</swc-action-button>
* <swc-action-button>Rotate</swc-action-button>
* </swc-action-group>
*
* @example
* <swc-action-group orientation="vertical" accessible-label="Alignment">
* <swc-action-button>Left</swc-action-button>
* <swc-action-button>Center</swc-action-button>
* <swc-action-group orientation="vertical" accessible-label="Edit">
* <swc-action-button>Cut</swc-action-button>
* <swc-action-button>Copy</swc-action-button>
* </swc-action-group>
*/
export class ActionGroup extends ActionGroupBase {
Expand Down Expand Up @@ -141,7 +141,7 @@ export class ActionGroup extends ActionGroupBase {
* Watches for `disabled` and `aria-disabled` attribute changes on individual
* managed children. `slotchange` does not fire when an existing child's
* disabled state changes, so without this observer the navigation controller
* would hold a stale tab-stop assignment (e.g. btn1 retaining tabindex=0
* would hold a stale tab-stop assignment (e.g., btn1 retaining tabindex=0
* after becoming disabled) and Tab could no longer enter the group.
*/
private childObserver?: MutationObserver;
Expand Down
111 changes: 111 additions & 0 deletions 2nd-gen/packages/swc/components/action-group/action-group.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { Canvas, Meta } from '@storybook/addon-docs/blocks';
import { DocsFooter, DocsHeader } from '../../.storybook/blocks';

import * as ActionGroupStories from './stories/action-group.stories';

<Meta of={ActionGroupStories} />

<DocsHeader />

Migrating from `<sp-action-group>`? See the [Action group migration guide](?path=/docs/components-action-group-migration-guide--docs).

## Anatomy

An action group consists of one or more `swc-action-button` elements placed in the default slot. An icon-only child needs `accessible-label`, since there's no visible text to derive an accessible name from.

<Canvas of={ActionGroupStories.Anatomy} />

## Options

### Sizes

Action groups come in five sizes: extra-small (`xs`), small (`s`), medium (`m`), large (`l`), and extra-large (`xl`). The `size` attribute controls both the size of slotted children and the gap between them.

<Canvas of={ActionGroupStories.Sizes} />

### Orientation

The `orientation` attribute controls both the layout direction and the arrow-key direction of the composite keyboard navigation. `horizontal` is the default. `vertical` stacks children and moves the arrow-key direction to match. `swc-action-group` never sets `aria-orientation` on itself. See [Toolbar composition](#toolbar-composition) for details on applying it to a wrapper instead.

<Canvas of={ActionGroupStories.Orientations} />

### Quiet

The `quiet` attribute applies a low-emphasis visual treatment and is propagated to all slotted children. Setting `quiet` also disables the `compact` border-join styling.

<Canvas of={ActionGroupStories.Quiet} />

### Compact

The `compact` attribute visually joins children by collapsing shared borders and resetting the interior border-radius values where buttons meet. It has no visual effect when `quiet` is also set.

<Canvas of={ActionGroupStories.Compact} />

### Justified

The `justified` attribute makes slotted children expand equally to fill the group's available inline width. It has no visible effect unless the host has more available width than its content needs, for example an explicit `inline-size` on the host or a container that stretches it.

<Canvas of={ActionGroupStories.Justified} />

### Static colors

Setting `static-color` on the group propagates that value to every slotted `swc-action-button` child, pinning their color to the chosen value regardless of the active color theme. Action-group has no static-color-specific styling of its own; use it when the group needs to sit on top of a photo or colored background:

- **`white`**: use on dark color or image backgrounds
- **`black`**: use on light color or image backgrounds

<Canvas of={ActionGroupStories.StaticColors} />

## States

### Disabled

The `disabled` attribute sets `aria-disabled="true"` on the host and propagates `aria-disabled="true"` to every slotted child. Children remain in the <kbd>Tab</kbd> and arrow-key sequence so screen reader users can still discover the group. Activation is suppressed rather than removing the group from the keyboard sequence entirely.

<Canvas of={ActionGroupStories.Disabled} />

## Accessibility

### Features

The `<swc-action-group>` element implements several accessibility features:

#### Keyboard navigation

- <kbd>Tab</kbd>: moves focus into the group, to the last-focused child or the
first enabled child
- <kbd>Arrow Left</kbd> / <kbd>Arrow Right</kbd>: moves focus between children
when `orientation="horizontal"`
- <kbd>Arrow Up</kbd> / <kbd>Arrow Down</kbd>: moves focus between children when
`orientation="vertical"`

Only one child is in the <kbd>Tab</kbd> sequence at a time. Arrow keys move that single <kbd>Tab</kbd> stop among the group's children, wrapping from the last child to the first and back.

#### Toolbar composition

`swc-action-group` never sets `role="toolbar"` or `aria-orientation` on itself, in any mode or orientation. Those two attributes only become available once you wrap one or more `swc-action-group` elements in your own element to build an actual toolbar landmark:

- Put `role="toolbar"` and a label on that wrapper element, not on `swc-action-group`. Nesting multiple named groups inside one outer toolbar is the supported pattern for grouping related clusters of actions.
- If the wrapper stacks its groups vertically, also set `aria-orientation="vertical"` on the wrapper itself. `role="toolbar"` defaults to horizontal per the [ARIA `aria-orientation` reference](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-orientation), and assistive technology assumes that until told otherwise.
- Each inner `swc-action-group`'s own `orientation` attribute is unrelated to the wrapper's `aria-orientation`: it only controls that group's own arrow-key direction, not the wrapper's ARIA state.

The first example below is a standalone group with no wrapper at all. `role="toolbar"` and `aria-orientation` are optional, wrapper-only additions, not requirements for using `swc-action-group`.

1. **Fixed role**: the host always exposes `role="group"`. It never switches to `role="toolbar"` or `role="radiogroup"`, and this role is not author-overridable.
2. **Naming**: `accessible-label` reflects to `aria-label` on the host. `aria-labelledby` remains valid as an alternative. Providing one is recommended whenever the strip has a distinct purpose.
3. **Group `disabled`**: `aria-disabled="true"` is set on the host and propagated to every child. Children stay focusable and keyboard-discoverable rather than being removed from the tab order.

### Best practices

- Provide `accessible-label` whenever the group's purpose is not obvious from surrounding context.
- When combining multiple `swc-action-group` elements into a toolbar, wrap them in a single outer `role="toolbar"` element with its own label rather than nesting `role="toolbar"` landmarks.
- When that toolbar wrapper stacks its groups vertically, set `aria-orientation="vertical"` on the wrapper. Assistive technology assumes a toolbar is horizontal until told otherwise.
- Use `disabled` on the group to disable every child at once rather than disabling each `swc-action-button` individually.

<Canvas of={ActionGroupStories.Accessibility} />

## Upcoming features

- **Truncated labels in justified groups**: when `swc-action-button` adds a `truncate` property, `justified` action groups will apply it automatically to slotted children, so equal-width buttons clip an overflowing label with an ellipsis instead of wrapping or overflowing their fixed width.

<DocsFooter />
Loading
Loading