@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.
- package/dist/es6/lib/stackblitzFiles/packageJSONFile.js +5 -5
- package/dist/es6/lib/stackblitzFiles/packageJSONFile.ts +5 -5
- package/dist/mdx/preview-react/status-indicator/StatusIndicator.mdx +119 -6
- package/dist/mdx/preview-react/status-indicator/examples/Icon.tsx +2 -2
- package/dist/mdx/react/dialog/Dialog.mdx +235 -48
- package/dist/mdx/react/form-field/FormField.mdx +174 -45
- package/dist/mdx/react/menu/Menu.mdx +188 -43
- package/dist/mdx/react/modal/Modal.mdx +230 -52
- package/dist/mdx/react/popup/Popup.mdx +297 -11
- package/dist/mdx/react/text-area/TextArea.mdx +134 -30
- package/dist/mdx/react/text-input/TextInput.mdx +193 -34
- package/package.json +6 -6
|
@@ -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
|
|
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
|
|
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 `
|
|
138
|
-
`FormFieldGroup.Label` renders a `
|
|
139
|
-
|
|
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`: `"
|
|
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
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
|
|
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
|
|
227
|
-
|
|
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
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
-
|
|
241
|
-
|
|
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
|
-
|
|
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
|
-
|
|
247
|
-
|
|
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
|
-
|
|
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
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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
|
|
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**:
|
|
103
|
-
>
|
|
104
|
-
>
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
|