@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.
@@ -53,9 +53,10 @@ build popup UIs that are not already covered by Canvas Kit.
53
53
  The Popup has no pre-defined behaviors built in, therefore the `usePopupModel` must always be used
54
54
  to create a new `model`. This `model` is then used by all behavior hooks to apply additional popup
55
55
  behaviors to the compound component group. The following example creates a typical popup around a
56
- target element and adds `useCloseOnOutsideClick`, `useCloseOnEscape`, `useInitialFocus`, and
57
- `useReturnFocus` behaviors. You can read through the [hooks](#hooks) section to learn about all the
58
- popup behaviors. For accessibility, these behaviors should be included most of the time.
56
+ target element and adds `useCloseOnOutsideClick`, `useCloseOnEscape`, `useInitialFocus`,
57
+ `useReturnFocus`, and `useFocusRedirect` behaviors. You can read through the [hooks](#hooks) section
58
+ to learn about all the popup behaviors. For accessibility, these behaviors should be included most
59
+ of the time.
59
60
 
60
61
  <ExampleCodeBlock code={Basic} />
61
62
 
@@ -193,15 +194,300 @@ The Popup component automatically handles right-to-left rendering.
193
194
 
194
195
  ## Accessibility
195
196
 
196
- Popup content is usually portaled to the bottom of the `document.body`, which can affect **reading
197
- order for screen readers** and **keyboard focus order**. For more information about Popup
198
- accessibility, check out our documentation at
199
- [Guides > Accessibility > Inline Popups](https://workday.github.io/canvas-kit/?path=/docs/guides-accessibility-inline-popups--docs).
197
+ Ensure users of assistive technology can discover, name, and operate a popup that is typically
198
+ portaled to the end of `document.body`: the popup has an accessible name that matches its visible
199
+ heading, keyboard users can open and dismiss it predictably, and focus and reading order remain
200
+ usable despite portal placement (see
201
+ [Guides > Accessibility > Inline Popups](https://workday.github.io/canvas-kit/?path=/docs/guides-accessibility-inline-popups--docs)).
202
+ Prefer a semantic component before composing **Popup** directly:
203
+ [**Dialog**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-dialog--docs) for a
204
+ standard non-modal dialog (behaviors and `aria-owns` built in), or
205
+ [**Modal**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs) for
206
+ blocking tasks with focus trapping and assistive sibling hiding (see also the W3C
207
+ [Dialog (Modal) Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/)). Use **Popup** with
208
+ composed hooks when you need a custom popup stack or behavior set that those components do not
209
+ provide.
210
+
211
+ ### Minimum Accessible Structure
212
+
213
+ The following matches the [Basic Example](#basic-example): hoist **`usePopupModel`**, compose the
214
+ non-modal behavior hooks on that model, place **`Popup.CloseIcon`** before **`Popup.Heading`** so
215
+ default open focus lands on the dismiss control first, and use **`Popup.CloseButton`** for actions
216
+ that should also close the popup.
217
+
218
+ ```tsx
219
+ import {DeleteButton} from '@workday/canvas-kit-react/button';
220
+ import {
221
+ Popup,
222
+ useCloseOnEscape,
223
+ useCloseOnOutsideClick,
224
+ useFocusRedirect,
225
+ useInitialFocus,
226
+ usePopupModel,
227
+ useReturnFocus,
228
+ } from '@workday/canvas-kit-react/popup';
229
+
230
+ const Example = () => {
231
+ const model = usePopupModel();
232
+
233
+ useCloseOnOutsideClick(model);
234
+ useCloseOnEscape(model);
235
+ useInitialFocus(model);
236
+ useReturnFocus(model);
237
+ useFocusRedirect(model);
238
+
239
+ return (
240
+ <Popup model={model}>
241
+ <Popup.Target as={DeleteButton}>Delete Item</Popup.Target>
242
+ <Popup.Popper>
243
+ <Popup.Card>
244
+ <Popup.CloseIcon aria-label="Close" />
245
+ <Popup.Heading>Delete Item</Popup.Heading>
246
+ <Popup.Body>
247
+ <p>Are you sure you'd like to delete the item titled 'My Item'?</p>
248
+ </Popup.Body>
249
+ <Popup.ButtonGroup>
250
+ <Popup.CloseButton>Cancel</Popup.CloseButton>
251
+ <Popup.CloseButton as={DeleteButton}>Delete</Popup.CloseButton>
252
+ </Popup.ButtonGroup>
253
+ </Popup.Card>
254
+ </Popup.Popper>
255
+ </Popup>
256
+ );
257
+ };
258
+ ```
259
+
260
+ Include a dismiss control: **`Popup.CloseButton`** with visible text (for example "Cancel" or
261
+ "Close"), and/or **`Popup.CloseIcon`** when the design uses an icon-only dismiss (requires
262
+ **`aria-label`** or **`Tooltip`**). Pass the same **`model`** instance to **`Popup`** and every
263
+ behavior hook so focus and dismiss wiring share one stack.
264
+
265
+ ### Built-in Behaviors
266
+
267
+ Canvas Kit applies ARIA and DOM wiring automatically via Popup subcomponents when you compose them.
268
+ Behavioral hooks are **not** applied by `usePopupModel` alone—you must call them (as in the Basic
269
+ Example). Once applied, **do not duplicate them** in consuming code.
270
+
271
+ **Popup behaviors** (_compose on the model; recommended for non-modal dialogs_):
272
+
273
+ - `useInitialFocus` — moves focus into the popup when it opens (default: first focusable element in
274
+ DOM order; optional override via `initialFocusRef` on the model)
275
+ - `useReturnFocus` — returns focus to `Popup.Target` (or configured return target) when it closes
276
+ - `useCloseOnEscape` — <kbd>Escape</kbd> closes the popup
277
+ - `useCloseOnOutsideClick` — pointer interaction outside closes the popup
278
+ - `useFocusRedirect` — <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> at the first or last
279
+ focusable element inside the popup closes it and moves focus to the next or previous focusable
280
+ element on the page (non-modal; **not** a focus trap; does **not** change screen reader reading
281
+ order; does **not** provide `aria-owns`)
282
+
283
+ **ARIA and DOM** (_applied by hooks/subcomponents_):
284
+
285
+ - `Popup.Card`: `role="dialog"`, `aria-labelledby` referencing the heading `id` (non-modal by
286
+ default; page content is not hidden with `aria-hidden` unless you compose
287
+ `useAssistiveHideSiblings`)
288
+ - `Popup.Heading`: `id` wired to `Popup.Card`'s `aria-labelledby`
289
+ - `Popup.Popper`: positions and registers the popup with the stack; unlike
290
+ [**Dialog.Popper**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-dialog--docs),
291
+ it does **not** set `aria-owns`
292
+ - `Popup.CloseIcon` / `Popup.CloseButton`: `onClick` that calls `model.events.hide()`
293
+ - `Popup.Target`: `ref` and `onClick` to open and to receive return focus
294
+
295
+ **Implementation note on `aria-owns`:** `useFocusRedirect` does not provide `aria-owns`. When you
296
+ need remapped reading order for portaled content, add it yourself (see **Reading order** in
297
+ Accessibility Requirements) or prefer **Dialog**, which wires `aria-owns` automatically. Support
298
+ varies by browser and screen reader.
299
+
300
+ **Keyboard** (_trigger is `Popup.Target`, default `SecondaryButton`_):
301
+
302
+ - <kbd>Enter</kbd> / <kbd>Space</kbd> on the trigger opens the popup (standard button behavior)
303
+ - On open and close, focus is managed by **`useInitialFocus`** and **`useReturnFocus`** when those
304
+ hooks are composed (application overrides: see **Focus management** in Accessibility Requirements)
305
+ - <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> move focus forward and backward through
306
+ interactive elements inside the popup (standard sequential focus behavior)
307
+ - With **`useFocusRedirect`**, tabbing past the last or before the first focusable element closes
308
+ the popup
309
+ - <kbd>Escape</kbd> closes the popup when **`useCloseOnEscape`** is composed and returns focus per
310
+ `useReturnFocus`
311
+
312
+ **Screen reader expectations** (_when built-in behaviors and recommended hooks are used as
313
+ intended_):
314
+
315
+ - On open, assistive technology should announce the first focused control (often a dismiss control),
316
+ the popup name (`Popup.Heading`), and `dialog` role
317
+ - Background page content remains available to assistive technology unless you compose
318
+ **`useAssistiveHideSiblings`** (prefer
319
+ [**Modal**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs) for
320
+ that pattern)
321
+ - Reading order may still follow document order at the end of `body` unless `aria-owns` remapping is
322
+ added and honored; support varies by browser and screen reader
323
+
324
+ ### Accessibility Requirements
325
+
326
+ Required in application code for an accessible Popup. Always hoist **`usePopupModel`** and pass the
327
+ same instance to **`Popup`** and behavior hooks. Rows marked _(conditional)_ apply only when the
328
+ situation matches—otherwise omit.
329
+
330
+ **If no design spec is provided:** compose the Basic Example hooks (`useCloseOnOutsideClick`,
331
+ `useCloseOnEscape`, `useInitialFocus`, `useReturnFocus`, `useFocusRedirect`); use default focus
332
+ behavior; omit **`initialFocusRef`**, **`returnFocusRef`**, **`aria-describedby`**,
333
+ **`aria-expanded`**, **`aria-haspopup`**, **`useFocusTrap`**, and **`useAssistiveHideSiblings`**.
334
+ Prefer **Dialog** or **Modal** when those components already match the product need.
335
+
336
+ **Focus management — defaults and developer prompts:** When **`useInitialFocus`** /
337
+ **`useReturnFocus`** are composed, Canvas Kit handles open and close focus automatically. **State
338
+ the default to the developer first.** Only set **`initialFocusRef`** or **`returnFocusRef`** after
339
+ the developer (or an explicit design spec) chooses a non-default target. **Do not generate focus
340
+ refs by default.**
341
+
342
+ | When | Default behavior | Ask the developer before overriding |
343
+ | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
344
+ | Popup **opens** | **`useInitialFocus`** moves focus to the **first focusable element** in DOM order inside the popup (often **`Popup.CloseIcon`** or **`Popup.CloseButton`**). Omit **`initialFocusRef`**. | _Which element should receive focus when the popup opens?_ (Only when the default first focusable element is wrong for the design.) Attach **`initialFocusRef`** to that element on **`usePopupModel`**. |
345
+ | Popup **closes** | **`useReturnFocus`** moves focus to **`Popup.Target`**. Omit **`returnFocusRef`**. | _Which element should receive focus when the popup closes?_ (Only when return focus should land somewhere other than **`Popup.Target`**.) |
346
+
347
+ If close **removes the trigger from the DOM**, **`returnFocusRef`** alone is not enough—move focus
348
+ after the UI updates (for example with **`useLayoutEffect`**). See
349
+ [Modal > Return Focus](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs#return-focus).
350
+
351
+ **Custom targets** _(conditional)_: Apply when using a custom **`as`** component on
352
+ **`Popup.Target`**. **`Popup.Target`** adds **`onClick`** and **`ref`**. Custom targets must forward
353
+ both to a **keyboard-focusable** element (prefer a native **`<button>`** or
354
+ **`as={SecondaryButton}`** / another Canvas Kit button). Wrap the component in
355
+ **`React.forwardRef`** when it does not forward refs by default (required if the popup can open
356
+ programmatically before the user clicks the target).
357
+
358
+ **Reading order (`aria-owns`)** _(conditional)_:
359
+
360
+ Popup content is portaled; **`useFocusRedirect`** alone does not fix screen reader reading order.
361
+ When a design needs remapped sequential reading order and you are not using **Dialog**, set an `id`
362
+ on the stack element and point a sibling element's **`aria-owns`** at that `id` (see the
363
+ [Focus Redirect](#focus-redirect) example). Prefer **Dialog** when that pattern is the product
364
+ default—Dialog wires **`aria-owns`** for you.
365
+
366
+ **Modal-like focus trapping** _(conditional)_:
367
+
368
+ Prefer [**Modal**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs)
369
+ for blocking tasks. If you must compose trapping on **Popup**, use **`useFocusTrap`** with
370
+ **`useAssistiveHideSiblings`** (and typically **omit** **`useFocusRedirect`**). Focus trapping does
371
+ not stop mouse or virtual-cursor escape by itself.
372
+
373
+ **Open focus below the heading** _(conditional; see supplementary copy row below)_:
374
+
375
+ Button-focus variant (matches [Initial Focus](#initial-focus)): when open focus lands on a primary
376
+ action below the heading, wire **`aria-describedby`** to the supplementary copy. For the form-field
377
+ variant (focus an input), see
378
+ [Dialog](https://workday.github.io/canvas-kit/?path=/docs/components-popups-dialog--docs#accessibility-requirements)
379
+ or
380
+ [Modal](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs#accessibility-requirements)
381
+ **Open focus below the heading**.
382
+
383
+ ```tsx
384
+ import React from 'react';
385
+
386
+ import {PrimaryButton} from '@workday/canvas-kit-react/button';
387
+ import {useUniqueId} from '@workday/canvas-kit-react/common';
388
+ import {
389
+ Popup,
390
+ useCloseOnEscape,
391
+ useCloseOnOutsideClick,
392
+ useFocusRedirect,
393
+ useInitialFocus,
394
+ usePopupModel,
395
+ useReturnFocus,
396
+ } from '@workday/canvas-kit-react/popup';
397
+
398
+ const Example = () => {
399
+ const messageId = useUniqueId();
400
+ const initialFocusRef = React.useRef(null);
401
+ const model = usePopupModel({initialFocusRef});
402
+
403
+ useCloseOnOutsideClick(model);
404
+ useCloseOnEscape(model);
405
+ useInitialFocus(model);
406
+ useReturnFocus(model);
407
+ useFocusRedirect(model);
408
+
409
+ return (
410
+ <Popup model={model}>
411
+ <Popup.Target>Open</Popup.Target>
412
+ <Popup.Popper>
413
+ <Popup.Card aria-describedby={messageId}>
414
+ <Popup.Heading>Confirmation</Popup.Heading>
415
+ <Popup.Body>
416
+ <p id={messageId}>Your message has been sent!</p>
417
+ </Popup.Body>
418
+ <Popup.ButtonGroup>
419
+ <Popup.CloseButton as={PrimaryButton} ref={initialFocusRef}>
420
+ OK
421
+ </Popup.CloseButton>
422
+ </Popup.ButtonGroup>
423
+ </Popup.Card>
424
+ </Popup.Popper>
425
+ </Popup>
426
+ );
427
+ };
428
+ ```
200
429
 
201
- - For non-modal dialogs with `aria-owns` built-in to improve reading order for screen readers (that
202
- support it), check out the [**Dialog**](/components/popups/dialog/) component.
203
- - For modal dialogs with built-in overlays and focus traps, check out the
204
- [**Modal**](/components/popups/modal/) component.
430
+ When open focus lands on **`Popup.Heading`** itself, add **`tabIndex={-1}`** so the heading can
431
+ receive programmatic focus.
432
+
433
+ | Requirement | How to satisfy |
434
+ | ------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
435
+ | Shared model + behavior hooks | Hoist **`usePopupModel`**, pass **`model={model}`** to **`Popup`**, and compose at least the Basic Example hooks for non-modal dialogs (`useCloseOnOutsideClick`, `useCloseOnEscape`, `useInitialFocus`, `useReturnFocus`, `useFocusRedirect`) unless a design deliberately omits one. |
436
+ | Accessible popup name | Use **`Popup.Heading`** so `aria-labelledby` on `Popup.Card` references a visible title. Do not omit the heading: **`Popup.Card` always sets `aria-labelledby`**, and an `aria-label` fallback is unreliable when that ID does not exist. |
437
+ | Dismiss control | Provide a way to close the popup: **`Popup.CloseButton`** with visible text (no extra **`aria-label`** needed), and/or **`Popup.CloseIcon`** for icon-only dismiss (requires **`Tooltip`** or translated **`aria-label`**). |
438
+ | Keyboard-operable trigger | See **Custom targets** above. |
439
+ | Supplementary copy when overriding open focus _(conditional)_ | When **`initialFocusRef`** places open focus **below** **`Popup.Heading`**, assign a unique `id` to supplementary text and pass **`aria-describedby`** on **`Popup.Card`**. See **Open focus below the heading** above and [Initial Focus](#initial-focus). |
440
+ | Reading order remapping _(conditional)_ | See **Reading order (`aria-owns`)** above, or use **Dialog**. |
441
+ | Focus trapping / hide siblings _(conditional)_ | Prefer **Modal**. If composing on **Popup**, see **Modal-like focus trapping** above. |
442
+ | Open/closed state on the trigger _(conditional)_ | See **Wiring aria-expanded** below. **Default:** omit **`aria-expanded`** and **`aria-haspopup`**. |
443
+
444
+ **Summary for code generation:**
445
+
446
+ - **REQUIRED:** shared `usePopupModel`, non-modal behavior hooks (unless design omits), accessible
447
+ name, dismiss control, keyboard-operable trigger
448
+ - **CONDITIONAL:** **`initialFocusRef`**, **`returnFocusRef`**, **`aria-describedby`**,
449
+ **`tabIndex={-1}`** on heading focus, **`aria-owns`**, **`useFocusTrap`** /
450
+ **`useAssistiveHideSiblings`**, **`aria-expanded`** / **`aria-haspopup`**, **`forwardRef`** on
451
+ custom **`Popup.Target`**
452
+
453
+ **Wiring aria-expanded** _(conditional)_:
454
+
455
+ The **`aria-expanded`** pattern is **uncommon** for dialog-like Popups—omit **`aria-expanded`** and
456
+ **`aria-haspopup`** unless a review deliberately keeps open focus on the trigger (for example
457
+ **`initialFocusRef`** on the trigger per design spec). When required, on **`Popup.Target`** set
458
+ **`aria-expanded={model.state.visibility !== 'hidden'}`** and **`aria-haspopup="dialog"`**. See
459
+ **Focus management** and the open/closed-state row above.
460
+
461
+ ### Anti-Patterns
462
+
463
+ Do **not** generate code that does the following (see **Accessibility Requirements** above for what
464
+ to supply instead):
465
+
466
+ - Manually set `role="dialog"`, `aria-labelledby`, or the heading `id` on **`Popup.Card`** or
467
+ **`Popup.Heading`** — Canvas Kit hooks wire these
468
+ - Call behavior hooks on a **different** model instance than the one passed to **`Popup`**, or omit
469
+ **`model={model}`** after composing hooks outside the container
470
+ - Assume **`usePopupModel`** alone provides focus, escape, outside-click, or redirect behaviors —
471
+ compose the hooks (or use **Dialog** / **Modal**)
472
+ - Omit **`Popup.Popper`**, render **`Popup.Card`** outside it, or add a custom portal/restructure
473
+ instead of **`Popup` → `Popup.Popper` → `Popup.Card`** without using **`usePopupStack`**
474
+ - Use **`open`** / **`onClose`** props on **`Popup`** — Popup has no controlled visibility props;
475
+ use **`usePopupModel`** and **`model.events.show()`** / **`model.events.hide()`**
476
+ - Reach for **Popup** + **`useFocusTrap`** / **`useAssistiveHideSiblings`** when **Modal** already
477
+ matches the product need, or for non-modal UX when **Dialog** already matches
478
+ - Set **`initialFocusRef`** or **`returnFocusRef`** by default — state the default focus behavior
479
+ first and ask the developer before overriding (see **Focus management** in Accessibility
480
+ Requirements)
481
+ - Add **`aria-expanded`** / **`aria-haspopup`** on the default dialog-like Popup path, or bind
482
+ **`aria-expanded`** to a static value (see **Wiring aria-expanded** in Accessibility Requirements)
483
+ - Use a custom **`Popup.Target`** **`as`** component that does not forward **`ref`** to a focusable
484
+ element — use **`React.forwardRef`** or a Canvas Kit button component instead
485
+ - Rely on **`returnFocusRef`** alone when close **removes the trigger from the DOM** (see
486
+ [Modal > Return Focus](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs#return-focus))
487
+ - Nest multiple **Popup** instances without deliberate initial focus and return-focus planning
488
+ - Assume **`useFocusRedirect`** fixes screen reader reading order, or that **`aria-owns`** remapping
489
+ works in all browser and screen reader combinations — test your supported combinations
490
+ - Expect **`Popup.Popper`** to set **`aria-owns`** like **Dialog.Popper** — it does not
205
491
 
206
492
  ## Component API
207
493
 
@@ -1,8 +1,4 @@
1
- import {
2
- ExampleCodeBlock,
3
- Specifications,
4
- SymbolDoc,
5
- } from '@workday/canvas-kit-docs';
1
+ import {ExampleCodeBlock, Specifications, SymbolDoc} from '@workday/canvas-kit-docs';
6
2
  import Basic from './examples/Basic';
7
3
  import Disabled from './examples/Disabled';
8
4
  import Grow from './examples/Grow';
@@ -91,8 +87,8 @@ input component. By default, the orientation will be set to `vertical`.
91
87
 
92
88
  ### Required
93
89
 
94
- Set the `required` prop of the wrapping Form Field to `true` to indicate that the field is required.
95
- Labels for required fields are suffixed by a red asterisk.
90
+ Set the `isRequired` prop of the wrapping Form Field to `true` to indicate that the field is
91
+ required. Labels for required fields are suffixed by a red asterisk.
96
92
 
97
93
  <ExampleCodeBlock code={Required} />
98
94
 
@@ -100,38 +96,146 @@ Labels for required fields are suffixed by a red asterisk.
100
96
 
101
97
  Form Field provides error and caution states for Text Area. Set the `error` prop on Form Field to
102
98
  `"error"` or `"caution"` and use `FormField.Hint` to provide error messages. See
103
- [Form Field's Error documentation](/components/inputs/form-field/#error-states) for
104
- examples and accessibility guidance.
99
+ [Form Field's Error documentation](/components/inputs/form-field/#error-states) for examples and
100
+ accessibility guidance.
105
101
 
106
102
  ## Accessibility
107
103
 
108
- `TextArea` should be used with [Form Field](/components/inputs/form-field/) to
109
- ensure proper labeling, error handling, and help text association. See
110
- [FormField's accessibility documentation](/components/inputs/form-field/#accessibility)
111
- for comprehensive guidance on form accessibility best practices.
104
+ The primary accessibility goal for `TextArea` is to give every user a visible, persistent label and
105
+ clear instructions, and to ensure assistive technology users can identify the multi-line field and
106
+ hear hints, errors, required state, and character-limit information when the control receives focus.
107
+ Use `TextArea` when users need to enter multiple lines or paragraphs of text. For single-line values
108
+ (names, emails, short answers), use [TextInput](/components/inputs/text-input/) instead.
109
+
110
+ ### Minimum Accessible Structure
111
+
112
+ Build on the Basic example: label first, then the input inside `FormField.Field`. This order matches
113
+ the DOM reading sequence and ensures the label's `htmlFor` targets the `<textarea>` before hint text
114
+ follows the control.
115
+
116
+ ```tsx
117
+ import {FormField} from '@workday/canvas-kit-react/form-field';
118
+ import {TextArea} from '@workday/canvas-kit-react/text-area';
119
+
120
+ <FormField>
121
+ <FormField.Label>Leave a Review</FormField.Label>
122
+ <FormField.Field>
123
+ <FormField.Input as={TextArea} />
124
+ <FormField.Hint>Share any additional feedback.</FormField.Hint>
125
+ </FormField.Field>
126
+ </FormField>;
127
+ ```
128
+
129
+ Every `TextArea` requires **`FormField`**, a visible **`FormField.Label`**, and
130
+ **`FormField.Input as={TextArea}`** so the control has a programmatically determinable name,
131
+ relationships, and instructions. See
132
+ [FormField's accessibility documentation](/components/inputs/form-field/#accessibility) for shared
133
+ form-field guidance. Include **`FormField.Hint`** for instructions, validation messages, or
134
+ character counts—`FormField` associates that text with the text area through `aria-describedby`.
135
+
136
+ ### Built-in Behaviors
137
+
138
+ Canvas Kit applies these automatically when you compose `TextArea` with `FormField` subcomponents.
139
+ **Do not duplicate them** in consuming code.
140
+
141
+ **ARIA and DOM** (_applied by subcomponents_):
142
+
143
+ - **`TextArea`**: Renders a native `<textarea>` element. Screen readers identify it as a multi-line
144
+ text input.
145
+ - **`TextArea` `disabled`**: Maps to the native `disabled` attribute; disabled fields are removed
146
+ from the tab order.
147
+ - **User-resizable dimensions**: Defaults to `resize: both` so users can adjust the control for
148
+ visual comfort.
149
+
150
+ **Keyboard** (_standard `TextArea` behavior_):
151
+
152
+ <kbd>Enter</kbd>: Inserts a new line (native `<textarea>` behavior). Do not add custom key handlers that prevent standard text editing.
112
153
 
113
- ### Character Limits
154
+ `TextArea` uses native `<textarea>` keyboard behavior (tab order, label activation, and text-editing
155
+ shortcuts).
114
156
 
115
- When limiting text area length:
157
+ **Screen reader expectations** (_when built-in behaviors are used as intended_):
116
158
 
117
- - Use the `maxLength` attribute to enforce the limit programmatically.
118
- - For longer limits (100+ characters), consider adding character count information to
119
- `FormField.Hint`.
120
- - Avoid announcing character counts after every keystroke, as this disrupts screen reader users.
121
- Check out
122
- [Debouncing an AriaLiveRegion: TextArea with character limit](https://workday.github.io/canvas-kit/?path=/docs/guides-accessibility-aria-live-regions--docs#debouncing-an-arialiveregion-textarea-with-character-limit)
123
- for an example of how to wait for users to stop typing before announcing the character count to
124
- screen readers.
159
+ - On focus, assistive technology announces the field label and, when applicable: required state,
160
+ invalid state (`error="error"`), and hint or error text via `aria-describedby`.
161
+ - The current value or "blank" is announced when the text area receives focus.
162
+ - The Caution state is visual only `aria-invalid` is **not** set for `error="caution"`.
163
+ - Disabled text areas may be announced as unavailable and are skipped in the tab order.
125
164
 
126
- ### Screen Reader Experience
165
+ For rendered label, input, and hint association markup, see the DOM examples in
166
+ [FormField's Built-in Behaviors](/components/inputs/form-field/#built-in-behaviors). `TextArea`
167
+ renders a native `<textarea>` in place of `<input>`.
127
168
 
128
- When properly implemented with `FormField`, screen readers will announce:
169
+ ### Accessibility Requirements
170
+
171
+ Required in application code for an accessible `TextArea`. Rows marked _(conditional)_ apply only
172
+ when the situation matches—otherwise omit.
173
+
174
+ **If no design spec is provided:** use a visible `FormField.Label`, wrap the control with
175
+ `FormField.Input as={TextArea}`, omit `isHidden`, keep default `resize: both`, omit a custom `id`
176
+ unless testing or composition requires it, and omit a `ref` unless programmatic focus is required.
177
+
178
+ **Programmatic focus** _(conditional — omit by default)_:
179
+
180
+ Use a ref when the product needs to move focus to the text area after an action (for example,
181
+ focusing the field after a validation error, or a control that focuses the text area). Do not attach
182
+ a `ref` or call `focus()` unless the design or developer asks for it. See
183
+ [Ref Forwarding](#ref-forwarding) under Usage for a complete Storybook example.
184
+
185
+ ```tsx
186
+ const Example = () => {
187
+ const ref = React.useRef<HTMLTextAreaElement>(null);
188
+
189
+ const handleClick = () => {
190
+ ref.current?.focus();
191
+ };
192
+
193
+ return (
194
+ <>
195
+ <FormField>
196
+ <FormField.Label>Leave a Review</FormField.Label>
197
+ <FormField.Field>
198
+ <FormField.Input as={TextArea} ref={ref} />
199
+ </FormField.Field>
200
+ </FormField>
201
+ <PrimaryButton onClick={handleClick}>Focus Text Area</PrimaryButton>
202
+ </>
203
+ );
204
+ };
205
+ ```
129
206
 
130
- - The label text when the text area receives focus.
131
- - Required, disabled, or read-only status.
132
- - Help text and error messages (via `aria-describedby`).
133
- - The current value or "blank" if empty.
134
- - That it's a multi-line text input field.
207
+ | Requirement | How to satisfy |
208
+ | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
209
+ | Input wiring | **`FormField.Input as={TextArea}`** wrapping every `TextArea` instance. See [FormField accessibility](/components/inputs/form-field/#accessibility) for label, hint, error, and required wiring |
210
+ | Character limit _(conditional)_ | `maxLength` on **`FormField.Input`**, visible count in **`FormField.Hint`**, and debounced **`AriaLiveRegion`**. See [Aria Live Regions guide](?path=/docs/guides-accessibility-aria-live-regions--docs#debouncing-an-arialiveregion-textarea-with-character-limit) |
211
+ | Programmatic focus _(conditional)_ | `ref` on **`FormField.Input`** and call `focus()` when moving focus to the field after an action—omit by default (see **Programmatic focus** above) |
212
+
213
+ **Summary for code generation:**
214
+
215
+ - **REQUIRED:** visible label, `FormField.Input as={TextArea}` wiring
216
+ - **CONDITIONAL:** character limit with live region, programmatic focus via `ref`. See
217
+ [FormField accessibility](/components/inputs/form-field/#accessibility) for shared FormField
218
+ conditionals (hint/error, required, disabled, placeholder, stable `id`).
219
+
220
+ ### Anti-Patterns
221
+
222
+ Do **not** generate code that does the following (see **Accessibility Requirements** above for what
223
+ to supply instead):
224
+
225
+ - **Unlabeled text areas**: Do not use `TextArea` without `FormField` and `FormField.Label` (see
226
+ **Minimum accessible structure**). For shared FormField anti-patterns (manual ARIA wiring,
227
+ placeholder-only labels, color-only errors, broken ID references), see
228
+ [FormField Anti-Patterns](/components/inputs/form-field/#anti-patterns).
229
+ - **Single-line input for multi-line content**: Do not use
230
+ [TextInput](/components/inputs/text-input/) when the user needs to enter paragraphs or multi-line
231
+ text; use `TextArea` instead.
232
+ - **Per-keystroke character announcements**: Do not announce character counts after every keystroke;
233
+ debounce `AriaLiveRegion` updates so screen reader users are not interrupted while typing.
234
+ - **Disabling resize unnecessarily**: Do not set `resize` to `none` unless there is a strong design
235
+ or layout requirement; users lose a visual comfort affordance that supports low-vision and motor
236
+ needs.
237
+ - **Programmatic focus by default**: Do not attach a `ref` or call `focus()` on the text area unless
238
+ the design or developer asks for it (see **Programmatic focus** in Accessibility Requirements).
135
239
 
136
240
  ## Component API
137
241