@workday/canvas-kit-docs 16.0.10 → 16.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,20 +1,21 @@
1
1
  import {ExampleCodeBlock, Specifications, SymbolDoc} from '@workday/canvas-kit-docs';
2
+
3
+ import AllFields from './examples/AllFields';
2
4
  import Basic from './examples/Basic';
3
5
  import Caution from './examples/Caution';
4
- import Error from './examples/Error';
6
+ import Custom from './examples/Custom';
7
+ import CustomId from './examples/CustomId';
5
8
  import Disabled from './examples/Disabled';
9
+ import Error from './examples/Error';
10
+ import GroupedInputs from './examples/GroupedInputs';
11
+ import Grow from './examples/Grow';
6
12
  import HiddenLabel from './examples/HiddenLabel';
7
- import LabelPositionHorizontalStart from './examples/LabelPositionHorizontalStart';
13
+ import Hint from './examples/Hint';
8
14
  import LabelPositionHorizontalEnd from './examples/LabelPositionHorizontalEnd';
15
+ import LabelPositionHorizontalStart from './examples/LabelPositionHorizontalStart';
9
16
  import RefForwarding from './examples/RefForwarding';
10
17
  import Required from './examples/Required';
11
- import Custom from './examples/Custom';
12
- import CustomId from './examples/CustomId';
13
- import AllFields from './examples/AllFields';
14
- import Hint from './examples/Hint';
15
- import Grow from './examples/Grow';
16
18
  import ThemedError from './examples/ThemedErrors';
17
- import GroupedInputs from './examples/GroupedInputs';
18
19
 
19
20
 
20
21
  # Canvas Kit Form Field
@@ -134,14 +135,16 @@ for required fields are suffixed by a red asterisk.
134
135
  ### Grouped Inputs
135
136
 
136
137
  Use `FormFieldGroup` when you have a group of inputs that need to be associated to one another, like
137
- `RadioGroup` or a group of `Checkbox`'s. `FormFieldGroup` renders a `fieldset` element and
138
- `FormFieldGroup.Label` renders a `legend` element. These elements will allow screen readers to
139
- automatically announce the legend's context when focusing on the inputs in the group.
138
+ `RadioGroup` or a group of `Checkbox`'s. `FormFieldGroup` renders a `div` with `role="group"` and an
139
+ `aria-labelledby` reference. `FormFieldGroup.Label` renders a `div` with an `id` referenced by the
140
+ `aria-labelledby` of the `FormFieldGroup`. Screen readers announce the group label when focusing
141
+ controls in the group.
140
142
 
141
143
  `FormFieldGroup` supports the same props of `FormField`:
142
144
 
143
145
  - `error`: `"caution" | "error"` Defines the error around the whole group of inputs.
144
- - `orientation`: `"horizontal" | "vertical"` Defines the legend placement.
146
+ - `orientation`: `"vertical" | "horizontalStart" | "horizontalEnd"` Defines the group label
147
+ placement.
145
148
  - `isRequired`: `true` Defines if a group like RadioGroup is required.
146
149
 
147
150
  <ExampleCodeBlock code={GroupedInputs} />
@@ -149,7 +152,7 @@ automatically announce the legend's context when focusing on the inputs in the g
149
152
  > **Accessibility Note**: In addition to radio button and checkbox groups, `FormFieldGroup` can be
150
153
  > useful in any situation where the form needs to have multiple sets of identical input fields. For
151
154
  > example, a form with identical fields for a Shipping address and a Billing address. The legend
152
- > provides critical context for screen reader users in these situations.
155
+ > (group label) provides critical context for screen reader users in these situations.
153
156
 
154
157
  ### Custom
155
158
 
@@ -200,21 +203,70 @@ check our
200
203
 
201
204
  ## Accessibility
202
205
 
203
- `FormField` provides essential accessibility features to ensure form inputs are properly labeled and
204
- described for all users, including those using assistive technologies. This section covers both the
205
- technical implementation and best practices for creating accessible forms.
206
+ `FormField` provides essential accessibility features to ensure form inputs have a programmatically
207
+ determinable name, relationships, and instructions. The primary accessibility goal is to ensure
208
+ assistive technology users can identify every field, understand how to complete it, and hear hints,
209
+ errors, and required state when the form input receives focus.
210
+
211
+ ### Minimum Accessible Structure
206
212
 
207
- ### Label Association
213
+ ```tsx
214
+ import {FormField} from '@workday/canvas-kit-react/form-field';
215
+ import {TextInput} from '@workday/canvas-kit-react/text-input';
208
216
 
209
- The `FormField` adds a `for` attribute to the `FormField.Label` (`<label>` element) element that
210
- matches the `id` attribute of the `FormField.Input` which is usually a `input` element. This both
211
- labels the input for screen readers and other assistive technology as well as will focus on the
212
- input when the user clicks on the label. If your form field input component is more complicated, the
213
- `FormField` will also add an `id` to the `FormField.Label` and an `aria-labelledby` to the
214
- `FormField.Input` component. You can then forward the `aria-labelledby` to whatever elements you
215
- need for the proper accessibility.
217
+ <FormField>
218
+ <FormField.Label>Email</FormField.Label>
219
+ <FormField.Field>
220
+ <FormField.Input as={TextInput} />
221
+ <FormField.Hint>We'll never share your email.</FormField.Hint>
222
+ </FormField.Field>
223
+ </FormField>;
224
+ ```
225
+
226
+ Include **`FormField.Hint`** whenever there is hint or error text—the wiring slot for
227
+ `aria-describedby` exists by default (see **Built-in Behaviors**).
228
+
229
+ ### Built-in Behaviors
230
+
231
+ Canvas Kit applies these automatically when you compose `FormField` with its subcomponents. **Do not
232
+ duplicate them** in consuming code.
233
+
234
+ **ARIA and DOM** (_applied by subcomponents_):
235
+
236
+ - `FormField.Label`: `<label>` with `htmlFor` matching the `id` on `FormField.Input` (`input-{id}`).
237
+ Clicking the label moves focus to the input.
238
+ - `FormField.Input`: `id` (`input-{id}`), `aria-labelledby` (`label-{id}`), `aria-describedby`
239
+ (`hint-{id}` when an `id` exists), `required` when `isRequired`, and `aria-invalid="true"` when
240
+ `error="error"`. Caution (`error="caution"`) does **not** set `aria-invalid`.
241
+ - `FormField.Hint`: `id="hint-{id}"` for description association.
242
+ - Generated IDs: `label-{id}`, `input-{id}`, and `hint-{id}` from the `FormField` `id`
243
+ (auto-generated or custom via the `id` prop).
244
+ - `FormFieldGroup`: `role="group"` with `aria-labelledby` referencing `FormFieldGroup.Label`.
245
+ - Composite controls (for example, `MultiSelect`) forward `aria-labelledby` to related sub-elements
246
+ (combobox, listbox) so every part shares the same accessible name.
247
+
248
+ **Implementation note on `aria-describedby`:** `FormField.Input` always sets
249
+ `aria-describedby="hint-{id}"` when an `id` exists, even if `FormField.Hint` is not rendered. Always
250
+ render `FormField.Hint` when there is hint or error text. Omitting `FormField.Hint` leaves a
251
+ dangling `aria-describedby` reference.
252
+
253
+ **Keyboard** (_standard form control behavior_):
254
+
255
+ - <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> move focus to and from the input (native tab
256
+ order)
257
+ - Clicking `FormField.Label` moves focus to the associated input (native `<label>` behavior)
258
+ - Checkbox, radio, and composite controls follow the wrapped input component's own keyboard patterns
216
259
 
217
- For example, the DOM will look something like this:
260
+ **Screen reader expectations** (_when built-in behaviors are used as intended_):
261
+
262
+ - On focus, assistive technology announces the field label and, when applicable: required state,
263
+ invalid state (`error="error"`), and hint or error text via `aria-describedby`
264
+ - The Caution state is visual only —`aria-invalid` is **not** set for `error="caution"`
265
+ - In a `FormFieldGroup`, the group label is associated via `aria-labelledby`; focusing a control in
266
+ the group includes the group name in context
267
+ - Disabled inputs are skipped in the tab order and may be announced as unavailable
268
+
269
+ For a simple field, the DOM looks like:
218
270
 
219
271
  ```html
220
272
  <div>
@@ -223,38 +275,115 @@ For example, the DOM will look something like this:
223
275
  </div>
224
276
  ```
225
277
 
226
- Some components, like `MultiSelect`, have an additional `role=listbox` element that also needs to
227
- link to the `label` element. The resulting DOM will look something like:
278
+ Some composite controls such as `MultiSelect`, have additional sub-elements that also need to be
279
+ linked to the `FormField.Label`. In the example below, the FormField.Label is applied to both the
280
+ `input[role=combobox]` element and the `ul[role=listbox]` element so that screen reader knows that
281
+ the label for both is the "States you've lived in".
228
282
 
229
283
  ```html
230
284
  <div>
231
285
  <label id="label-abc" for="input-abc">States you've lived in</label>
232
- <input id="input-abc" aria-labelledby="label-abc" role="combobox" ... />
233
- <ul role="listbox" aria-labelledby="label-abc">
234
- <li>Texas</li>
235
- <li>California</li>
286
+ <input
287
+ id="input-abc"
288
+ aria-labelledby="label-abc"
289
+ role="combobox"
290
+ aria-expanded="false"
291
+ aria-autocomplete="list"
292
+ aria-controls="listbox-abc"
293
+ type="text"
294
+ />
295
+ <ul id="listbox-abc" role="listbox" aria-labelledby="label-abc">
296
+ <li role="option">Texas</li>
297
+ <li role="option">California</li>
236
298
  </ul>
237
299
  </div>
238
300
  ```
239
301
 
240
- The `MultiSelect` component gets the `aria-labelledby` from the `FormField.Input` and forwards it to
241
- both the `input[role=combobox]` element and the `ul[role=listbox]` element so the screen reader
242
- knows the label for both is "States you've lived in".
302
+ For a field in an error state, the error text is referenced using `aria-describedby`. The rendered
303
+ markup looks like:
243
304
 
244
- ### Label Text Best Practices
305
+ ```html
306
+ <div>
307
+ <label id="label-abc" for="input-abc">Password</label>
308
+ <input
309
+ id="input-abc"
310
+ aria-labelledby="label-abc"
311
+ aria-describedby="hint-abc"
312
+ required
313
+ aria-invalid="true"
314
+ />
315
+ <p id="hint-abc">Error: Must contain a number and a capital letter</p>
316
+ </div>
317
+ ```
245
318
 
246
- - **Be Clear and Concise**: Labels should clearly describe the purpose of the input field.
247
- - **Use Visible Labels Instead of Only Placeholders**: Always provide a persistent and accessible
248
- label with `FormField.Label`. Do not rely solely on placeholder text, as it can disappear while
249
- typing and may not be accessible to assistive technologies. Use the `isHidden` prop on
250
- `FormField.Label` if a hidden label is required for visual design.
319
+ For grouped checkbox controls, compose a `FormFieldGroup` with `FormFieldGroup.Label` and
320
+ `FormFieldGroup.Input`. The rendered markup looks like:
251
321
 
252
- ### Screen Reader Experience
322
+ ```html
323
+ <div role="group" aria-labelledby="label-abc">
324
+ <div id="label-abc">Pizza toppings:</div>
325
+ <div>
326
+ <input type="checkbox" id="checkbox-1" />
327
+ <label for="checkbox-1">Pepperoni</label>
328
+ </div>
329
+ <div>
330
+ <input type="checkbox" id="checkbox-2" />
331
+ <label for="checkbox-2">Mushrooms</label>
332
+ </div>
333
+ <div>
334
+ <input type="checkbox" id="checkbox-3" />
335
+ <label for="checkbox-3">Pineapple</label>
336
+ </div>
337
+ </div>
338
+ ```
253
339
 
254
- - The label is announced when the input receives focus.
255
- - Required, disabled, and invalid statuses are announced automatically.
256
- - Help text and error messages are announced automatically when focused.
257
- - For grouped inputs, the group label (`legend`) is announced automatically when focused.
340
+ ### Accessibility Requirements
341
+
342
+ Required in application code for accessible form fields. Rows marked _(conditional)_ apply only when
343
+ the situation matches—otherwise omit.
344
+
345
+ **If no design spec is provided:** use a visible `FormField.Label`, wrap the input with
346
+ `FormField.Input`, omit `isHidden`, and omit a custom `id` unless testing or composition requires
347
+ it.
348
+
349
+ | Requirement | How to satisfy |
350
+ | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
351
+ | Visible label | **`FormField.Label`** with concise, meaningful text |
352
+ | Input wiring | **`FormField.Input as={...}`** wrapping every Canvas Kit input (`TextInput`, `Select`, `TextArea`, etc.) |
353
+ | Hint or error text _(conditional)_ | **`FormField.Hint`** when there is help or validation copy—required because `aria-describedby` is wired by default (see **Built-in Behaviors**) |
354
+ | Error state _(conditional)_ | `error="error"` on **`FormField`** and **`FormField.Hint`** prefixed with "Error:" so users who cannot perceive color can distinguish errors from hints |
355
+ | Caution state _(conditional)_ | `error="caution"` on **`FormField`** with descriptive **`FormField.Hint`** (no `aria-invalid`) |
356
+ | Required field _(conditional)_ | `isRequired` on **`FormField`**; consider a form-level note that asterisk (\*) marks required fields |
357
+ | Group label _(conditional)_ | **`FormFieldGroup`** + **`FormFieldGroup.Label`** for radio buttons, checkbox groups, or related inputs. Alternatively, **`FormField as="fieldset"`** with **`FormField.Label as="legend"`** |
358
+ | Hidden label _(conditional)_ | **`FormField.Label isHidden`** only when an equally clear alternative exists (for example, a universally recognizable search icon) |
359
+ | Stable IDs _(conditional)_ | `id` prop on **`FormField`** when multiple fields share a page or predictable `label-`, `input-`, and `hint-` IDs are needed for testing or custom composition |
360
+
361
+ **Summary for code generation:**
362
+
363
+ - **REQUIRED:** visible label, input wiring
364
+ - **CONDITIONAL:** hint/error text, required state, group label, hidden label, stable `id`
365
+
366
+ ### Anti-Patterns
367
+
368
+ Do **not** generate code that does the following (see **Accessibility Requirements** above for what
369
+ to supply instead):
370
+
371
+ - Manually set `id`, `htmlFor`, `aria-labelledby`, `aria-describedby`, `aria-invalid`, or `required`
372
+ on **`FormField.Input`** or **`FormField.Label`** — Canvas Kit subcomponents wire these
373
+ automatically
374
+ - **Unlabeled inputs**: Do not use Canvas Kit inputs without `FormField` and `FormField.Label`.
375
+ - **Placeholder-only labels**: Do not rely on `placeholder` instead of `FormField.Label`;
376
+ placeholders disappear while typing and are poor substitutes for labels.
377
+ - **Color-only errors**: Do not set `error="error"` without descriptive text in `FormField.Hint`.
378
+ - **Caution for invalid values**: Do not use `error="caution"` when a value is invalid; use
379
+ `error="error"` so `aria-invalid` is exposed.
380
+ - **Broken ID references**: Do not point `aria-labelledby` or `aria-describedby` at missing or empty
381
+ elements; ensure `FormField.Label` and `FormField.Hint` render meaningful content.
382
+ - **Skipping group labels**: Do not render `RadioGroup` or multiple related checkboxes without
383
+ `FormFieldGroup` and `FormFieldGroup.Label` (or `FormField` with `as="fieldset"` and
384
+ `FormField.Label as="legend"`).
385
+ - **Disabled when read-only fits**: Avoid `disabled` on `FormField.Input` when users only need to
386
+ view data; disabled fields are harder to perceive and are removed from the tab order.
258
387
 
259
388
  ## Component API
260
389
 
@@ -31,7 +31,7 @@ yarn add @workday/canvas-kit-react
31
31
 
32
32
  <ExampleCodeBlock code={Basic} />
33
33
 
34
- `Menu` will automatically focus on the cursor item (first item by default). The `Menu` uses a menu
34
+ `Menu` will automatically move focus to the first menu item when it opens. The `Menu` uses a menu
35
35
  model which composes a list model and a popup model and sets up accessibility features for you.
36
36
 
37
37
  > **Note:** When content exceeds `60vh`, the menu content is clipped and the menu becomes scrollable.
@@ -99,9 +99,9 @@ indicate this dual role.
99
99
 
100
100
  <ExampleCodeBlock code={Nested} />
101
101
 
102
- > **Accessibility Note**: When a menu item has an attached submenu, the `<Menu.Submenu.TargetItem>`
103
- > includes `aria-haspopup="true"` and `aria-expanded={true | false}` properties. These properties
104
- > will alert screen reader users to the available submenu systems.
102
+ > **Accessibility Note**: Canvas Kit applies `aria-haspopup` and `aria-expanded` on
103
+ > **`Menu.Submenu.TargetItem`** automatically. Do not set these manually see
104
+ > [Accessibility](#accessibility).
105
105
 
106
106
  ### Nested Dynamic Items
107
107
 
@@ -115,45 +115,190 @@ submenus.
115
115
 
116
116
  ## Accessibility
117
117
 
118
- Our Menu component is based on the Menu Button pattern on the ARIA Authoring Practices Guide from
119
- the W3C and relies on the roving tabindex technique for managing focus within the opened menu. This
120
- means that the minimum requirements for screen reader support and keyboard navigation are included
121
- in the component.
122
-
123
- [Menu Button Pattern | APG | WAI | W3C](https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/)
124
-
125
- - The `<Menu.Target>` sub-component uses `aria-haspopup="true"` and `aria-expanded={true | false}`
126
- properties. This benefits screen reader users by indicating when a button element has an attached
127
- menu.
128
- - The `<Menu.List>` sub-component uses `role="menu"` and `<Menu.Item>` uses `role="menuitem"` ARIA
129
- roles. These roles allow screen readers to pass through arrow key events to the web application.
130
- - The `<Menu.List>` sub-component includes an `aria-labelledby` ID reference to the `<Menu.Target>`
131
- sub-component. This assigns a label to the menu for context.
132
-
133
- ### Navigation
134
-
135
- - **Enter or Space**: When focused on the menu button, opens the menu and moves focus to the first
136
- menu item. When focused on a menu item, activates the item and closes the menu
137
- - **Escape**: Closes the menu and returns focus to the menu button
138
- - **Up & Down Arrow**: Moves focus up and down the menu items
139
- - **Home & End**: Moves focus to the first or last menu item
140
- - **Right & Left Arrow**: When focused on a menu item with a submenu, opens the submenu and moves
141
- focus to the first item in the submenu or closes the submenu and returns focus to the parent menu
142
- item
143
-
144
- ### Screen Reader Experience
145
-
146
- - The menu button will be announced with its label text followed by the button role, a notification
147
- that it has a popup menu, and the current state of the menu (For example: "Actions, button, menu
148
- popup, collapsed")
149
- - **Opening the Menu:** When the menu button is activated, screen readers will announce the menu
150
- opening, the number of menu items available, and the currently focused item (For example:
151
- "Actions, menu, First Action, menu item, 1 of 4.")
152
- - **Navigating Menu Items:** As focus moves between menu items, screen readers will announce the
153
- item name and its position in the list (For example: "Second Action, menu item, 2 of 4.")
154
- - **Menu Items with Submenus:** When focused on a menu item that has a submenu, screen readers will
155
- announce that it has a submenu and provide the expanded/collapsed state (For example: "More
156
- Actions, menu item, has submenu, collapsed, 3 of 4.")
118
+ `Menu` follows the
119
+ [Menu Button Pattern | APG | WAI | W3C](https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/), which
120
+ has two parts with different accessibility jobs.
121
+
122
+ **Menu button** (`Menu.Target`): a focusable control that opens and closes the menu. It exposes
123
+ popup presence and expanded/collapsed state (`aria-haspopup`, `aria-expanded`), and receives focus
124
+ again when the menu is dismissed.
125
+
126
+ **Menu popup** (`Menu.List` and its items): the floating action list. It uses `role="menu"` /
127
+ `role="menuitem"`, is labeled by the button, and manages focus inside the list with **roving
128
+ tabindex** so users can move between items and activate one.
129
+
130
+ Use **Menu** for action lists opened from a control. Prefer
131
+ [**Select**](https://workday.github.io/canvas-kit/?path=/docs/components-inputs-select--docs) or
132
+ [**Combobox**](https://workday.github.io/canvas-kit/?path=/docs/features-combobox--docs)
133
+ when choosing a value from options (`Menu.Option` / `listbox` patterns are composed there—do not use
134
+ `Menu.Option` alone for a standard menu button). Prefer
135
+ [**Modal**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs) or
136
+ [**Dialog**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-dialog--docs) for
137
+ task dialogs, not menus.
138
+
139
+ ### Minimum Accessible Structure
140
+
141
+ The following matches the [Basic Example](#basic-example): a keyboard-operable **`Menu.Target`**,
142
+ portaled **`Menu.Popper` → `Menu.Card` → `Menu.List`**, and **`Menu.Item`** children. On open, focus
143
+ moves to the first menu item by default.
144
+
145
+ ```tsx
146
+ import {Menu} from '@workday/canvas-kit-react/menu';
147
+
148
+ <Menu>
149
+ <Menu.Target>Open Menu</Menu.Target>
150
+ <Menu.Popper>
151
+ <Menu.Card>
152
+ <Menu.List>
153
+ <Menu.Item>First Item</Menu.Item>
154
+ <Menu.Item>Second Item</Menu.Item>
155
+ </Menu.List>
156
+ </Menu.Card>
157
+ </Menu.Popper>
158
+ </Menu>;
159
+ ```
160
+
161
+ Provide a clearly named **`Menu.Target`** (visible text, or an icon-only control with
162
+ **`Tooltip`**, or a translated **`aria-label`** if you are not using **`Tooltip`**).
163
+ Use **`aria-disabled`** on items that should stay in the keyboard sequence but
164
+ not activate — do not use the native `disabled` attribute for disabled menu items.
165
+
166
+ ### Built-in Behaviors
167
+
168
+ Canvas Kit applies these automatically via `useMenuModel` (list + popup) and Menu subcomponents.
169
+ **Do not duplicate them** in consuming code.
170
+
171
+ **Popup behaviors** (_composed on the default model_):
172
+
173
+ - `useAlwaysCloseOnOutsideClick` — pointer interaction outside closes the menu
174
+ - `useCloseOnEscape` — <kbd>Escape</kbd> closes the menu
175
+ - `useReturnFocus` (_on `Menu.List`_) — returns focus to **`Menu.Target`** (or configured return
176
+ target) when the menu closes
177
+ - `useFocusRedirect` (_on `Menu.List`_) — <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> from
178
+ inside the menu closes it and moves focus to the next or previous focusable element on the page
179
+ (not a focus trap)
180
+
181
+ **ARIA and DOM** (_applied by hooks/subcomponents_):
182
+
183
+ - **`Menu.Target`**: shared model `id`, `aria-haspopup="true"`,
184
+ `aria-expanded={visibility === 'visible'}`; <kbd>ArrowDown</kbd> / <kbd>ArrowUp</kbd> also open
185
+ the menu
186
+ - **`Menu.List`**: `role="menu"`, `aria-labelledby` referencing the target `id`,
187
+ `aria-orientation` from the model
188
+ - **`Menu.Item`**: `role="menuitem"`, roving `tabIndex` (`0` on the focused item, `-1` on others);
189
+ in default `mode="single"`, activating an item selects it and closes the menu (and any open parent
190
+ menus)
191
+ - **`Menu.Group`**: `role="group"` with `aria-labelledby` referencing **`Menu.Group.Heading`** (or
192
+ the heading created from the `title` prop)
193
+ - **`Menu.Submenu.TargetItem`**: `role="menuitem"`, `aria-haspopup="true"`, `aria-expanded` for the
194
+ submenu
195
+
196
+ **Implementation note on open focus:** Menu does **not** compose `useInitialFocus`. In default
197
+ `mode="single"`, **`useMenuItemFocus`** moves focus to the first menu item when the menu opens. Do
198
+ not generate **`initialFocusRef`** — it is not wired on Menu.
199
+
200
+ **Implementation note on `mode="multiple"`:** `useMenuModel` supports `mode="multiple"`, which keeps
201
+ the menu open and toggles selection in model state. **`Menu.Item`** uses `role="menuitem"`, which
202
+ does not support **`aria-selected`**, so selected state is not exposed to assistive technology. Do
203
+ not generate **`mode="multiple"`** with **`Menu.Item`** for an accessible multi-select UI—use
204
+ [**Select**](https://workday.github.io/canvas-kit/?path=/docs/components-inputs-select--docs),
205
+ **MultiSelect**, or
206
+ [**Combobox**](https://workday.github.io/canvas-kit/?path=/docs/features-combobox--docs) instead.
207
+
208
+ **Keyboard** (_trigger is `Menu.Target`, default `SecondaryButton`; list uses vertical orientation by
209
+ default_):
210
+
211
+ - <kbd>Enter</kbd> / <kbd>Space</kbd> on the trigger opens the menu (button activation)
212
+ - <kbd>ArrowDown</kbd> / <kbd>ArrowUp</kbd> on the trigger also opens the menu
213
+ - On open, focus moves to the first menu item by default
214
+ - <kbd>ArrowDown</kbd> / <kbd>ArrowUp</kbd> moves the roving tabindex between items
215
+ - <kbd>Home</kbd> / <kbd>End</kbd> moves to the first or last item
216
+ - <kbd>Enter</kbd> / <kbd>Space</kbd> on an item activates it and closes the menu (default
217
+ `mode="single"`)
218
+ - <kbd>Escape</kbd> closes the menu and returns focus per `useReturnFocus`
219
+ - <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> closes the menu via `useFocusRedirect`
220
+ - <kbd>ArrowRight</kbd> / <kbd>Enter</kbd> / <kbd>Space</kbd> on **`Menu.Submenu.TargetItem`** opens
221
+ the submenu
222
+ - <kbd>ArrowLeft</kbd> on a submenu item closes it (for LTR languages)
223
+
224
+ **Screen reader expectations** (_when built-in behaviors are used as intended_):
225
+
226
+ - On the trigger: name, button role, menu popup is available, and expanded/collapsed state
227
+ (for example: "Open Menu, button, menu popup, collapsed")
228
+ - On open: menu role (labeled by the trigger), focused item name, menuitem role, and often position
229
+ in set (for example: "Open Menu, menu, First Item, menu item, 1 of 4")
230
+ - While navigating: each focused item’s name and role; group labels when entering a
231
+ **`Menu.Group`**; submenu items announce has-popup / expanded state (for example: "More Actions, menu item, has submenu, collapsed, 3 of 4.")
232
+ - Disabled items with **`aria-disabled`** remain discoverable but not selectable
233
+
234
+ ### Accessibility Requirements
235
+
236
+ Required in application code for an accessible Menu. Hoist **`useMenuModel`** when you need return
237
+ focus overrides or dynamic `items`. Rows marked _(conditional)_ apply only when the situation
238
+ matches—otherwise omit.
239
+
240
+ **If no design spec is provided:** use **`Menu.Target`** + **`Menu.Item`** (not **`Menu.Option`**),
241
+ default `mode="single"`, default open focus on the first menu item, and omit **`returnFocusRef`**,
242
+ **`initialFocusRef`**, and manual ARIA on Target/List/Item.
243
+
244
+ **Focus management — defaults and developer prompts:** Canvas Kit handles open and close focus for
245
+ the default menu button pattern. **State the default to the developer first.** Only set
246
+ **`returnFocusRef`** after the developer (or an explicit design spec) chooses a non-default return
247
+ target. **Do not generate `returnFocusRef` or `initialFocusRef` by default.**
248
+
249
+ | When | Default behavior | Ask the developer before overriding |
250
+ | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
251
+ | Menu **opens** | Focus moves to the **first menu item** by default via item focus hooks—not `useInitialFocus`. Omit **`initialFocusRef`**. | _Which item should receive focus when the menu opens?_ Prefer item order / `data-id` registration; do not assume **`initialFocusRef`** works on Menu. |
252
+ | Menu **closes** | **`useReturnFocus`** moves focus to **`Menu.Target`**. Omit **`returnFocusRef`**. | _Which element should receive focus when the menu closes?_ (Only when return focus should land somewhere other than **`Menu.Target`**.) |
253
+
254
+ **Custom targets** _(conditional)_: Apply when using a custom **`as`** component on
255
+ **`Menu.Target`**. **`Menu.Target`** adds **`onClick`**, keyboard openers, and **`ref`**. Custom
256
+ targets must forward **`ref`** and props to a **keyboard-focusable** element (prefer a native
257
+ **`<button>`** or **`as={SecondaryButton}`**). Wrap the component in **`React.forwardRef`** when it
258
+ does not forward refs by default.
259
+
260
+ | Requirement | How to satisfy |
261
+ | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
262
+ | Keyboard-operable, named trigger | **`Menu.Target`** with visible text, or icon-only with **`Tooltip`** (default `type="label"` sets `aria-label`) or a translated **`aria-label`** without **`Tooltip`**. See **Custom targets** above. |
263
+ | Menu list composition | **`Menu.Popper` → `Menu.Card` → `Menu.List`** with **`Menu.Item`** children (or dynamic `items` + render prop on **`Menu.List`**). |
264
+ | Disabled items _(conditional)_ | **`aria-disabled`** on **`Menu.Item`** so the item stays in the roving tabindex / screen reader sequence. |
265
+ | Stable item ids _(conditional)_ | **`data-id`** on items when using **`onSelect`**, dynamic lists, or nested menus that need stable selection ids. |
266
+ | Complex item content / icons _(conditional)_ | For static API when children are not plain text, set **`data-text`** on **`Menu.Item`** so typeahead/filtering can resolve the item text. Decorative icons alongside **`Menu.Item.Text`** usually need no extra accessible name. |
267
+ | Groups _(conditional)_ | **`Menu.Group`** with **`title`** or **`Menu.Group.Heading`** so `role="group"` is labeled. Group headers are not keyboard-selectable. |
268
+ | Nested menus _(conditional)_ | **`Menu.Submenu`** with **`Menu.Submenu.TargetItem`** plus **`Popper` / `Card` / `List` / `Item`**. Do not manually set submenu `aria-haspopup` / `aria-expanded`. |
269
+ | Context menu trigger _(conditional)_ | **`Menu.TargetContext`** instead of **`Menu.Target`**. OS/browser support for `contextmenu` / Shift+F10 varies—provide an alternate open path for critical actions when required. |
270
+ | Selectable or multi-select options _(conditional)_ | Do **not** use **`Menu.Option`**, `role="listbox"`, or **`mode="multiple"`** with **`Menu.Item`** for a menu button. Compose via [**Select**](https://workday.github.io/canvas-kit/?path=/docs/components-inputs-select--docs), **MultiSelect**, or [**Combobox**](https://workday.github.io/canvas-kit/?path=/docs/features-combobox--docs). |
271
+
272
+ **Summary for code generation:**
273
+
274
+ - **REQUIRED:** keyboard-operable named **`Menu.Target`**, **`Menu.Popper` → `Menu.Card` →
275
+ `Menu.List`**, **`Menu.Item`** (or dynamic list items)
276
+ - **CONDITIONAL:** **`aria-disabled`**, **`data-id`**, **`data-text`**, groups, submenus,
277
+ **`Menu.TargetContext`**, **`returnFocusRef`**, **`forwardRef`** on custom targets
278
+
279
+ ### Anti-Patterns
280
+
281
+ Do **not** generate code that does the following (see **Accessibility Requirements** above for what
282
+ to supply instead):
283
+
284
+ - Manually set `role="menu"`, `role="menuitem"`, `aria-labelledby`, `aria-orientation`,
285
+ `aria-haspopup`, `aria-expanded`, shared `id`, or roving `tabIndex` on **`Menu.Target`**,
286
+ **`Menu.List`**, **`Menu.Item`**, or **`Menu.Submenu.TargetItem`** — Canvas Kit hooks wire these
287
+ - Omit **`Menu.Popper`**, or render **`Menu.Card` / `Menu.List`** outside the Menu composition
288
+ - Use **`Menu.Option`**, `role="listbox"`, or **`mode="multiple"`** with **`Menu.Item`** for
289
+ selectable or multi-select UIs — use **Select**, **MultiSelect**, or **Combobox** instead (see
290
+ **Implementation note on `mode="multiple"`** in Built-in Behaviors)
291
+ - Set **`initialFocusRef`** — Menu does not compose **`useInitialFocus`**, so this prop has no
292
+ effect on open focus (see **Built-in Behaviors**)
293
+ - Set **`returnFocusRef`** by default — state the default return-to-target behavior first and ask
294
+ before overriding
295
+ - Use native **`disabled`** (or deprecated **`isDisabled`**) instead of **`aria-disabled`** when
296
+ the item should remain discoverable
297
+ - Skip **`data-text`** on static items whose accessible/filter text is not plain string children
298
+ - Use a custom **`Menu.Target`** **`as`** component that does not forward **`ref`** to a focusable
299
+ element — use **`React.forwardRef`** or a Canvas Kit button component instead
300
+ - Treat Menu like a **Modal** / **Dialog** (focus trap, `role="dialog"`, inert page) — Menu is a
301
+ menu button popup with roving tabindex inside **`role="menu"`**
157
302
 
158
303
  ## Component API
159
304