@workday/canvas-kit-docs 16.0.9 → 16.0.11

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.
@@ -18,11 +18,11 @@ export const packageJSONFile = `{
18
18
  "@emotion/react": "11.11.4",
19
19
  "@types/react": "18.2.60",
20
20
  "@types/react-dom": "18.2.19",
21
- "@workday/canvas-kit-labs-react": "16.0.9",
22
- "@workday/canvas-kit-preview-react": "16.0.9",
23
- "@workday/canvas-kit-react": "16.0.9",
24
- "@workday/canvas-kit-react-fonts": "^16.0.9",
25
- "@workday/canvas-kit-styling": "16.0.9",
21
+ "@workday/canvas-kit-labs-react": "16.0.11",
22
+ "@workday/canvas-kit-preview-react": "16.0.11",
23
+ "@workday/canvas-kit-react": "16.0.11",
24
+ "@workday/canvas-kit-react-fonts": "^16.0.11",
25
+ "@workday/canvas-kit-styling": "16.0.11",
26
26
  "@workday/canvas-system-icons-web": "^5.0.3",
27
27
  "@workday/canvas-expressive-icons-web": "1.0.1",
28
28
  "@workday/canvas-tokens-web": "4.4.0-beta.11"
@@ -19,11 +19,11 @@ export const packageJSONFile = `{
19
19
  "@emotion/react": "11.11.4",
20
20
  "@types/react": "18.2.60",
21
21
  "@types/react-dom": "18.2.19",
22
- "@workday/canvas-kit-labs-react": "16.0.9",
23
- "@workday/canvas-kit-preview-react": "16.0.9",
24
- "@workday/canvas-kit-react": "16.0.9",
25
- "@workday/canvas-kit-react-fonts": "^16.0.9",
26
- "@workday/canvas-kit-styling": "16.0.9",
22
+ "@workday/canvas-kit-labs-react": "16.0.11",
23
+ "@workday/canvas-kit-preview-react": "16.0.11",
24
+ "@workday/canvas-kit-react": "16.0.11",
25
+ "@workday/canvas-kit-react-fonts": "^16.0.11",
26
+ "@workday/canvas-kit-styling": "16.0.11",
27
27
  "@workday/canvas-system-icons-web": "^5.0.3",
28
28
  "@workday/canvas-expressive-icons-web": "1.0.1",
29
29
  "@workday/canvas-tokens-web": "4.4.0-beta.11"
@@ -1,7 +1,8 @@
1
1
  import {ExampleCodeBlock, SymbolDoc} from '@workday/canvas-kit-docs';
2
+ import Alt from './examples/Alt';
2
3
  import Basic from './examples/Basic';
3
4
  import Focus from './examples/Focus';
4
- import Alt from './examples/Alt';
5
+
5
6
 
6
7
  # Canvas Kit Dialog
7
8
 
@@ -18,16 +19,17 @@ yarn add @workday/canvas-kit-react
18
19
 
19
20
  ### Basic Example
20
21
 
21
- Unlike Modal, Dialog **does not** render the rest of the page inert while it is active. Dialog
22
- should be used in situations where the task does not require immediate attention.
22
+ The following example shows a typical Dialog with heading, close control, and form content.
23
23
 
24
24
  <ExampleCodeBlock code={Basic} />
25
25
 
26
26
  ### Focus Redirect
27
27
 
28
- Dialog **does not** trap keyboard focus like the Modal component does. Instead, it allows focus to
29
- move freely in and out of the dialog, supporting more flexible navigation. The following example
30
- shows how Dialog manages focus in and out of the component.
28
+ Dialog **does not** trap keyboard focus like the Modal component does. The default `useDialogModel`
29
+ composes `useFocusRedirect`: <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> at the last or first
30
+ focusable element inside the dialog closes it and moves focus to the next or previous focusable
31
+ element on the page. Dialog is non-modal and is **not** a focus trap; it does **not** change screen
32
+ reader reading order. The following example shows how Dialog manages focus at those edges.
31
33
 
32
34
  <ExampleCodeBlock code={Focus} />
33
35
 
@@ -36,53 +38,238 @@ shows how Dialog manages focus in and out of the component.
36
38
 
37
39
  ### Alt Example
38
40
 
39
- The `alt` variant is designed for use on alternative page backgrounds (`system.color.bg.alt.default`). Use this variant to maintain proper visual hierarchy when placing components on colored backgrounds. While the default variant should be used on `system.color.bg.default` backgrounds, the `alt` variant ensures the component remains visually elevated on `system.color.bg.alt.default` backgrounds.
41
+ The `alt` variant is designed for use on alternative page backgrounds
42
+ (`system.color.bg.alt.default`). Use this variant to maintain proper visual hierarchy when placing
43
+ components on colored backgrounds. While the default variant should be used on
44
+ `system.color.bg.default` backgrounds, the `alt` variant ensures the component remains visually
45
+ elevated on `system.color.bg.alt.default` backgrounds.
40
46
 
41
47
  <ExampleCodeBlock code={Alt} />
42
48
 
43
49
  ## Accessibility
44
50
 
45
- `Dialog` composes the popup stack with `useInitialFocus`, `useReturnFocus`, `useCloseOnEscape`,
46
- `useCloseOnOutsideClick`, and `useFocusRedirect`. The card container includes an ARIA
47
- **`role="dialog"`** that is **non-modal**: the rest of the page stays available. The card also
48
- includes an **`aria-labelledby`** attribute referencing the `id` on `Dialog.Heading`, so the dialog
49
- has an accessible name that matches the visible heading.
50
-
51
- The Dialog component includes a `<div>` element (sibling to the `Dialog.Target`) with `aria-owns`
52
- pointing to the `Dialog.Card`. This remaps the hierarchy of the accessibility tree to improve
53
- sequential reading order in supported browsers. For more information, see
54
- [Guides > Accessibility > Inline Popups](https://workday.github.io/canvas-kit/?path=/docs/guides-accessibility-inline-popups--docs).
55
-
56
- [Dialog Pattern | APG | WAI | W3C](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/)
57
-
58
- - Prefer **`Dialog.Heading`** so the dialog is properly labelled; avoid leaving a dialog without an
59
- accessible name.
60
- - Ensure icon-only controls such as **`Dialog.CloseIcon`** include an accessible name. Prefer the
61
- `Tooltip` component to provide a visible label, or a translated `aria-label` string is acceptable.
62
-
63
- ### Navigation
64
-
65
- - **Enter** / **Space**: Open the dialog (standard button behavior on the trigger). When it opens,
66
- focus moves to the **first focusable element** inside the dialog in DOM order—often the close
67
- control—or to the element referenced by **`initialFocusRef`** on the dialog model when set.
68
- - **Tab** / **Shift + Tab**: Move through focusable elements inside the dialog; leaving the first or
69
- last focusable element **closes** the dialog and moves focus to the next or previous focusable
70
- element on the page (non-modal focus redirect behavior).
71
- - **Escape**: Closes the dialog and returns focus to the `Dialog.Target` (or configured return
72
- target).
73
-
74
- ### Screen Reader Experience
75
-
76
- - **When the dialog opens:** Screen readers should announce the name and role of the first focused
77
- control (often the close button), the dialog's name (`Dialog.Heading`) and role.
78
- - **Reading order:** The dialog contents should be read in the same order as it appears on screen
79
- for browsers and screen readers that support `aria-owns`. Results vary, so always test with your
80
- supported browsers and screen reader combinations.
81
- - **Expanded or collapsed state:** The `Dialog.Target` does not include an expanded or collapsed
82
- state by default, but it can be added if the interaction design isn't using an initial focus for
83
- the Dialog. See
84
- [Guides > Accessibility > Inline Popups](https://workday.github.io/canvas-kit/?path=/docs/guides-accessibility-inline-popups--docs)
85
- for more information.
51
+ Ensure users of assistive technology can discover, name, and operate a **non-modal** dialog: the
52
+ rest of the page stays available (no inert background), the dialog has an accessible name that
53
+ matches its visible heading, keyboard users can open and dismiss it predictably, and screen reader
54
+ reading order is improved where `aria-owns` is supported (see
55
+ [Guides > Accessibility > Inline Popups](https://workday.github.io/canvas-kit/?path=/docs/guides-accessibility-inline-popups--docs)).
56
+ For blocking tasks, use
57
+ [**Modal**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs) instead.
58
+ Prefer **Dialog** for the standard non-modal dialog; use
59
+ [**Popup**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-popup--docs) with
60
+ composed hooks when you need a custom popup stack or behavior (for example omitting
61
+ **`useInitialFocus`**). The W3C
62
+ [Dialog (Modal) Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/) applies to
63
+ [**Modal**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs); Dialog
64
+ is intentionally non-modal.
65
+
66
+ ### Minimum Accessible Structure
67
+
68
+ The following matches the [Basic Example](#basic-example) layout: **`Dialog.CloseIcon`** before
69
+ **`Dialog.Heading`** so open focus lands on the dismiss control first; primary actions use
70
+ **`Dialog.CloseButton`** (which closes the dialog on activate).
71
+
72
+ ```tsx
73
+ import {PrimaryButton} from '@workday/canvas-kit-react/button';
74
+ import {Dialog} from '@workday/canvas-kit-react/dialog';
75
+ import {FormField} from '@workday/canvas-kit-react/form-field';
76
+ import {TextInput} from '@workday/canvas-kit-react/text-input';
77
+
78
+ <Dialog>
79
+ <Dialog.Target as={PrimaryButton}>Open</Dialog.Target>
80
+ <Dialog.Popper>
81
+ <Dialog.Card>
82
+ <Dialog.CloseIcon aria-label="Close" />
83
+ <Dialog.Heading>Title</Dialog.Heading>
84
+ <Dialog.Body>
85
+ <FormField>
86
+ <FormField.Label>Email</FormField.Label>
87
+ <FormField.Input as={TextInput} />
88
+ </FormField>
89
+ </Dialog.Body>
90
+ <Dialog.ButtonGroup>
91
+ <Dialog.CloseButton>Cancel</Dialog.CloseButton>
92
+ <Dialog.CloseButton as={PrimaryButton}>Submit</Dialog.CloseButton>
93
+ </Dialog.ButtonGroup>
94
+ </Dialog.Card>
95
+ </Dialog.Popper>
96
+ </Dialog>;
97
+ ```
98
+
99
+ Include a dismiss control: **`Dialog.CloseButton`** with visible text (for example "Cancel" or
100
+ "Close"), and/or **`Dialog.CloseIcon`** when the design uses an icon-only dismiss (requires
101
+ **`aria-label`** or **`Tooltip`**). Use **`Dialog.CloseButton`** for actions that should also close
102
+ the dialog (for example "Submit").
103
+
104
+ ### Built-in Behaviors
105
+
106
+ Canvas Kit applies these automatically via `useDialogModel` and Dialog subcomponents. **Do not
107
+ duplicate them** in consuming code.
108
+
109
+ **Popup behaviors** (_composed on the default model_):
110
+
111
+ - `useInitialFocus` — moves focus into the dialog when it opens (default: first focusable element in
112
+ DOM order; optional override via `initialFocusRef` on the model)
113
+ - `useReturnFocus` — returns focus to `Dialog.Target` (or configured return target) when it closes
114
+ - `useCloseOnEscape` — <kbd>Escape</kbd> closes the dialog
115
+ - `useCloseOnOutsideClick` — pointer interaction outside closes the dialog
116
+ - `useFocusRedirect` — <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> at the first or last
117
+ focusable element inside the dialog closes it and moves focus to the next or previous focusable
118
+ element on the page (non-modal; **not** a focus trap; does **not** change screen reader reading
119
+ order)
120
+
121
+ **ARIA and DOM** (_applied by hooks/subcomponents_):
122
+
123
+ - `Dialog.Card`: `role="dialog"`, `aria-labelledby` referencing the heading `id` (non-modal; page
124
+ content is not hidden with `aria-hidden`)
125
+ - `Dialog.Popper`: sibling wrapper rendered when open with `aria-owns` pointing at `Dialog.Card` to
126
+ remap the accessibility tree for sequential reading order in supported browsers
127
+ - `Dialog.Heading`: `id` wired to `Dialog.Card`'s `aria-labelledby`
128
+ - `Dialog.CloseIcon` / `Dialog.CloseButton`: `onClick` that calls `model.events.hide()`
129
+ - `Dialog.Target`: `ref` and `onClick` to open and to receive return focus
130
+
131
+ **Keyboard** (_trigger is `Dialog.Target`, default `SecondaryButton`_):
132
+
133
+ - <kbd>Enter</kbd> / <kbd>Space</kbd> on the trigger opens the dialog (standard button behavior)
134
+ - On open and close, focus is managed by **`useInitialFocus`** and **`useReturnFocus`** (application
135
+ overrides: see **Focus management** in Accessibility Requirements)
136
+ - <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> move focus forward and backward through
137
+ interactive elements inside the dialog (standard sequential focus behavior)
138
+ - <kbd>Escape</kbd> closes the dialog and returns focus per `useReturnFocus`
139
+
140
+ **Screen reader expectations** (_when built-in behaviors are used as intended_):
141
+
142
+ - On open, assistive technology should announce the first focused control (often a dismiss control),
143
+ the dialog name (`Dialog.Heading`), and `dialog` role
144
+ - Background page content remains available to assistive technology—Dialog does **not** apply
145
+ **`aria-hidden`** to siblings or render the rest of the page inert (unlike
146
+ [**Modal**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs))
147
+ - Reading order may follow on-screen order where `aria-owns` is honored; support varies by browser
148
+ and screen reader
149
+
150
+ ### Accessibility Requirements
151
+
152
+ Required in application code for an accessible Dialog. Hoist **`useDialogModel`** when you need to
153
+ configure focus targets. Rows marked _(conditional)_ apply only when the situation matches—otherwise
154
+ omit.
155
+
156
+ **If no design spec is provided:** use default focus behavior; omit **`initialFocusRef`**,
157
+ **`returnFocusRef`**, **`aria-describedby`**, **`aria-expanded`**, and **`aria-haspopup`**.
158
+
159
+ **Focus management — defaults and developer prompts:** Canvas Kit handles open and close focus
160
+ automatically. **State the default to the developer first.** Only set **`initialFocusRef`** or
161
+ **`returnFocusRef`** after the developer (or an explicit design spec) chooses a non-default target.
162
+ **Do not generate focus refs by default.**
163
+
164
+ | When | Default behavior | Ask the developer before overriding |
165
+ | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
166
+ | Dialog **opens** | **`useInitialFocus`** moves focus to the **first focusable element** in DOM order inside the dialog (often **`Dialog.CloseIcon`** or **`Dialog.CloseButton`**). Omit **`initialFocusRef`**. | _Which element should receive focus when the dialog opens?_ (Only when the default first focusable element is wrong for the design.) Attach **`initialFocusRef`** to that element on **`useDialogModel`**. |
167
+ | Dialog **closes** | **`useReturnFocus`** moves focus to **`Dialog.Target`**. Omit **`returnFocusRef`**. | _Which element should receive focus when the dialog closes?_ (Only when return focus should land somewhere other than **`Dialog.Target`**.) |
168
+
169
+ If close **removes the trigger from the DOM**, **`returnFocusRef`** alone is not enough—move focus
170
+ after the UI updates (for example with **`useLayoutEffect`**). See
171
+ [Modal > Return Focus](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs#return-focus).
172
+
173
+ **Custom targets** _(conditional)_: Apply when using a custom **`as`** component on
174
+ **`Dialog.Target`**. **`Dialog.Target`** adds **`onClick`** and **`ref`**. Custom targets must
175
+ forward both to a **keyboard-focusable** element (prefer a native **`<button>`** or
176
+ **`as={SecondaryButton}`** / another Canvas Kit button). Wrap the component in
177
+ **`React.forwardRef`** when it does not forward refs by default (required if the dialog can open
178
+ programmatically before the user clicks the target).
179
+
180
+ | Requirement | How to satisfy |
181
+ | ------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
182
+ | Accessible dialog name | Use **`Dialog.Heading`** so `aria-labelledby` on `Dialog.Card` references a visible title. Do not omit the heading: **`Dialog.Card` always sets `aria-labelledby`**, and an `aria-label` fallback is unreliable when that ID does not exist. |
183
+ | Dismiss control | Provide a way to close the dialog: **`Dialog.CloseButton`** with visible text (no extra **`aria-label`** needed), and/or **`Dialog.CloseIcon`** for icon-only dismiss (requires **`Tooltip`** or translated **`aria-label`**). |
184
+ | Keyboard-operable trigger | See **Custom targets** above. |
185
+ | Supplementary copy when overriding open focus _(conditional)_ | When **`initialFocusRef`** places open focus **below** **`Dialog.Heading`**, assign a unique `id` to supplementary text and pass **`aria-describedby`** on **`Dialog.Card`**. See **Open focus below the heading** below and [Popup > Initial Focus](https://workday.github.io/canvas-kit/?path=/docs/components-popups-popup--docs#initial-focus) (button-focus variant). |
186
+ | Open/closed state on the trigger _(conditional)_ | See **Wiring aria-expanded** below. **Default:** omit **`aria-expanded`** and **`aria-haspopup`**. |
187
+
188
+ **Open focus below the heading** _(conditional; see supplementary copy row above)_:
189
+
190
+ When open focus moves past the heading (for example into a form field), wire **`aria-describedby`**
191
+ so assistive technology still announces the supplementary copy. For focusing a primary action
192
+ instead of an input, see
193
+ [Popup > Initial Focus](https://workday.github.io/canvas-kit/?path=/docs/components-popups-popup--docs#initial-focus).
194
+
195
+ ```tsx
196
+ import React from 'react';
197
+
198
+ import {useUniqueId} from '@workday/canvas-kit-react/common';
199
+ import {Dialog, useDialogModel} from '@workday/canvas-kit-react/dialog';
200
+ import {FormField} from '@workday/canvas-kit-react/form-field';
201
+ import {TextInput} from '@workday/canvas-kit-react/text-input';
202
+
203
+ const Example = () => {
204
+ const descriptionId = useUniqueId();
205
+ const inputRef = React.useRef<HTMLInputElement>(null);
206
+ const model = useDialogModel({initialFocusRef: inputRef});
207
+
208
+ return (
209
+ <Dialog model={model}>
210
+ <Dialog.Target>Open</Dialog.Target>
211
+ <Dialog.Popper>
212
+ <Dialog.Card aria-describedby={descriptionId}>
213
+ <Dialog.CloseIcon aria-label="Close" />
214
+ <Dialog.Heading>Title</Dialog.Heading>
215
+ <Dialog.Body>
216
+ <p id={descriptionId}>Enter your email to continue.</p>
217
+ <FormField>
218
+ <FormField.Label>Email</FormField.Label>
219
+ <FormField.Input as={TextInput} ref={inputRef} />
220
+ </FormField>
221
+ </Dialog.Body>
222
+ <Dialog.CloseButton>Cancel</Dialog.CloseButton>
223
+ </Dialog.Card>
224
+ </Dialog.Popper>
225
+ </Dialog>
226
+ );
227
+ };
228
+ ```
229
+
230
+ **Summary for code generation:**
231
+
232
+ - **REQUIRED:** accessible name, dismiss control, keyboard-operable trigger
233
+ - **CONDITIONAL:** **`initialFocusRef`**, **`returnFocusRef`**, **`aria-describedby`**,
234
+ **`aria-expanded`** / **`aria-haspopup`**, **`forwardRef`** on custom **`Dialog.Target`**
235
+
236
+ **Wiring aria-expanded** _(conditional)_:
237
+
238
+ The **`aria-expanded`** pattern is **uncommon** for Dialog—omit **`aria-expanded`** and
239
+ **`aria-haspopup`** unless a review deliberately keeps open focus on the trigger (for example
240
+ **`initialFocusRef`** on the trigger per design spec). When required, on **`Dialog.Target`** set
241
+ **`aria-expanded={model.state.visibility !== 'hidden'}`** and **`aria-haspopup="dialog"`**. See
242
+ **Focus management** and the open/closed-state row above. If the design should not move focus into
243
+ the dialog on open, use
244
+ [**Popup**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-popup--docs) with
245
+ composed hooks instead of overriding Dialog defaults.
246
+
247
+ ### Anti-Patterns
248
+
249
+ Do **not** generate code that does the following (see **Accessibility Requirements** above for what
250
+ to supply instead):
251
+
252
+ - Manually set `role="dialog"`, `aria-labelledby`, `aria-owns`, or dialog `id` on **`Dialog.Card`**,
253
+ **`Dialog.Popper`**, or **`Dialog.Heading`** — Canvas Kit hooks wire these
254
+ - Omit **`Dialog.Popper`**, render **`Dialog.Card`** outside it, or add a custom portal/restructure
255
+ instead of **`Dialog` → `Dialog.Popper` → `Dialog.Card`**
256
+ - Use **`open`** / **`onClose`** props on **`Dialog`** — Dialog has no controlled visibility props;
257
+ use **`useDialogModel`** and **`model.events.show()`** / **`model.events.hide()`**
258
+ - Add **`useFocusTrap`**, **`aria-modal="true"`**, or **`aria-hidden`** on page siblings expecting
259
+ modal behavior — Dialog is intentionally non-modal
260
+ - Use **Modal** when the task is non-critical or the rest of the page must stay operable
261
+ - Set **`initialFocusRef`** or **`returnFocusRef`** by default — state the default focus behavior
262
+ first and ask the developer before overriding (see **Focus management** in Accessibility
263
+ Requirements)
264
+ - Add **`aria-expanded`** / **`aria-haspopup`** on the default Dialog path, or bind
265
+ **`aria-expanded`** to a static value (see **Wiring aria-expanded** in Accessibility Requirements)
266
+ - Use a custom **`Dialog.Target`** **`as`** component that does not forward **`ref`** to a focusable
267
+ element — use **`React.forwardRef`** or a Canvas Kit button component instead
268
+ - Rely on **`returnFocusRef`** alone when close **removes the trigger from the DOM** (see
269
+ [Modal > Return Focus](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs#return-focus))
270
+ - Nest multiple **`Dialog`** instances without deliberate initial focus and return-focus planning
271
+ - Assume **`useFocusRedirect`** fixes screen reader reading order, or that **`aria-owns`** remapping
272
+ works in all browser and screen reader combinations — test your supported combinations
86
273
 
87
274
  ## Component API
88
275
 
@@ -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