@workday/canvas-kit-mcp 16.0.10 → 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.
- package/dist/apps/dialog.html +300 -144
- package/dist/apps/form-field.html +290 -187
- package/dist/apps/menu.html +233 -121
- package/dist/apps/modal.html +299 -149
- package/dist/apps/popper.html +399 -154
- package/dist/apps/popup.html +399 -154
- package/dist/apps/text-input.html +271 -136
- package/dist/apps/textarea.html +213 -127
- package/dist/cli.js +17 -17
- package/dist/cli.js.map +1 -1
- package/dist/index.js +17 -17
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -18,7 +18,7 @@ import { z } from "zod";
|
|
|
18
18
|
// package.json
|
|
19
19
|
var package_default = {
|
|
20
20
|
name: "@workday/canvas-kit-mcp",
|
|
21
|
-
version: "16.0.
|
|
21
|
+
version: "16.0.11",
|
|
22
22
|
description: "MCP package for Canvas Kit",
|
|
23
23
|
author: "Workday, Inc. (https://www.workday.com)",
|
|
24
24
|
license: "Apache-2.0",
|
|
@@ -333,15 +333,15 @@ var stories_config_default = {
|
|
|
333
333
|
title: "Components/Inputs/Text Input",
|
|
334
334
|
storybookUrl: "https://workday.github.io/canvas-kit/?path=/docs/components-inputs-text-input--docs",
|
|
335
335
|
mdxPath: "modules/react/text-input/stories/TextInput.mdx",
|
|
336
|
-
mdxProse: "# Canvas Kit Text Input\n\nText Inputs allow users to enter words or characters without styling.\n\n[> Workday Design Reference](https://design.workday.com/components/inputs/text-input)\n\n## Installation\n\n```sh\nyarn add @workday/canvas-kit-react\n```\n\n## Usage\n\n### Basic Example\n\nText Input should be used in tandem with [Form Field](/components/inputs/form-field/) to ensure\nproper label association and screen reader support.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const Basic = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <FormField>\n <FormField.Label>Email</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextInput} onChange={handleChange} value={value} />\n </FormField.Field>\n </FormField>\n );\n};\n```\n\n### Disabled\n\nSet the `disabled` prop of the Text Input to prevent users from interacting with it.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const Disabled = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <FormField>\n <FormField.Label>Email</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextInput} disabled onChange={handleChange} value={value} />\n </FormField.Field>\n </FormField>\n );\n};\n```\n\n### Placeholder\n\nSet the `placeholder` prop of the Text Input to display a sample of its expected format or value\nbefore a value has been provided.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const Placeholder = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <FormField>\n <FormField.Label>Email</FormField.Label>\n <FormField.Field>\n <FormField.Input\n as={TextInput}\n onChange={handleChange}\n placeholder=\"user@email.com\"\n value={value}\n />\n </FormField.Field>\n </FormField>\n );\n};\n```\n\n> **Accessibility Note**: Always provide a persistent `FormField.Label` and never rely on\n> placeholder text as the only label for an input. Placeholders can disappear or lack sufficient\n> contrast. Use placeholders only for short format examples (e.g., \"name@example.com\"), and place\n> detailed instructions or guidance in `FormField.Hint` instead of the placeholder.\n\n### Ref Forwarding\n\nText Input supports [ref forwarding](https://reactjs.org/docs/forwarding-refs.html). It will forward\n`ref` to its underlying `<input type=\"text\">` element.\n```tsx\nimport React from 'react';\n\nimport {PrimaryButton} from '@workday/canvas-kit-react/button';\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const RefForwarding = () => {\n const [value, setValue] = React.useState('');\n const ref = React.useRef(null);\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n const handleClick = () => {\n ref.current.focus();\n };\n\n return (\n <>\n <FormField>\n <FormField.Label>Email</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextInput} onChange={handleChange} ref={ref} value={value} />\n </FormField.Field>\n </FormField>\n <PrimaryButton onClick={handleClick}>Focus Text Input</PrimaryButton>\n </>\n );\n};\n```\n\n### Grow\n\nSet the `grow` prop of the wrapping Form Field to `true` to configure the Text Input to expand to\nthe width of its container.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const Grow = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <FormField grow>\n <FormField.Label>Street Address</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextInput} onChange={handleChange} value={value} />\n </FormField.Field>\n </FormField>\n );\n};\n```\n\nThe `grow` prop may also be applied directly to the Text Input if Form Field is not being used.\n\n### Label Position Horizontal\n\nSet the `orientation` prop of the Form Field to designate the position of the label relative to the\ninput component. By default, the orientation will be set to `vertical`.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const LabelPosition = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <FormField orientation=\"horizontalStart\">\n <FormField.Label>Email</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextInput} onChange={handleChange} value={value} />\n <FormField.Hint>Add a valid email</FormField.Hint>\n </FormField.Field>\n </FormField>\n );\n};\n```\n\n### Required\n\nSet the `required` prop of the wrapping Form Field to `true` to indicate that the field is required.\nLabels for required fields are suffixed by a red asterisk.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const Required = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <FormField isRequired={true}>\n <FormField.Label>Email</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextInput} onChange={handleChange} value={value} />\n </FormField.Field>\n </FormField>\n );\n};\n```\n\n### Icons\n\n`InputGroup` is available to add icons to the `TextInput`. Internally, a container `div` element is\nused with relative position styling on the `div` and absolute position styling on the start and end\nicons. `InputGroup.InnerStart` and `InputGroup.InnerEnd` are used to position elements at the start\nand end of the input. \"start\" and \"end\" are used instead of \"left\" and \"right\" to match\n[CSS Logical Properties](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties)\nand will be semantically correct in left-to-right and right-to-left languages.\n\n`InputGroup.InnerStart` and `InputGroup.InnerEnd` subcomponents can handle any child elements, but\nare built for icons. The default width is `40px`, which is perfect for icons. If you need to use\nsomething else, be sure to set the `width` property of `InputGroup.InnerStart` or\n`InputGroup.InnerEnd` to match the intended width of the element. Do not use the `cs` prop or any\nmethod to change width. The `width` prop is used to correctly position other inner elements.\n```tsx\nimport React from 'react';\n\nimport {\n FormField,\n useFormFieldInput,\n useFormFieldModel,\n} from '@workday/canvas-kit-react/form-field';\nimport {SystemIcon} from '@workday/canvas-kit-react/icon';\nimport {InputGroup} from '@workday/canvas-kit-react/text-input';\nimport {mailIcon} from '@workday/canvas-system-icons-web';\n\n/**\n * Using `as={InputGroup}` on `FormField.Input` will break the label associations necessary for accessibility.\n * In this example, we've rendered `FormField.Field` as `InputGroup` and then hoisted the `id` of the input from the FormField model.\n * This allows us to set the `id` of the `InputGroup.Input` correctly for proper label association.\n */\n\nexport const Icons = () => {\n const model = useFormFieldModel();\n const {id: formFieldInputId} = useFormFieldInput(model);\n\n return (\n <FormField model={model}>\n <FormField.Label>Email</FormField.Label>\n <FormField.Field as={InputGroup}>\n <InputGroup.InnerStart>\n <SystemIcon icon={mailIcon} />\n </InputGroup.InnerStart>\n <InputGroup.Input id={formFieldInputId} autoComplete=\"email\" />\n <InputGroup.InnerEnd>\n <InputGroup.ClearButton />\n </InputGroup.InnerEnd>\n </FormField.Field>\n </FormField>\n );\n};\n```\n\n> **Accessibility Note**: In this example, the mail icon is decorative and hidden from screen\n> readers. If icons are used for conveying meaning in addition to the label text, a text alternative\n> must be provided for screen readers.\n\n### Error States\n\nForm Field provides error and caution states for Text Input. Set the `error` prop on Form Field to\n`\"error\"` or `\"caution\"` and use `FormField.Hint` to provide error messages. See\n[Form Field's Error documentation](/components/inputs/form-field/#error-states) for examples and\naccessibility guidance.\n\n## Accessibility\n\n`TextInput` should be used with [Form Field](/components/inputs/form-field/) to ensure proper\nlabeling, error handling, and help text association. See\n[FormField's accessibility documentation](/components/inputs/form-field/#accessibility) for\ncomprehensive guidance on form accessibility best practices.\n\n### Autocomplete Attribute\n\n- Add appropriate `autoComplete` values to indicate the input's purpose (e.g., `\"email\"`, `\"name\"`,\n `\"street-address\"`, `\"tel\"`). Read more about\n [Identify Input Purpose](https://www.w3.org/WAI/WCAG22/Understanding/identify-input-purpose.html).\n- Autocomplete enables browser autofill and helps assistive technologies understand the field's\n purpose, benefiting users with cognitive disabilities and motor impairments.\n- Autocomplete also helps password managers identify the correct fields.\n\n### Input Type for Mobile Keyboards\n\n`TextInput` defaults to `<input type=\"text\">`, but for better mobile keyboard support, use more\nspecific `type` attributes (like `\"email\"`, `\"tel\"`, `\"url\"`, or `\"search\"`) as needed.\n\n### Screen Reader Experience\n\nWhen properly implemented with `FormField`, screen readers will announce:\n\n- The label text when the input receives focus.\n- Required, disabled, or read-only status.\n- Help text and error messages (via `aria-describedby`).\n- The current value or \"blank\" if empty.\n\n## Component API\n\n## Specifications\n\n",
|
|
337
|
-
accessibilityProse: '## Accessibility\n\n`TextInput` should be used with [Form Field](/components/inputs/form-field/) to ensure proper\nlabeling, error handling, and help text association. See\n[FormField\'s accessibility documentation](/components/inputs/form-field/#accessibility) for\ncomprehensive guidance on form accessibility best practices.\n\n### Autocomplete Attribute\n\n- Add appropriate `autoComplete` values to indicate the input\'s purpose (e.g., `"email"`, `"name"`,\n `"street-address"`, `"tel"`). Read more about\n [Identify Input Purpose](https://www.w3.org/WAI/WCAG22/Understanding/identify-input-purpose.html).\n- Autocomplete enables browser autofill and helps assistive technologies understand the field\'s\n purpose, benefiting users with cognitive disabilities and motor impairments.\n- Autocomplete also helps password managers identify the correct fields.\n\n### Input Type for Mobile Keyboards\n\n`TextInput` defaults to `<input type="text">`, but for better mobile keyboard support, use more\nspecific `type` attributes (like `"email"`, `"tel"`, `"url"`, or `"search"`) as needed.\n\n### Screen Reader Experience\n\nWhen properly implemented with `FormField`, screen readers will announce:\n\n- The label text when the input receives focus.\n- Required, disabled, or read-only status.\n- Help text and error messages (via `aria-describedby`).\n- The current value or "blank" if empty.'
|
|
336
|
+
mdxProse: "# Canvas Kit Text Input\n\nText Inputs allow users to enter words or characters without styling.\n\n[> Workday Design Reference](https://design.workday.com/components/inputs/text-input)\n\n## Installation\n\n```sh\nyarn add @workday/canvas-kit-react\n```\n\n## Usage\n\n### Basic Example\n\nText Input should be used in tandem with [Form Field](/components/inputs/form-field/) to ensure\nproper label association and screen reader support.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const Basic = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <FormField>\n <FormField.Label>Email</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextInput} onChange={handleChange} value={value} />\n </FormField.Field>\n </FormField>\n );\n};\n```\n\n### Disabled\n\nSet the `disabled` prop of the Text Input to prevent users from interacting with it.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const Disabled = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <FormField>\n <FormField.Label>Email</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextInput} disabled onChange={handleChange} value={value} />\n </FormField.Field>\n </FormField>\n );\n};\n```\n\n### Placeholder\n\nSet the `placeholder` prop of the Text Input to display a sample of its expected format or value\nbefore a value has been provided.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const Placeholder = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <FormField>\n <FormField.Label>Email</FormField.Label>\n <FormField.Field>\n <FormField.Input\n as={TextInput}\n onChange={handleChange}\n placeholder=\"user@email.com\"\n value={value}\n />\n </FormField.Field>\n </FormField>\n );\n};\n```\n\n> **Accessibility Note**: Always provide a persistent `FormField.Label` and never rely on\n> placeholder text as the only label for an input. Placeholders can disappear or lack sufficient\n> contrast. Use placeholders only for short format examples (e.g., \"name@example.com\"), and place\n> detailed instructions or guidance in `FormField.Hint` instead of the placeholder.\n\n### Ref Forwarding\n\nText Input supports [ref forwarding](https://reactjs.org/docs/forwarding-refs.html). It will forward\n`ref` to its underlying `<input type=\"text\">` element.\n```tsx\nimport React from 'react';\n\nimport {PrimaryButton} from '@workday/canvas-kit-react/button';\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const RefForwarding = () => {\n const [value, setValue] = React.useState('');\n const ref = React.useRef(null);\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n const handleClick = () => {\n ref.current.focus();\n };\n\n return (\n <>\n <FormField>\n <FormField.Label>Email</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextInput} onChange={handleChange} ref={ref} value={value} />\n </FormField.Field>\n </FormField>\n <PrimaryButton onClick={handleClick}>Focus Text Input</PrimaryButton>\n </>\n );\n};\n```\n\n### Grow\n\nSet the `grow` prop of the wrapping Form Field to `true` to configure the Text Input to expand to\nthe width of its container.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const Grow = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <FormField grow>\n <FormField.Label>Street Address</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextInput} onChange={handleChange} value={value} />\n </FormField.Field>\n </FormField>\n );\n};\n```\n\n### Label Position Horizontal\n\nSet the `orientation` prop of the Form Field to designate the position of the label relative to the\ninput component. By default, the orientation will be set to `vertical`.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const LabelPosition = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <FormField orientation=\"horizontalStart\">\n <FormField.Label>Email</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextInput} onChange={handleChange} value={value} />\n <FormField.Hint>Add a valid email</FormField.Hint>\n </FormField.Field>\n </FormField>\n );\n};\n```\n\n### Required\n\nSet the `isRequired` prop of the wrapping Form Field to `true` to indicate that the field is\nrequired. Labels for required fields are suffixed by a red asterisk.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const Required = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <FormField isRequired={true}>\n <FormField.Label>Email</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextInput} onChange={handleChange} value={value} />\n </FormField.Field>\n </FormField>\n );\n};\n```\n\n### Icons\n\n`InputGroup` is available to add icons to the `TextInput`. Internally, a container `div` element is\nused with relative position styling on the `div` and absolute position styling on the start and end\nicons. `InputGroup.InnerStart` and `InputGroup.InnerEnd` are used to position elements at the start\nand end of the input. \"start\" and \"end\" are used instead of \"left\" and \"right\" to match\n[CSS Logical Properties](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties)\nand will be semantically correct in left-to-right and right-to-left languages.\n\n`InputGroup.InnerStart` and `InputGroup.InnerEnd` subcomponents can handle any child elements, but\nare built for icons. The default width is `40px`, which is perfect for icons. If you need to use\nsomething else, be sure to set the `width` property of `InputGroup.InnerStart` or\n`InputGroup.InnerEnd` to match the intended width of the element. Do not use the `cs` prop or any\nmethod to change width. The `width` prop is used to correctly position other inner elements.\n\nDo **not** use `FormField.Input as={InputGroup}` \u2014 that breaks label association. Render\n`FormField.Field as={InputGroup}`, hoist the input `id` from the Form Field model, and set it on\n`InputGroup.Input` (see the Icons example).\n```tsx\nimport React from 'react';\n\nimport {\n FormField,\n useFormFieldInput,\n useFormFieldModel,\n} from '@workday/canvas-kit-react/form-field';\nimport {SystemIcon} from '@workday/canvas-kit-react/icon';\nimport {InputGroup} from '@workday/canvas-kit-react/text-input';\nimport {mailIcon} from '@workday/canvas-system-icons-web';\n\n/**\n * Using `as={InputGroup}` on `FormField.Input` will break the label associations necessary for accessibility.\n * In this example, we've rendered `FormField.Field` as `InputGroup` and then hoisted the `id` of the input from the FormField model.\n * This allows us to set the `id` of the `InputGroup.Input` correctly for proper label association.\n */\n\nexport const Icons = () => {\n const model = useFormFieldModel();\n const {id: formFieldInputId} = useFormFieldInput(model);\n\n return (\n <FormField model={model}>\n <FormField.Label>Email</FormField.Label>\n <FormField.Field as={InputGroup}>\n <InputGroup.InnerStart>\n <SystemIcon icon={mailIcon} />\n </InputGroup.InnerStart>\n <InputGroup.Input id={formFieldInputId} autoComplete=\"email\" />\n <InputGroup.InnerEnd>\n <InputGroup.ClearButton />\n </InputGroup.InnerEnd>\n </FormField.Field>\n </FormField>\n );\n};\n```\n\n> **Accessibility Note**: Canvas Kit icons are already hidden from assistive technology \u2014 their SVG\n> markup sets `role=\"presentation\"` and `focusable=\"false\"` \u2014 so decorative icons like the mail icon\n> in this example need no extra attributes. If an icon conveys meaning beyond the label text,\n> provide that meaning as text for screen readers.\n\n### Error States\n\nForm Field provides error and caution states for Text Input. Set the `error` prop on Form Field to\n`\"error\"` or `\"caution\"` and use `FormField.Hint` to provide error messages. See\n[Form Field's Error documentation](/components/inputs/form-field/#error-states) for examples and\naccessibility guidance.\n\n## Accessibility\n\nThe primary accessibility goal for `TextInput` is to give every user a visible, persistent label and\nclear instructions, and to ensure assistive technology users can identify the single-line field and\nhear hints, errors, required state, and input purpose when the control receives focus. Use\n`TextInput` for single-line values (names, emails, short answers). For multiple lines or paragraphs\nof text, use [TextArea](/components/inputs/text-area/) instead.\n\n### Minimum Accessible Structure\n\nBuild on the Basic example: label first, then the input inside `FormField.Field`. This order matches\nthe DOM reading sequence and ensures the label's `htmlFor` targets the `<input>` before hint text\nfollows the control.\n\n```tsx\n\n<FormField>\n <FormField.Label>Email</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextInput} />\n <FormField.Hint>We'll never share your email.</FormField.Hint>\n </FormField.Field>\n</FormField>;\n```\n\nEvery `TextInput` requires **`FormField`**, a visible **`FormField.Label`**, and\n**`FormField.Input as={TextInput}`** so the control has a programmatically determinable name,\nrelationships, and instructions. See\n[FormField's accessibility documentation](/components/inputs/form-field/#accessibility) for shared\nform-field guidance. Include **`FormField.Hint`** for instructions or validation\nmessages\u2014`FormField` associates that text with the input through `aria-describedby`.\n\n### Built-in Behaviors\n\nCanvas Kit applies these automatically when you compose `TextInput` with `FormField` subcomponents\n(and `InputGroup`, when used). **Do not duplicate them** in consuming code.\n\n**ARIA and DOM** (_applied by subcomponents_):\n\n- **`TextInput`**: Renders a native `<input type=\"text\">` by default. Screen readers identify it as\n a single-line text input.\n- **`TextInput` `disabled`**: Maps to the native `disabled` attribute; disabled fields are removed\n from the tab order.\n- **`InputGroup.ClearButton`**: Sets `role=\"presentation\"` and `tabIndex={-1}` so the control is not\n in the tab order and is not exposed as an operable button to screen readers. Clearing is available\n via native keyboard editing in the input.\n- **`InputGroup.Input`**: Always ensures a `placeholder` attribute exists (empty string when unset)\n so `:placeholder-shown` styling for the clear button works correctly.\n- **Canvas Kit icons** (for example `SystemIcon` inside `InputGroup`): SVG markup includes\n `role=\"presentation\"` and `focusable=\"false\"`, which removes the implied `img` role. Decorative\n icons need no `aria-hidden`.\n\n**Keyboard** (_standard `TextInput` behavior_):\n\n`TextInput` uses native `<input>` keyboard behavior (tab order, label activation, and text-editing\nshortcuts). Do not add custom key handlers that prevent standard text editing.\n\n**`InputGroup.ClearButton`** is intentionally not keyboard-focusable; users clear the value with\nstandard input editing keys.\n\n**Screen reader expectations** (_when built-in behaviors are used as intended_):\n\n- On focus, assistive technology announces the field label and, when applicable: required state,\n invalid state (`error=\"error\"`), and hint or error text via `aria-describedby`.\n- The current value or \"blank\" is announced when the input receives focus.\n- The Caution state is visual only \u2014 `aria-invalid` is **not** set for `error=\"caution\"`.\n- Disabled inputs may be announced as unavailable and are skipped in the tab order.\n- **`InputGroup.ClearButton`** is not announced as a separate operable control.\n- Icons rendered inside **`InputGroup.InnerStart`** or **`InputGroup.InnerEnd`** are not announced,\n because their SVG markup uses `role=\"presentation\"`.\n\nFor rendered label, input, and hint association markup, see the DOM examples in\n[FormField's Built-in Behaviors](/components/inputs/form-field/#built-in-behaviors). `TextInput`\nrenders a native `<input>` (see FormField examples).\n\n### Accessibility Requirements\n\nRequired in application code for an accessible `TextInput`. Rows marked _(conditional)_ apply only\nwhen the situation matches\u2014otherwise omit.\n\n**If no design spec is provided:** use a visible `FormField.Label`, wrap the control with\n`FormField.Input as={TextInput}`, omit `isHidden`, omit a custom `id` unless testing or composition\nrequires it, omit a `ref` unless programmatic focus is required, and omit `InputGroup` unless icons\nor a clear control are part of the design.\n\n**Programmatic focus** _(conditional \u2014 omit by default)_:\n\nUse a ref when the product needs to move focus to the input after an action (for example, focusing\nthe field after a validation error, or a control that focuses the input). Do not attach a `ref` or\ncall `focus()` unless the design or developer asks for it. See [Ref Forwarding](#ref-forwarding)\nunder Usage for a complete Storybook example.\n\n```tsx\nconst Example = () => {\n const ref = React.useRef<HTMLInputElement>(null);\n\n const handleClick = () => {\n ref.current?.focus();\n };\n\n return (\n <>\n <FormField>\n <FormField.Label>Email</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextInput} ref={ref} />\n </FormField.Field>\n </FormField>\n <PrimaryButton onClick={handleClick}>Focus Text Input</PrimaryButton>\n </>\n );\n};\n```\n\n**`InputGroup` with icons** _(conditional)_:\n\nWhen the design includes start/end icons or a clear control, compose `InputGroup` as\n`FormField.Field` (not as `FormField.Input`) and wire the input `id` from the Form Field model:\n\n```tsx\n\nconst model = useFormFieldModel();\nconst {id: formFieldInputId} = useFormFieldInput(model);\n\n<FormField model={model}>\n <FormField.Label>Email</FormField.Label>\n <FormField.Field as={InputGroup}>\n <InputGroup.InnerStart>\n <SystemIcon icon={mailIcon} />\n </InputGroup.InnerStart>\n <InputGroup.Input id={formFieldInputId} autoComplete=\"email\" />\n <InputGroup.InnerEnd>\n <InputGroup.ClearButton />\n </InputGroup.InnerEnd>\n </FormField.Field>\n</FormField>;\n```\n\n| Requirement | How to satisfy |\n| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| Input wiring | **`FormField.Input as={TextInput}`** wrapping every `TextInput` instance. See [FormField accessibility](/components/inputs/form-field/#accessibility) for label, hint, error, and required wiring |\n| Autocomplete _(conditional)_ | `autoComplete` on **`FormField.Input`** (or **`InputGroup.Input`**) with an appropriate token (e.g. `\"email\"`, `\"name\"`, `\"street-address\"`, `\"tel\"`). See [Identify Input Purpose](https://www.w3.org/WAI/WCAG22/Understanding/identify-input-purpose.html) |\n| Input `type` _(conditional)_ | More specific `type` than `\"text\"` (e.g. `\"email\"`, `\"tel\"`, `\"url\"`, `\"search\"`) when a specialized mobile keyboard improves entry |\n| Icons / clear control _(conditional)_ | **`FormField.Field as={InputGroup}`** + **`InputGroup.Input`** with hoisted `id` (see **`InputGroup` with icons** above). Decorative icons need no extra attributes; when an icon conveys meaning beyond the label, convey that meaning as text |\n| Programmatic focus _(conditional)_ | `ref` on **`FormField.Input`** and call `focus()` when moving focus to the field after an action\u2014omit by default (see **Programmatic focus** above) |\n\n**Summary for code generation:**\n\n- **REQUIRED:** visible label, `FormField.Input as={TextInput}` wiring\n- **CONDITIONAL:** `autoComplete`, specialized `type`, `InputGroup` icon/clear composition with\n hoisted `id`, programmatic focus via `ref`. See\n [FormField accessibility](/components/inputs/form-field/#accessibility) for shared FormField\n conditionals (hint/error, required, disabled, placeholder, stable `id`).\n\n### Anti-Patterns\n\nDo **not** generate code that does the following (see **Accessibility Requirements** above for what\nto supply instead):\n\n- **Unlabeled text inputs**: Do not use `TextInput` without `FormField` and `FormField.Label` (see\n **Minimum accessible structure**). For shared FormField anti-patterns (manual ARIA wiring,\n placeholder-only labels, color-only errors, broken ID references), see\n [FormField Anti-Patterns](/components/inputs/form-field/#anti-patterns).\n- **Multi-line content in `TextInput`**: Do not use `TextInput` when the user needs to enter\n paragraphs or multi-line text; use [TextArea](/components/inputs/text-area/) instead.\n- **`FormField.Input as={InputGroup}`**: Do not put `InputGroup` on `FormField.Input` \u2014 that breaks\n label association. Use **`FormField.Field as={InputGroup}`**, hoist `id` from\n `useFormFieldInput(model)`, and pass it to **`InputGroup.Input`** (see **`InputGroup` with icons**\n in Accessibility Requirements and [Icons](#icons) under Usage).\n- **Re-wiring `ClearButton` a11y**: Do not override `InputGroup.ClearButton`'s `role` or `tabIndex`\n to make it a focusable, announced button \u2014 Canvas Kit intentionally keeps clearing on the input.\n- **Redundant `aria-hidden` on icons**: Do not add `aria-hidden` to Canvas Kit icons \u2014 their SVG\n markup already sets `role=\"presentation\"` and `focusable=\"false\"`.\n- **Meaningful icons without a text alternative**: Do not rely on an icon inside `InputGroup` to\n convey information beyond the label; because icons are presentational, that meaning must come from\n text such as **`FormField.Label`** or **`FormField.Hint`**.\n- **Programmatic focus by default**: Do not attach a `ref` or call `focus()` on the input unless the\n design or developer asks for it (see **Programmatic focus** in Accessibility Requirements).\n\n## Component API\n\n## Specifications\n\n",
|
|
337
|
+
accessibilityProse: '## Accessibility\n\nThe primary accessibility goal for `TextInput` is to give every user a visible, persistent label and\nclear instructions, and to ensure assistive technology users can identify the single-line field and\nhear hints, errors, required state, and input purpose when the control receives focus. Use\n`TextInput` for single-line values (names, emails, short answers). For multiple lines or paragraphs\nof text, use [TextArea](/components/inputs/text-area/) instead.\n\n### Minimum Accessible Structure\n\nBuild on the Basic example: label first, then the input inside `FormField.Field`. This order matches\nthe DOM reading sequence and ensures the label\'s `htmlFor` targets the `<input>` before hint text\nfollows the control.\n\n```tsx\n\n<FormField>\n <FormField.Label>Email</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextInput} />\n <FormField.Hint>We\'ll never share your email.</FormField.Hint>\n </FormField.Field>\n</FormField>;\n```\n\nEvery `TextInput` requires **`FormField`**, a visible **`FormField.Label`**, and\n**`FormField.Input as={TextInput}`** so the control has a programmatically determinable name,\nrelationships, and instructions. See\n[FormField\'s accessibility documentation](/components/inputs/form-field/#accessibility) for shared\nform-field guidance. Include **`FormField.Hint`** for instructions or validation\nmessages\u2014`FormField` associates that text with the input through `aria-describedby`.\n\n### Built-in Behaviors\n\nCanvas Kit applies these automatically when you compose `TextInput` with `FormField` subcomponents\n(and `InputGroup`, when used). **Do not duplicate them** in consuming code.\n\n**ARIA and DOM** (_applied by subcomponents_):\n\n- **`TextInput`**: Renders a native `<input type="text">` by default. Screen readers identify it as\n a single-line text input.\n- **`TextInput` `disabled`**: Maps to the native `disabled` attribute; disabled fields are removed\n from the tab order.\n- **`InputGroup.ClearButton`**: Sets `role="presentation"` and `tabIndex={-1}` so the control is not\n in the tab order and is not exposed as an operable button to screen readers. Clearing is available\n via native keyboard editing in the input.\n- **`InputGroup.Input`**: Always ensures a `placeholder` attribute exists (empty string when unset)\n so `:placeholder-shown` styling for the clear button works correctly.\n- **Canvas Kit icons** (for example `SystemIcon` inside `InputGroup`): SVG markup includes\n `role="presentation"` and `focusable="false"`, which removes the implied `img` role. Decorative\n icons need no `aria-hidden`.\n\n**Keyboard** (_standard `TextInput` behavior_):\n\n`TextInput` uses native `<input>` keyboard behavior (tab order, label activation, and text-editing\nshortcuts). Do not add custom key handlers that prevent standard text editing.\n\n**`InputGroup.ClearButton`** is intentionally not keyboard-focusable; users clear the value with\nstandard input editing keys.\n\n**Screen reader expectations** (_when built-in behaviors are used as intended_):\n\n- On focus, assistive technology announces the field label and, when applicable: required state,\n invalid state (`error="error"`), and hint or error text via `aria-describedby`.\n- The current value or "blank" is announced when the input receives focus.\n- The Caution state is visual only \u2014 `aria-invalid` is **not** set for `error="caution"`.\n- Disabled inputs may be announced as unavailable and are skipped in the tab order.\n- **`InputGroup.ClearButton`** is not announced as a separate operable control.\n- Icons rendered inside **`InputGroup.InnerStart`** or **`InputGroup.InnerEnd`** are not announced,\n because their SVG markup uses `role="presentation"`.\n\nFor rendered label, input, and hint association markup, see the DOM examples in\n[FormField\'s Built-in Behaviors](/components/inputs/form-field/#built-in-behaviors). `TextInput`\nrenders a native `<input>` (see FormField examples).\n\n### Accessibility Requirements\n\nRequired in application code for an accessible `TextInput`. Rows marked _(conditional)_ apply only\nwhen the situation matches\u2014otherwise omit.\n\n**If no design spec is provided:** use a visible `FormField.Label`, wrap the control with\n`FormField.Input as={TextInput}`, omit `isHidden`, omit a custom `id` unless testing or composition\nrequires it, omit a `ref` unless programmatic focus is required, and omit `InputGroup` unless icons\nor a clear control are part of the design.\n\n**Programmatic focus** _(conditional \u2014 omit by default)_:\n\nUse a ref when the product needs to move focus to the input after an action (for example, focusing\nthe field after a validation error, or a control that focuses the input). Do not attach a `ref` or\ncall `focus()` unless the design or developer asks for it. See [Ref Forwarding](#ref-forwarding)\nunder Usage for a complete Storybook example.\n\n```tsx\nconst Example = () => {\n const ref = React.useRef<HTMLInputElement>(null);\n\n const handleClick = () => {\n ref.current?.focus();\n };\n\n return (\n <>\n <FormField>\n <FormField.Label>Email</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextInput} ref={ref} />\n </FormField.Field>\n </FormField>\n <PrimaryButton onClick={handleClick}>Focus Text Input</PrimaryButton>\n </>\n );\n};\n```\n\n**`InputGroup` with icons** _(conditional)_:\n\nWhen the design includes start/end icons or a clear control, compose `InputGroup` as\n`FormField.Field` (not as `FormField.Input`) and wire the input `id` from the Form Field model:\n\n```tsx\n\nconst model = useFormFieldModel();\nconst {id: formFieldInputId} = useFormFieldInput(model);\n\n<FormField model={model}>\n <FormField.Label>Email</FormField.Label>\n <FormField.Field as={InputGroup}>\n <InputGroup.InnerStart>\n <SystemIcon icon={mailIcon} />\n </InputGroup.InnerStart>\n <InputGroup.Input id={formFieldInputId} autoComplete="email" />\n <InputGroup.InnerEnd>\n <InputGroup.ClearButton />\n </InputGroup.InnerEnd>\n </FormField.Field>\n</FormField>;\n```\n\n| Requirement | How to satisfy |\n| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| Input wiring | **`FormField.Input as={TextInput}`** wrapping every `TextInput` instance. See [FormField accessibility](/components/inputs/form-field/#accessibility) for label, hint, error, and required wiring |\n| Autocomplete _(conditional)_ | `autoComplete` on **`FormField.Input`** (or **`InputGroup.Input`**) with an appropriate token (e.g. `"email"`, `"name"`, `"street-address"`, `"tel"`). See [Identify Input Purpose](https://www.w3.org/WAI/WCAG22/Understanding/identify-input-purpose.html) |\n| Input `type` _(conditional)_ | More specific `type` than `"text"` (e.g. `"email"`, `"tel"`, `"url"`, `"search"`) when a specialized mobile keyboard improves entry |\n| Icons / clear control _(conditional)_ | **`FormField.Field as={InputGroup}`** + **`InputGroup.Input`** with hoisted `id` (see **`InputGroup` with icons** above). Decorative icons need no extra attributes; when an icon conveys meaning beyond the label, convey that meaning as text |\n| Programmatic focus _(conditional)_ | `ref` on **`FormField.Input`** and call `focus()` when moving focus to the field after an action\u2014omit by default (see **Programmatic focus** above) |\n\n**Summary for code generation:**\n\n- **REQUIRED:** visible label, `FormField.Input as={TextInput}` wiring\n- **CONDITIONAL:** `autoComplete`, specialized `type`, `InputGroup` icon/clear composition with\n hoisted `id`, programmatic focus via `ref`. See\n [FormField accessibility](/components/inputs/form-field/#accessibility) for shared FormField\n conditionals (hint/error, required, disabled, placeholder, stable `id`).\n\n### Anti-Patterns\n\nDo **not** generate code that does the following (see **Accessibility Requirements** above for what\nto supply instead):\n\n- **Unlabeled text inputs**: Do not use `TextInput` without `FormField` and `FormField.Label` (see\n **Minimum accessible structure**). For shared FormField anti-patterns (manual ARIA wiring,\n placeholder-only labels, color-only errors, broken ID references), see\n [FormField Anti-Patterns](/components/inputs/form-field/#anti-patterns).\n- **Multi-line content in `TextInput`**: Do not use `TextInput` when the user needs to enter\n paragraphs or multi-line text; use [TextArea](/components/inputs/text-area/) instead.\n- **`FormField.Input as={InputGroup}`**: Do not put `InputGroup` on `FormField.Input` \u2014 that breaks\n label association. Use **`FormField.Field as={InputGroup}`**, hoist `id` from\n `useFormFieldInput(model)`, and pass it to **`InputGroup.Input`** (see **`InputGroup` with icons**\n in Accessibility Requirements and [Icons](#icons) under Usage).\n- **Re-wiring `ClearButton` a11y**: Do not override `InputGroup.ClearButton`\'s `role` or `tabIndex`\n to make it a focusable, announced button \u2014 Canvas Kit intentionally keeps clearing on the input.\n- **Redundant `aria-hidden` on icons**: Do not add `aria-hidden` to Canvas Kit icons \u2014 their SVG\n markup already sets `role="presentation"` and `focusable="false"`.\n- **Meaningful icons without a text alternative**: Do not rely on an icon inside `InputGroup` to\n convey information beyond the label; because icons are presentational, that meaning must come from\n text such as **`FormField.Label`** or **`FormField.Hint`**.\n- **Programmatic focus by default**: Do not attach a `ref` or call `focus()` on the input unless the\n design or developer asks for it (see **Programmatic focus** in Accessibility Requirements).'
|
|
338
338
|
},
|
|
339
339
|
textarea: {
|
|
340
340
|
title: "Components/Inputs/TextArea",
|
|
341
341
|
storybookUrl: "https://workday.github.io/canvas-kit/?path=/docs/components-inputs-textarea--docs",
|
|
342
342
|
mdxPath: "modules/react/text-area/stories/TextArea.mdx",
|
|
343
|
-
mdxProse: "# Canvas Kit Text Area\n\nText Areas allow users to enter and edit multiple lines of text.\n\n[> Workday Design Reference](https://design.workday.com/components/inputs/text-area)\n\n## Installation\n\n```sh\nyarn add @workday/canvas-kit-react\n```\n\n## Usage\n\n### Basic Example\n\nText Area should be used in tandem with [Form Field](/components/inputs/form-field/) to ensure\nproper label association and screen reader support.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextArea} from '@workday/canvas-kit-react/text-area';\n\nexport const Basic = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <FormField>\n <FormField.Label>Leave a Review</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextArea} onChange={handleChange} value={value} />\n </FormField.Field>\n </FormField>\n );\n};\n```\n\n### Disabled\n\nSet the `disabled` prop of the Text Area to prevent users from interacting with it.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextArea} from '@workday/canvas-kit-react/text-area';\n\nexport const Disabled = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <FormField>\n <FormField.Label>Leave a Review</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextArea} disabled onChange={handleChange} value={value} />\n </FormField.Field>\n </FormField>\n );\n};\n```\n\n### Placeholder\n\nSet the `placeholder` prop of the Text Area to display a sample of its expected format or value\nbefore a value has been provided.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextArea} from '@workday/canvas-kit-react/text-area';\n\nexport const Placeholder = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <FormField>\n <FormField.Label>Leave a Review</FormField.Label>\n <FormField.Field>\n <FormField.Input\n as={TextArea}\n onChange={handleChange}\n placeholder=\"Let us know how we did!\"\n value={value}\n />\n </FormField.Field>\n </FormField>\n );\n};\n```\n\n> **Accessibility Note**: Always provide a persistent `FormField.Label` and never rely on\n> placeholder text as the only label for a text area. Placeholders can disappear or lack sufficient\n> contrast. Use placeholders only for short format examples, and place detailed instructions or\n> guidance in `FormField.Hint` instead of the placeholder.\n\n### Ref Forwarding\n\nText Area supports [ref forwarding](https://reactjs.org/docs/forwarding-refs.html). It will forward\n`ref` to its underlying `<textarea>` element.\n```tsx\nimport React from 'react';\n\nimport {PrimaryButton} from '@workday/canvas-kit-react/button';\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextArea} from '@workday/canvas-kit-react/text-area';\n\nexport const RefForwarding = () => {\n const [value, setValue] = React.useState('');\n const ref = React.useRef(null);\n\n const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {\n setValue(event.target.value);\n };\n\n const handleClick = () => {\n ref.current.focus();\n };\n\n return (\n <>\n <FormField>\n <FormField.Label>Leave a Review</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextArea} onChange={handleChange} ref={ref} value={value} />\n </FormField.Field>\n </FormField>\n <PrimaryButton onClick={handleClick}>Focus Text Area</PrimaryButton>\n </>\n );\n};\n```\n\n### Resize Constraints\n\nSet the `resize` prop of the Text Area to restrict resizing of it to certain dimensions. `resize`\naccepts the following values:\n\n- `TextArea.ResizeDirection.Both` (Default)\n- `TextArea.ResizeDirection.Horizontal`\n- `TextArea.ResizeDirection.None`\n- `TextArea.ResizeDirection.Vertical`\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextArea} from '@workday/canvas-kit-react/text-area';\n\nexport const ResizeConstraints = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <FormField>\n <FormField.Label>Leave a Review</FormField.Label>\n <FormField.Field>\n <FormField.Input\n as={TextArea}\n onChange={handleChange}\n resize={TextArea.ResizeDirection.Vertical}\n value={value}\n />\n </FormField.Field>\n </FormField>\n );\n};\n```\n\n> **Accessibility Note**: Allowing users to resize the text area (default `resize: both`) improves\n> accessibility by letting them adjust it for comfort. Avoid disabling resizing (`resize: none`)\n> unless necessary, and always ensure the initial size meets the needs of your content.\n\n### Grow\n\nSet the `grow` prop of the Text Area to `true` to configure the Text Area to expand to the width of\nits container.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextArea} from '@workday/canvas-kit-react/text-area';\n\nexport const Grow = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <FormField grow>\n <FormField.Label>Leave a Review</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextArea} onChange={handleChange} value={value} />\n </FormField.Field>\n </FormField>\n );\n};\n```\n\n### Label Position Horizontal\n\nSet the `orientation` prop of the Form Field to designate the position of the label relative to the\ninput component. By default, the orientation will be set to `vertical`.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextArea} from '@workday/canvas-kit-react/text-area';\n\nexport const LabelPosition = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <FormField orientation=\"horizontalStart\">\n <FormField.Label>Leave a Review</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextArea} onChange={handleChange} value={value} />\n <FormField.Hint>Message must be under 200 characters</FormField.Hint>\n </FormField.Field>\n </FormField>\n );\n};\n```\n\n### Required\n\nSet the `required` prop of the wrapping Form Field to `true` to indicate that the field is required.\nLabels for required fields are suffixed by a red asterisk.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextArea} from '@workday/canvas-kit-react/text-area';\n\nexport const Required = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <FormField isRequired={true}>\n <FormField.Label>Leave a Review</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextArea} onChange={handleChange} value={value} />\n </FormField.Field>\n </FormField>\n );\n};\n```\n\n### Error States\n\nForm Field provides error and caution states for Text Area. Set the `error` prop on Form Field to\n`\"error\"` or `\"caution\"` and use `FormField.Hint` to provide error messages. See\n[Form Field's Error documentation](/components/inputs/form-field/#error-states) for\nexamples and accessibility guidance.\n\n## Accessibility\n\n`TextArea` should be used with [Form Field](/components/inputs/form-field/) to\nensure proper labeling, error handling, and help text association. See\n[FormField's accessibility documentation](/components/inputs/form-field/#accessibility)\nfor comprehensive guidance on form accessibility best practices.\n\n### Character Limits\n\nWhen limiting text area length:\n\n- Use the `maxLength` attribute to enforce the limit programmatically.\n- For longer limits (100+ characters), consider adding character count information to\n `FormField.Hint`.\n- Avoid announcing character counts after every keystroke, as this disrupts screen reader users.\n Check out\n [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)\n for an example of how to wait for users to stop typing before announcing the character count to\n screen readers.\n\n### Screen Reader Experience\n\nWhen properly implemented with `FormField`, screen readers will announce:\n\n- The label text when the text area receives focus.\n- Required, disabled, or read-only status.\n- Help text and error messages (via `aria-describedby`).\n- The current value or \"blank\" if empty.\n- That it's a multi-line text input field.\n\n## Component API\n\n## Specifications\n\n",
|
|
344
|
-
accessibilityProse:
|
|
343
|
+
mdxProse: "# Canvas Kit Text Area\n\nText Areas allow users to enter and edit multiple lines of text.\n\n[> Workday Design Reference](https://design.workday.com/components/inputs/text-area)\n\n## Installation\n\n```sh\nyarn add @workday/canvas-kit-react\n```\n\n## Usage\n\n### Basic Example\n\nText Area should be used in tandem with [Form Field](/components/inputs/form-field/) to ensure\nproper label association and screen reader support.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextArea} from '@workday/canvas-kit-react/text-area';\n\nexport const Basic = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <FormField>\n <FormField.Label>Leave a Review</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextArea} onChange={handleChange} value={value} />\n </FormField.Field>\n </FormField>\n );\n};\n```\n\n### Disabled\n\nSet the `disabled` prop of the Text Area to prevent users from interacting with it.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextArea} from '@workday/canvas-kit-react/text-area';\n\nexport const Disabled = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <FormField>\n <FormField.Label>Leave a Review</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextArea} disabled onChange={handleChange} value={value} />\n </FormField.Field>\n </FormField>\n );\n};\n```\n\n### Placeholder\n\nSet the `placeholder` prop of the Text Area to display a sample of its expected format or value\nbefore a value has been provided.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextArea} from '@workday/canvas-kit-react/text-area';\n\nexport const Placeholder = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <FormField>\n <FormField.Label>Leave a Review</FormField.Label>\n <FormField.Field>\n <FormField.Input\n as={TextArea}\n onChange={handleChange}\n placeholder=\"Let us know how we did!\"\n value={value}\n />\n </FormField.Field>\n </FormField>\n );\n};\n```\n\n> **Accessibility Note**: Always provide a persistent `FormField.Label` and never rely on\n> placeholder text as the only label for a text area. Placeholders can disappear or lack sufficient\n> contrast. Use placeholders only for short format examples, and place detailed instructions or\n> guidance in `FormField.Hint` instead of the placeholder.\n\n### Ref Forwarding\n\nText Area supports [ref forwarding](https://reactjs.org/docs/forwarding-refs.html). It will forward\n`ref` to its underlying `<textarea>` element.\n```tsx\nimport React from 'react';\n\nimport {PrimaryButton} from '@workday/canvas-kit-react/button';\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextArea} from '@workday/canvas-kit-react/text-area';\n\nexport const RefForwarding = () => {\n const [value, setValue] = React.useState('');\n const ref = React.useRef(null);\n\n const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {\n setValue(event.target.value);\n };\n\n const handleClick = () => {\n ref.current.focus();\n };\n\n return (\n <>\n <FormField>\n <FormField.Label>Leave a Review</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextArea} onChange={handleChange} ref={ref} value={value} />\n </FormField.Field>\n </FormField>\n <PrimaryButton onClick={handleClick}>Focus Text Area</PrimaryButton>\n </>\n );\n};\n```\n\n### Resize Constraints\n\nSet the `resize` prop of the Text Area to restrict resizing of it to certain dimensions. `resize`\naccepts the following values:\n\n- `TextArea.ResizeDirection.Both` (Default)\n- `TextArea.ResizeDirection.Horizontal`\n- `TextArea.ResizeDirection.None`\n- `TextArea.ResizeDirection.Vertical`\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextArea} from '@workday/canvas-kit-react/text-area';\n\nexport const ResizeConstraints = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <FormField>\n <FormField.Label>Leave a Review</FormField.Label>\n <FormField.Field>\n <FormField.Input\n as={TextArea}\n onChange={handleChange}\n resize={TextArea.ResizeDirection.Vertical}\n value={value}\n />\n </FormField.Field>\n </FormField>\n );\n};\n```\n\n> **Accessibility Note**: Allowing users to resize the text area (default `resize: both`) improves\n> accessibility by letting them adjust it for comfort. Avoid disabling resizing (`resize: none`)\n> unless necessary, and always ensure the initial size meets the needs of your content.\n\n### Grow\n\nSet the `grow` prop of the Text Area to `true` to configure the Text Area to expand to the width of\nits container.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextArea} from '@workday/canvas-kit-react/text-area';\n\nexport const Grow = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <FormField grow>\n <FormField.Label>Leave a Review</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextArea} onChange={handleChange} value={value} />\n </FormField.Field>\n </FormField>\n );\n};\n```\n\n### Label Position Horizontal\n\nSet the `orientation` prop of the Form Field to designate the position of the label relative to the\ninput component. By default, the orientation will be set to `vertical`.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextArea} from '@workday/canvas-kit-react/text-area';\n\nexport const LabelPosition = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <FormField orientation=\"horizontalStart\">\n <FormField.Label>Leave a Review</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextArea} onChange={handleChange} value={value} />\n <FormField.Hint>Message must be under 200 characters</FormField.Hint>\n </FormField.Field>\n </FormField>\n );\n};\n```\n\n### Required\n\nSet the `isRequired` prop of the wrapping Form Field to `true` to indicate that the field is\nrequired. Labels for required fields are suffixed by a red asterisk.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextArea} from '@workday/canvas-kit-react/text-area';\n\nexport const Required = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <FormField isRequired={true}>\n <FormField.Label>Leave a Review</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextArea} onChange={handleChange} value={value} />\n </FormField.Field>\n </FormField>\n );\n};\n```\n\n### Error States\n\nForm Field provides error and caution states for Text Area. Set the `error` prop on Form Field to\n`\"error\"` or `\"caution\"` and use `FormField.Hint` to provide error messages. See\n[Form Field's Error documentation](/components/inputs/form-field/#error-states) for examples and\naccessibility guidance.\n\n## Accessibility\n\nThe primary accessibility goal for `TextArea` is to give every user a visible, persistent label and\nclear instructions, and to ensure assistive technology users can identify the multi-line field and\nhear hints, errors, required state, and character-limit information when the control receives focus.\nUse `TextArea` when users need to enter multiple lines or paragraphs of text. For single-line values\n(names, emails, short answers), use [TextInput](/components/inputs/text-input/) instead.\n\n### Minimum Accessible Structure\n\nBuild on the Basic example: label first, then the input inside `FormField.Field`. This order matches\nthe DOM reading sequence and ensures the label's `htmlFor` targets the `<textarea>` before hint text\nfollows the control.\n\n```tsx\n\n<FormField>\n <FormField.Label>Leave a Review</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextArea} />\n <FormField.Hint>Share any additional feedback.</FormField.Hint>\n </FormField.Field>\n</FormField>;\n```\n\nEvery `TextArea` requires **`FormField`**, a visible **`FormField.Label`**, and\n**`FormField.Input as={TextArea}`** so the control has a programmatically determinable name,\nrelationships, and instructions. See\n[FormField's accessibility documentation](/components/inputs/form-field/#accessibility) for shared\nform-field guidance. Include **`FormField.Hint`** for instructions, validation messages, or\ncharacter counts\u2014`FormField` associates that text with the text area through `aria-describedby`.\n\n### Built-in Behaviors\n\nCanvas Kit applies these automatically when you compose `TextArea` with `FormField` subcomponents.\n**Do not duplicate them** in consuming code.\n\n**ARIA and DOM** (_applied by subcomponents_):\n\n- **`TextArea`**: Renders a native `<textarea>` element. Screen readers identify it as a multi-line\n text input.\n- **`TextArea` `disabled`**: Maps to the native `disabled` attribute; disabled fields are removed\n from the tab order.\n- **User-resizable dimensions**: Defaults to `resize: both` so users can adjust the control for\n visual comfort.\n\n**Keyboard** (_standard `TextArea` behavior_):\n\n<kbd>Enter</kbd>: Inserts a new line (native `<textarea>` behavior). Do not add custom key handlers that prevent standard text editing.\n\n`TextArea` uses native `<textarea>` keyboard behavior (tab order, label activation, and text-editing\nshortcuts).\n\n**Screen reader expectations** (_when built-in behaviors are used as intended_):\n\n- On focus, assistive technology announces the field label and, when applicable: required state,\n invalid state (`error=\"error\"`), and hint or error text via `aria-describedby`.\n- The current value or \"blank\" is announced when the text area receives focus.\n- The Caution state is visual only \u2014 `aria-invalid` is **not** set for `error=\"caution\"`.\n- Disabled text areas may be announced as unavailable and are skipped in the tab order.\n\nFor rendered label, input, and hint association markup, see the DOM examples in\n[FormField's Built-in Behaviors](/components/inputs/form-field/#built-in-behaviors). `TextArea`\nrenders a native `<textarea>` in place of `<input>`.\n\n### Accessibility Requirements\n\nRequired in application code for an accessible `TextArea`. Rows marked _(conditional)_ apply only\nwhen the situation matches\u2014otherwise omit.\n\n**If no design spec is provided:** use a visible `FormField.Label`, wrap the control with\n`FormField.Input as={TextArea}`, omit `isHidden`, keep default `resize: both`, omit a custom `id`\nunless testing or composition requires it, and omit a `ref` unless programmatic focus is required.\n\n**Programmatic focus** _(conditional \u2014 omit by default)_:\n\nUse a ref when the product needs to move focus to the text area after an action (for example,\nfocusing the field after a validation error, or a control that focuses the text area). Do not attach\na `ref` or call `focus()` unless the design or developer asks for it. See\n[Ref Forwarding](#ref-forwarding) under Usage for a complete Storybook example.\n\n```tsx\nconst Example = () => {\n const ref = React.useRef<HTMLTextAreaElement>(null);\n\n const handleClick = () => {\n ref.current?.focus();\n };\n\n return (\n <>\n <FormField>\n <FormField.Label>Leave a Review</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextArea} ref={ref} />\n </FormField.Field>\n </FormField>\n <PrimaryButton onClick={handleClick}>Focus Text Area</PrimaryButton>\n </>\n );\n};\n```\n\n| Requirement | How to satisfy |\n| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| 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 |\n| 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) |\n| Programmatic focus _(conditional)_ | `ref` on **`FormField.Input`** and call `focus()` when moving focus to the field after an action\u2014omit by default (see **Programmatic focus** above) |\n\n**Summary for code generation:**\n\n- **REQUIRED:** visible label, `FormField.Input as={TextArea}` wiring\n- **CONDITIONAL:** character limit with live region, programmatic focus via `ref`. See\n [FormField accessibility](/components/inputs/form-field/#accessibility) for shared FormField\n conditionals (hint/error, required, disabled, placeholder, stable `id`).\n\n### Anti-Patterns\n\nDo **not** generate code that does the following (see **Accessibility Requirements** above for what\nto supply instead):\n\n- **Unlabeled text areas**: Do not use `TextArea` without `FormField` and `FormField.Label` (see\n **Minimum accessible structure**). For shared FormField anti-patterns (manual ARIA wiring,\n placeholder-only labels, color-only errors, broken ID references), see\n [FormField Anti-Patterns](/components/inputs/form-field/#anti-patterns).\n- **Single-line input for multi-line content**: Do not use\n [TextInput](/components/inputs/text-input/) when the user needs to enter paragraphs or multi-line\n text; use `TextArea` instead.\n- **Per-keystroke character announcements**: Do not announce character counts after every keystroke;\n debounce `AriaLiveRegion` updates so screen reader users are not interrupted while typing.\n- **Disabling resize unnecessarily**: Do not set `resize` to `none` unless there is a strong design\n or layout requirement; users lose a visual comfort affordance that supports low-vision and motor\n needs.\n- **Programmatic focus by default**: Do not attach a `ref` or call `focus()` on the text area unless\n the design or developer asks for it (see **Programmatic focus** in Accessibility Requirements).\n\n## Component API\n\n## Specifications\n\n",
|
|
344
|
+
accessibilityProse: '## Accessibility\n\nThe primary accessibility goal for `TextArea` is to give every user a visible, persistent label and\nclear instructions, and to ensure assistive technology users can identify the multi-line field and\nhear hints, errors, required state, and character-limit information when the control receives focus.\nUse `TextArea` when users need to enter multiple lines or paragraphs of text. For single-line values\n(names, emails, short answers), use [TextInput](/components/inputs/text-input/) instead.\n\n### Minimum Accessible Structure\n\nBuild on the Basic example: label first, then the input inside `FormField.Field`. This order matches\nthe DOM reading sequence and ensures the label\'s `htmlFor` targets the `<textarea>` before hint text\nfollows the control.\n\n```tsx\n\n<FormField>\n <FormField.Label>Leave a Review</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextArea} />\n <FormField.Hint>Share any additional feedback.</FormField.Hint>\n </FormField.Field>\n</FormField>;\n```\n\nEvery `TextArea` requires **`FormField`**, a visible **`FormField.Label`**, and\n**`FormField.Input as={TextArea}`** so the control has a programmatically determinable name,\nrelationships, and instructions. See\n[FormField\'s accessibility documentation](/components/inputs/form-field/#accessibility) for shared\nform-field guidance. Include **`FormField.Hint`** for instructions, validation messages, or\ncharacter counts\u2014`FormField` associates that text with the text area through `aria-describedby`.\n\n### Built-in Behaviors\n\nCanvas Kit applies these automatically when you compose `TextArea` with `FormField` subcomponents.\n**Do not duplicate them** in consuming code.\n\n**ARIA and DOM** (_applied by subcomponents_):\n\n- **`TextArea`**: Renders a native `<textarea>` element. Screen readers identify it as a multi-line\n text input.\n- **`TextArea` `disabled`**: Maps to the native `disabled` attribute; disabled fields are removed\n from the tab order.\n- **User-resizable dimensions**: Defaults to `resize: both` so users can adjust the control for\n visual comfort.\n\n**Keyboard** (_standard `TextArea` behavior_):\n\n<kbd>Enter</kbd>: Inserts a new line (native `<textarea>` behavior). Do not add custom key handlers that prevent standard text editing.\n\n`TextArea` uses native `<textarea>` keyboard behavior (tab order, label activation, and text-editing\nshortcuts).\n\n**Screen reader expectations** (_when built-in behaviors are used as intended_):\n\n- On focus, assistive technology announces the field label and, when applicable: required state,\n invalid state (`error="error"`), and hint or error text via `aria-describedby`.\n- The current value or "blank" is announced when the text area receives focus.\n- The Caution state is visual only \u2014 `aria-invalid` is **not** set for `error="caution"`.\n- Disabled text areas may be announced as unavailable and are skipped in the tab order.\n\nFor rendered label, input, and hint association markup, see the DOM examples in\n[FormField\'s Built-in Behaviors](/components/inputs/form-field/#built-in-behaviors). `TextArea`\nrenders a native `<textarea>` in place of `<input>`.\n\n### Accessibility Requirements\n\nRequired in application code for an accessible `TextArea`. Rows marked _(conditional)_ apply only\nwhen the situation matches\u2014otherwise omit.\n\n**If no design spec is provided:** use a visible `FormField.Label`, wrap the control with\n`FormField.Input as={TextArea}`, omit `isHidden`, keep default `resize: both`, omit a custom `id`\nunless testing or composition requires it, and omit a `ref` unless programmatic focus is required.\n\n**Programmatic focus** _(conditional \u2014 omit by default)_:\n\nUse a ref when the product needs to move focus to the text area after an action (for example,\nfocusing the field after a validation error, or a control that focuses the text area). Do not attach\na `ref` or call `focus()` unless the design or developer asks for it. See\n[Ref Forwarding](#ref-forwarding) under Usage for a complete Storybook example.\n\n```tsx\nconst Example = () => {\n const ref = React.useRef<HTMLTextAreaElement>(null);\n\n const handleClick = () => {\n ref.current?.focus();\n };\n\n return (\n <>\n <FormField>\n <FormField.Label>Leave a Review</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextArea} ref={ref} />\n </FormField.Field>\n </FormField>\n <PrimaryButton onClick={handleClick}>Focus Text Area</PrimaryButton>\n </>\n );\n};\n```\n\n| Requirement | How to satisfy |\n| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| 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 |\n| 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) |\n| Programmatic focus _(conditional)_ | `ref` on **`FormField.Input`** and call `focus()` when moving focus to the field after an action\u2014omit by default (see **Programmatic focus** above) |\n\n**Summary for code generation:**\n\n- **REQUIRED:** visible label, `FormField.Input as={TextArea}` wiring\n- **CONDITIONAL:** character limit with live region, programmatic focus via `ref`. See\n [FormField accessibility](/components/inputs/form-field/#accessibility) for shared FormField\n conditionals (hint/error, required, disabled, placeholder, stable `id`).\n\n### Anti-Patterns\n\nDo **not** generate code that does the following (see **Accessibility Requirements** above for what\nto supply instead):\n\n- **Unlabeled text areas**: Do not use `TextArea` without `FormField` and `FormField.Label` (see\n **Minimum accessible structure**). For shared FormField anti-patterns (manual ARIA wiring,\n placeholder-only labels, color-only errors, broken ID references), see\n [FormField Anti-Patterns](/components/inputs/form-field/#anti-patterns).\n- **Single-line input for multi-line content**: Do not use\n [TextInput](/components/inputs/text-input/) when the user needs to enter paragraphs or multi-line\n text; use `TextArea` instead.\n- **Per-keystroke character announcements**: Do not announce character counts after every keystroke;\n debounce `AriaLiveRegion` updates so screen reader users are not interrupted while typing.\n- **Disabling resize unnecessarily**: Do not set `resize` to `none` unless there is a strong design\n or layout requirement; users lose a visual comfort affordance that supports low-vision and motor\n needs.\n- **Programmatic focus by default**: Do not attach a `ref` or call `focus()` on the text area unless\n the design or developer asks for it (see **Programmatic focus** in Accessibility Requirements).'
|
|
345
345
|
},
|
|
346
346
|
title: {
|
|
347
347
|
title: "Components/Text/Title",
|
|
@@ -424,15 +424,15 @@ var stories_config_default = {
|
|
|
424
424
|
title: "Components/Popups/Popup",
|
|
425
425
|
storybookUrl: "https://workday.github.io/canvas-kit/?path=/docs/components-popups-popup--docs",
|
|
426
426
|
mdxPath: "modules/react/popup/stories/Popup.mdx",
|
|
427
|
-
mdxProse: "# Canvas Kit Popups\n\nA \"popup\" is a classification for a type of stacked UI element that appears \"on top\" of statically\npositioned content. Tooltips, Modals, Dropdown menus, etc are all examples of \"popups\". Canvas Kit\nhas a \"stack manager\" system for managing these popups. Different types of popups have different\nrequirements of behavior for UX and accessibility - we can call them behaviors, capabilities, or\ntraits. Canvas Kit comes with a number of [behavioral hooks](#hooks) in the form of React Hooks.\n\nYou should use the most semantic component for your use-case before using `Popup` directly, like\n`Modal`, which already has the correct behaviors built-in. If no component already exists that\nmatches your use case, you can use `Popup` and use our [hooks](#hooks). The `Popup` component comes\nwith a `Popup.Popper` subcomponent that positions a popup using [PopperJS](https://popper.js.org/)\nthat registers a popup with the `PopupStack` automatically and sets the popup model's `placement`\nproperty. `Popup.Popper` component and hooks work with the stack management system for correct\nrendering and accessibility behavior. If you cannot use `Popup.Popper`, use the\n[usePopupStack](#usepoupstack) hook to properly register and deregister the popup at the correct\ntime. If you cannot use our hooks, consider upgrading your component to use Hooks. If you cannot do\nthat, you'll have to look up the `PopupStack` package for the direct API and have a look at the\nsource code for our hooks into the `PopupStack` API.\n\nThis package comes with everything you need to build Popup UIs.\n\n[Buttons](/components/buttons/button)\n\n## Installation\n\n```sh\nyarn add @workday/canvas-kit-react\n```\n\n## Usage\n\nThe `Popup` component is a generic\n[Compound Component](/get-started/for-developers/documentation/compound-components/) that is used to\nbuild popup UIs that are not already covered by Canvas Kit.\n\n### Basic Example\n\nThe Popup has no pre-defined behaviors built in, therefore the `usePopupModel` must always be used\nto create a new `model`. This `model` is then used by all behavior hooks to apply additional popup\nbehaviors to the compound component group. The following example creates a typical popup around a\ntarget element and adds `useCloseOnOutsideClick`, `useCloseOnEscape`, `useInitialFocus`, and\n`useReturnFocus` behaviors. You can read through the [hooks](#hooks) section to learn about all the\npopup behaviors. For accessibility, these behaviors should be included most of the time.\n```tsx\nimport {DeleteButton} from '@workday/canvas-kit-react/button';\nimport {Box} from '@workday/canvas-kit-react/layout';\nimport {\n Popup,\n useCloseOnEscape,\n useCloseOnOutsideClick,\n useFocusRedirect,\n useInitialFocus,\n usePopupModel,\n useReturnFocus,\n} from '@workday/canvas-kit-react/popup';\nimport {createStyles, px2rem} from '@workday/canvas-kit-styling';\n\nconst cardStyles = createStyles({\n width: px2rem(400),\n});\n\nconst bodyStyles = createStyles({\n marginBlock: '0',\n});\n\nexport const Basic = () => {\n const model = usePopupModel();\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusRedirect(model);\n\n const handleDelete = () => {\n console.log('Delete Item');\n };\n\n return (\n <Popup model={model}>\n <Popup.Target as={DeleteButton}>Delete Item</Popup.Target>\n <Popup.Popper placement=\"top\">\n <Popup.Card cs={cardStyles}>\n <Popup.CloseIcon aria-label=\"Close\" />\n <Popup.Heading>Delete Item</Popup.Heading>\n <Popup.Body>\n <Box as=\"p\" cs={bodyStyles}>\n Are you sure you'd like to delete the item titled 'My Item'?\n </Box>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton>Cancel</Popup.CloseButton>\n <Popup.CloseButton as={DeleteButton} onClick={handleDelete}>\n Delete\n </Popup.CloseButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n );\n};\n```\n\n### Initial Focus\n\nIf you want focus to move to a specific element when the popup is opened, set the `initialFocusRef`\nof the model. This is useful for popups that don't have a Close icon button near the top right of\nthe popup. In general, we recommend setting focus to the first interactive component inside the\npopup that is the least destructive action.\n```tsx\nimport React from 'react';\n\nimport {PrimaryButton} from '@workday/canvas-kit-react/button';\nimport {useUniqueId} from '@workday/canvas-kit-react/common';\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {\n Popup,\n useCloseOnEscape,\n useCloseOnOutsideClick,\n useFocusRedirect,\n useInitialFocus,\n usePopupModel,\n useReturnFocus,\n} from '@workday/canvas-kit-react/popup';\nimport {Text} from '@workday/canvas-kit-react/text';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\nimport {createStyles, px2rem} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst cardStyles = createStyles({\n width: px2rem(400),\n});\n\nconst bodyStyles = createStyles({\n marginBlock: '0',\n});\n\nconst columnStyles = createStyles({\n gap: system.gap.md,\n alignItems: 'flex-start',\n});\n\nconst InitialFocusOnButton = () => {\n const messageId = useUniqueId();\n const initialFocusRef = React.useRef(null);\n const model = usePopupModel({\n initialFocusRef,\n });\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusRedirect(model);\n\n return (\n <Popup model={model}>\n <Popup.Target>Initial focus: OK button</Popup.Target>\n <Popup.Popper placement={'bottom'}>\n <Popup.Card cs={cardStyles} aria-describedby={messageId}>\n <Popup.Heading>Confirmation</Popup.Heading>\n <Popup.Body>\n <Text cs={bodyStyles} id={messageId}>\n Your message has been sent!\n </Text>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton as={PrimaryButton} ref={initialFocusRef}>\n OK\n </Popup.CloseButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n );\n};\n\nconst InitialFocusOnTextInput = () => {\n const descriptionId = useUniqueId();\n const initialFocusRef = React.useRef<HTMLInputElement>(null);\n const model = usePopupModel({\n initialFocusRef,\n });\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusRedirect(model);\n\n return (\n <Popup model={model}>\n <Popup.Target>Initial focus: text input</Popup.Target>\n <Popup.Popper placement={'bottom'}>\n <Popup.Card cs={cardStyles} aria-describedby={descriptionId}>\n <Popup.Heading>Quick reply</Popup.Heading>\n <Popup.Body>\n <FormField>\n <FormField.Label>Message</FormField.Label>\n <FormField.Input as={TextInput} ref={initialFocusRef} />\n </FormField>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton>Cancel</Popup.CloseButton>\n <Popup.CloseButton as={PrimaryButton}>Send</Popup.CloseButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n );\n};\n\nconst InitialFocusOnHeading = () => {\n const headingFocusRef = React.useRef<HTMLHeadingElement>(null);\n const model = usePopupModel({\n initialFocusRef: headingFocusRef,\n });\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusRedirect(model);\n\n return (\n <Popup model={model}>\n <Popup.Target>Initial focus: heading</Popup.Target>\n <Popup.Popper placement={'bottom'}>\n <Popup.Card cs={cardStyles}>\n <Popup.Heading ref={headingFocusRef} tabIndex={-1}>\n Important notice\n </Popup.Heading>\n <Popup.Body>\n <Text cs={bodyStyles}>Review the summary below before continuing.</Text>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton as={PrimaryButton}>Continue</Popup.CloseButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n );\n};\n\nexport const InitialFocus = () => {\n return (\n <Flex cs={columnStyles}>\n <InitialFocusOnButton />\n <InitialFocusOnTextInput />\n <InitialFocusOnHeading />\n </Flex>\n );\n};\n```\n\n> **Accessibility Note**: When initial focus lands on a control **below** the title (such as the OK\n> button in the example above), assign a unique `id` to supplementary text and pass\n> `aria-describedby` on `Popup.Card`. This augments the included `aria-labelledby` reference to\n> `Popup.Heading` so screen readers can announce both the heading and any supplementary text\n> automatically. When initial focus is on the heading itself, add `tabIndex={-1}` to `Popup.Heading`\n> so the title can receive programmatic focus. Choose where focus goes based on your product and\n> accessibility requirements.\n\n### Focus Redirect\n\nFocus management is important to accessibility of popup contents. The following example shows\n`useFocusRedirect` being used to manage focus in and out of a Popup. This is very useful for\nnon-modal popups. Focus redirection tries to treat the Popup as if it were inline to the document.\nTabbing out of the Popup will close the Popup and move focus to an adjacent focusable element.\n```tsx\nimport * as React from 'react';\n\nimport {DeleteButton, SecondaryButton} from '@workday/canvas-kit-react/button';\nimport {useUniqueId} from '@workday/canvas-kit-react/common';\nimport {Box, Flex} from '@workday/canvas-kit-react/layout';\nimport {\n Popup,\n useCloseOnEscape,\n useCloseOnOutsideClick,\n useFocusRedirect,\n useInitialFocus,\n usePopupModel,\n useReturnFocus,\n} from '@workday/canvas-kit-react/popup';\nimport {createStyles, px2rem} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst cardStyles = createStyles({\n width: px2rem(400),\n});\n\nconst bodyStyles = createStyles({\n marginBlock: '0',\n});\n\nconst flexStyles = createStyles({\n gap: system.gap.md,\n padding: system.padding.xs,\n});\n\nexport const FocusRedirect = () => {\n const model = usePopupModel();\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusRedirect(model);\n\n const handleDelete = () => {\n console.log('Delete Item');\n };\n\n const popupId = useUniqueId();\n const visible = model.state.visibility !== 'hidden';\n React.useLayoutEffect(() => {\n if (visible && model.state.stackRef.current) {\n model.state.stackRef.current.setAttribute('id', popupId);\n }\n }, [model.state.stackRef, visible, popupId]);\n\n return (\n <Popup model={model}>\n <Flex cs={flexStyles}>\n <Popup.Target as={DeleteButton}>Delete Item</Popup.Target>\n <div aria-owns={popupId} style={{position: 'absolute'}}></div>\n <Popup.Popper>\n <Popup.Card cs={cardStyles}>\n <Popup.CloseIcon aria-label=\"Close\" />\n <Popup.Heading>Delete Item</Popup.Heading>\n <Popup.Body>\n <Box as=\"p\" cs={bodyStyles}>\n Are you sure you'd like to delete the item titled 'My Item'?\n </Box>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton>Cancel</Popup.CloseButton>\n <Popup.CloseButton as={DeleteButton} onClick={handleDelete}>\n Delete\n </Popup.CloseButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n <SecondaryButton>Next Focusable Button</SecondaryButton>\n <SecondaryButton>Focusable Button After Popup</SecondaryButton>\n </Flex>\n </Popup>\n );\n};\n```\n\n> **Accessibility Note**: The `useFocusRedirect` hook **will not** have any effect on the reading\n> order of a screen reader. Screen reader users may get confused or disoriented when popups are\n> portalled to the bottom of the document body. In this example, we're testing the use of\n> `aria-owns` on a sibling `<div>` element pointing to the `Popup.Card` component. This remaps the\n> hierarchy of the accessibility tree (in supported browsers) to address the reading order problem.\n> For more information, see\n> [Guides > Accessibility > Inline Popups](https://workday.github.io/canvas-kit/?path=/docs/guides-accessibility-inline-popups--docs).\n\n### Focus Trapping\n\nFocus trapping is similar to the [Focus Redirect](#focus-redirect) example, but will trap focus\ninside the popup instead of redirecting focus to adjacent focusable elements. This is necessary for\nmodal dialogs where users must focus on the contents of the dialog before proceeding.\n```tsx\nimport * as React from 'react';\n\nimport {DeleteButton, SecondaryButton} from '@workday/canvas-kit-react/button';\nimport {Box, Flex} from '@workday/canvas-kit-react/layout';\nimport {\n Popup,\n useCloseOnEscape,\n useCloseOnOutsideClick,\n useFocusTrap,\n useInitialFocus,\n usePopupModel,\n useReturnFocus,\n} from '@workday/canvas-kit-react/popup';\nimport {px2rem} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nexport const FocusTrap = () => {\n const model = usePopupModel();\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusTrap(model);\n\n const handleDelete = () => {\n console.log('Delete Item');\n };\n\n const popupId = 'popup-test-id';\n const visible = model.state.visibility !== 'hidden';\n React.useLayoutEffect(() => {\n if (visible && model.state.stackRef.current) {\n model.state.stackRef.current.setAttribute('id', popupId);\n }\n }, [model.state.stackRef, visible]);\n\n return (\n <Popup model={model}>\n <Flex cs={{gap: system.gap.sm}}>\n <Popup.Target as={DeleteButton}>Delete Item</Popup.Target>\n <div aria-owns={popupId} style={{position: 'absolute'}} />\n <Popup.Popper>\n <Popup.Card cs={{width: px2rem(400)}}>\n <Popup.CloseIcon aria-label=\"Close\" />\n <Popup.Heading>Delete Item</Popup.Heading>\n <Popup.Body>\n <Box as=\"p\" cs={{marginBlock: '0'}}>\n Are you sure you'd like to delete the item titled 'My Item'?\n </Box>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton>Cancel</Popup.CloseButton>\n <Popup.CloseButton as={DeleteButton} onClick={handleDelete}>\n Delete\n </Popup.CloseButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n <SecondaryButton>Next Focusable Button</SecondaryButton>\n <SecondaryButton>Focusable Button After Popup</SecondaryButton>\n </Flex>\n </Popup>\n );\n};\n```\n\n> **Accessibility Note**: Focus trapping will not prevent mouse users from breaking out of a focus\n> trap, nor will it prevent screen reader users from using virtual reading cursors from breaking\n> out. Consider using [Modal](/components/popups/modal/) instead when you need to focus users'\n> attention on a specific task inside of a popup..\n\n### Multiple Popups\n\nYou can render more than one `Popup` in the same view by giving each its own model. This example\npairs `Popup` with `useDialogModel` and `useModalModel` so you can compare **focus redirection**\n(Tab / Shift + Tab can move focus out of the first popup) and **focus trapping** (focus stays inside\nthe second popup until it closes). Opening one does not close the other.\n```tsx\nimport {useDialogModel} from '@workday/canvas-kit-react/dialog';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {useModalModel} from '@workday/canvas-kit-react/modal';\nimport {Popup} from '@workday/canvas-kit-react/popup';\nimport {createStyles, px2rem} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst flexStyles = createStyles({\n gap: system.gap.md,\n});\n\nconst popupStyles = createStyles({\n width: px2rem(400),\n});\n\nexport const MultiplePopups = () => {\n const dialogModel = useDialogModel();\n const modalModel = useModalModel();\n\n return (\n <Flex cs={flexStyles}>\n <Popup model={dialogModel}>\n <Popup.Target>Focus Redirect Popup</Popup.Target>\n <Popup.Popper>\n <Popup.Card cs={popupStyles}>\n <Popup.CloseIcon aria-label=\"Close\" size=\"small\" />\n <Popup.Heading>Focus Redirect Popup</Popup.Heading>\n <Popup.Body>\n <p>\n This popup uses the dialog model and will allow keyboard focus to escape when users\n press Tab or Shift + Tab.\n </p>\n </Popup.Body>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n <Popup model={modalModel}>\n <Popup.Target>Focus Trap Popup</Popup.Target>\n <Popup.Popper>\n <Popup.Card cs={popupStyles}>\n <Popup.CloseIcon aria-label=\"Close\" size=\"small\" />\n <Popup.Heading>Focus Trap Popup</Popup.Heading>\n <Popup.Body>\n <p>\n This popup uses the modal model and will trap keyboard focus when users press Tab or\n Shift + Tab.\n </p>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton>OK</Popup.CloseButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n </Flex>\n );\n};\n```\n\n### Nested Popups\n\nIf you need nested Popups within the same component, you can create multiple models and pass a\nunique model to each Popup. Popup comes with a `Popup.CloseButton` that uses a `Button` and adds\nprops via the `usePopupCloseButton` hook to ensure the popups hides and focus is returned. The `as`\ncan be used in a powerful way to do this by using `<Popup.CloseButton as={Popup.CloseButton}>` which\nwill mix in click handlers from both popups. This is not very intuitive, however. You can create\nprops that merge a click handler for both Popups by using `usePopupCloseButton` directly. The second\nparameter is props to be merged which will effectively hide both popups. Focus management is\npreserved.\n```tsx\nimport {SecondaryButton} from '@workday/canvas-kit-react/button';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {\n Popup,\n useCloseOnEscape,\n useCloseOnOutsideClick,\n useInitialFocus,\n usePopupCloseButton,\n usePopupModel,\n useReturnFocus,\n} from '@workday/canvas-kit-react/popup';\nimport {system} from '@workday/canvas-tokens-web';\n\nexport const NestedPopups = () => {\n const popup1 = usePopupModel();\n const popup2 = usePopupModel();\n\n useCloseOnOutsideClick(popup1);\n useCloseOnEscape(popup1);\n useInitialFocus(popup1);\n useReturnFocus(popup1);\n\n useCloseOnOutsideClick(popup2);\n useCloseOnEscape(popup2);\n useInitialFocus(popup2);\n useReturnFocus(popup2);\n\n const closeBothProps = usePopupCloseButton(popup1, usePopupCloseButton(popup2));\n\n return (\n <>\n <Popup model={popup1}>\n <Popup.Target>Open Popup 1</Popup.Target>\n <Popup.Popper>\n <Popup.Card aria-label=\"Popup 1\">\n <Popup.CloseIcon aria-label=\"Close\" size=\"small\" />\n <Popup.Body>\n <p style={{marginBlockStart: 0, marginBlockEnd: 0}}>Contents of Popup 1</p>\n </Popup.Body>\n <Flex cs={{gap: system.gap.md, padding: system.padding.xs}}>\n <Popup model={popup2}>\n <Popup.Target>Open Popup 2</Popup.Target>\n <Popup.Popper>\n <Popup.Card aria-label=\"Popup 2\">\n <Popup.CloseIcon aria-label=\"Close\" size=\"small\" />\n <Popup.Body>\n <p style={{marginBlockStart: 0, marginBlockEnd: 0}}>Contents of Popup 2</p>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton as={Popup.CloseButton} model={popup1}>\n Close Both (as)\n </Popup.CloseButton>\n <SecondaryButton {...closeBothProps}>Close Both (props)</SecondaryButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n </Flex>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n </>\n );\n};\n```\n\n> **Accessibility Note**: In this example, observe how users can traverse both opened popups using\n> the keyboard. This is likely to be a confusing experience for users and may necessitate focus\n> trapping inside each popup with careful consideration for setting initial focus and returning\n> focus.\n\n### Custom Target\n\nIt is common to have a custom target for your popup. Use the `as` prop to use your custom component.\nThe `Popup.Target` element will add `onClick` and `ref` to the provided component. Your provided\ntarget component must forward the `onClick` to an element for the Popup to open. The `as` will cause\n`Popup.Target` to inherit the interface of your custom target component. This means any props your\ntarget requires, `Popup.Target` now also requires. The example below has a `MyTarget` component that\nrequires a `label` prop.\n\n> **Note**: If your application needs to programmatically open a Popup without the user interacting\n> with the target button first, you'll also need to use `React.forwardRef` in your target component.\n> Without this, the Popup will open at the top-left of the window instead of around the target.\n```tsx\nimport React from 'react';\n\nimport {\n Popup,\n useCloseOnEscape,\n useCloseOnOutsideClick,\n useInitialFocus,\n usePopupModel,\n useReturnFocus,\n} from '@workday/canvas-kit-react/popup';\nimport {px2rem} from '@workday/canvas-kit-styling';\n\ninterface MyTargetProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n label: string;\n}\n\nconst MyTarget = React.forwardRef<HTMLButtonElement, MyTargetProps>(({label, ...props}, ref) => {\n return (\n <button {...props} ref={ref}>\n {label}\n </button>\n );\n});\n\nexport const CustomTarget = () => {\n const model = usePopupModel();\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n\n return (\n <Popup model={model}>\n <Popup.Target as={MyTarget} label=\"Open\" />\n <Popup.Popper>\n <Popup.Card cs={{minWidth: px2rem(320)}}>\n <Popup.CloseIcon aria-label=\"Close\" />\n <Popup.Heading>Popup</Popup.Heading>\n <Popup.Body>Contents</Popup.Body>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n );\n};\n```\n\n> **Accessibility Note**: Custom targets must be keyboard focusable, otherwise users will not be\n> able to access the popup. Bear in mind that click handlers only work with the keyboard when\n> applied to HTML `<button>` elements and it is **strongly recommended** to base your custom target\n> on a `<button>` element. Otherwise, you will be required to build in your own custom keyboard\n> event handlers for invoking the popup.\n\n### Full Screen API\n\nBy default, popups are created as children of the `document.body` element, but the `PopupStack`\nsupports the [Fullscreen API](https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API). When\nfullscreen is entered, the `PopupStack` will automatically create a new stacking context for all\nfuture popups. Any existing popups will disappear, but not be removed. They disappear because the\nfullscreen API is only showing content within the fullscreen element. There are instances where a\npopup may not close when fullscreen is exited:\n\n- The escape key is used to exit fullscreen\n- There is a button to exit fullscreen, but the popup doesn't use `useCloseOnOutsideClick`\n\nIf fullscreen is exited, popups within the fullscreen stacking context are not removed or\ntransferred automatically. If you do not handle this case, the popup may not render correctly. This\nexample shows a popup that closes when fullscreen is entered/exited and another popup that transfers\nthe popup's stack context when entering/exiting fullscreen.\n```tsx\nimport * as React from 'react';\nimport screenfull from 'screenfull';\n\nimport {SecondaryButton} from '@workday/canvas-kit-react/button';\nimport {useIsFullscreen} from '@workday/canvas-kit-react/common';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {\n Popup,\n useCloseOnEscape,\n useCloseOnFullscreenExit,\n useCloseOnOutsideClick,\n useFocusTrap,\n useInitialFocus,\n usePopupModel,\n useReturnFocus,\n useTransferOnFullscreenEnter,\n useTransferOnFullscreenExit,\n} from '@workday/canvas-kit-react/popup';\nimport {px2rem} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst SelfClosePopup = () => {\n const model = usePopupModel();\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusTrap(model);\n useCloseOnFullscreenExit(model);\n\n return (\n <Popup model={model}>\n <Popup.Target>Open Self-close Popup</Popup.Target>\n <Popup.Popper>\n <Popup.Card cs={{width: px2rem(400), padding: system.padding.md}}>\n <Popup.CloseIcon aria-label=\"Close\" />\n <Popup.Heading>Self-close Popup</Popup.Heading>\n <Popup.Body>\n <p>\n When in fullscreen, the escape key will be highjacked by the browser to exit\n fullscreen and <code>useCloseOnEscape</code> hook will not receive the escape key. To\n close when fullscreen is exited, use the <code>useCloseOnFullscreenExit</code> hook.\n </p>\n </Popup.Body>\n <Popup.CloseButton>Close</Popup.CloseButton>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n );\n};\n\nconst TransferClosePopup = () => {\n const model = usePopupModel();\n\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusTrap(model);\n useTransferOnFullscreenEnter(model);\n useTransferOnFullscreenExit(model);\n\n return (\n <Popup model={model}>\n <Popup.Target>Open Transfer Popup</Popup.Target>\n <Popup.Popper>\n <Popup.Card cs={{width: px2rem(400), padding: system.padding.md}}>\n <Popup.CloseIcon aria-label=\"Close\" />\n <Popup.Heading>Transfer Popup</Popup.Heading>\n <Popup.Body>\n <p>\n When in fullscreen, the escape key will be highjacked by the browser to exit\n fullscreen and <code>useCloseOnEscape</code> hook will not receive the escape key. To\n close when fullscreen is exited, use the <code>useTransferOnFullscreenExit</code>{' '}\n hook.\n </p>\n </Popup.Body>\n <Popup.CloseButton>Close</Popup.CloseButton>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n );\n};\n\nexport const FullScreen = () => {\n // you could make this a hook depending on which fullscreen library your application uses\n const fullscreenElementRef = React.useRef<HTMLDivElement>();\n const isFullscreen = useIsFullscreen();\n\n const enterFullScreen = () => {\n screenfull.request(fullscreenElementRef.current);\n };\n\n const exitFullscreen = () => {\n screenfull.exit();\n };\n\n return (\n <>\n <SecondaryButton onClick={enterFullScreen}>Open Fullscreen</SecondaryButton>\n <Flex\n ref={fullscreenElementRef}\n cs={{alignItems: 'center', justifyContent: 'center', background: system.color.bg.default}}\n >\n <Flex cs={{gap: system.gap.md}}>\n <SelfClosePopup />\n <TransferClosePopup />\n {isFullscreen ? (\n <SecondaryButton onClick={exitFullscreen}>Exit fullscreen</SecondaryButton>\n ) : null}\n </Flex>\n </Flex>\n </>\n );\n};\n```\n\n### Opening an External Window\n\nA popup can open an external window. This isn't supported directly. The `Popup.Popper` subcomponent\nis replaced with a custom subcomponent that connects to the Popup model and controls the lifecycle\nof the extenal window. Be sure to connect the `unload` event of both the parent `window` and the\nexternal child `window` to the lifecycle of the Popup model to prevent memory leaks or zombie\nwindows.\n```tsx\nimport React from 'react';\nimport ReactDOM from 'react-dom';\n\nimport {SecondaryButton} from '@workday/canvas-kit-react/button';\nimport {\n CanvasProvider,\n ContentDirection,\n PartialEmotionCanvasTheme,\n createSubcomponent,\n useMount,\n useTheme,\n} from '@workday/canvas-kit-react/common';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {Popup, usePopupModel} from '@workday/canvas-kit-react/popup';\nimport {Tooltip} from '@workday/canvas-kit-react/tooltip';\nimport {createStyles} from '@workday/canvas-kit-styling';\nimport {infoIcon} from '@workday/canvas-system-icons-web';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst mainContentStyles = createStyles({\n padding: system.padding.md,\n});\n\nexport interface ExternalWindowPortalProps {\n /**\n * Child components of WindowPortal\n */\n children: React.ReactNode;\n /**\n * Callback to close the popup\n */\n onWindowClose?: () => void;\n /**\n * Width of the popup window\n */\n width?: number;\n /**\n * Height of the popup window\n */\n height?: number;\n /**\n * The name of the popup window. If another popup opens with the same name, that instance will\n * be reused. Use caution with setting this value\n */\n target?: string;\n}\n\nasync function copyAssets(sourceDoc: Document, targetDoc: Document) {\n for (const font of (sourceDoc as any).fonts.values()) {\n (targetDoc as any).fonts.add(font);\n\n font.load();\n }\n\n await (targetDoc as any).fonts.ready;\n\n // The current ES lib version doesn't include iterable interfaces, so we cast as an iterable\n for (const styleSheet of sourceDoc.styleSheets as StyleSheetList & Iterable<CSSStyleSheet>) {\n if (styleSheet.cssRules) {\n // text based styles\n const styleEl = targetDoc.createElement('style');\n for (const cssRule of styleSheet.cssRules as CSSRuleList & Iterable<CSSRule>) {\n styleEl.appendChild(targetDoc.createTextNode(cssRule.cssText));\n }\n targetDoc.head.appendChild(styleEl);\n } else if (styleSheet.href) {\n // link based styles\n const linkEl = targetDoc.createElement('link');\n\n linkEl.rel = 'stylesheet';\n linkEl.href = styleSheet.href;\n targetDoc.head.appendChild(linkEl);\n }\n }\n}\n\nconst ExternalWindowPortal = ({\n children,\n width = 300,\n height = 500,\n target = '',\n onWindowClose,\n}: ExternalWindowPortalProps) => {\n const [portalElement, setPortalElement] = React.useState<HTMLDivElement | null>(null);\n\n useMount(() => {\n const newWindow = window.open(\n '', // url\n target,\n `width=${width},height=${height},left=100,top=100,popup=true`\n );\n\n if (newWindow) {\n // copy fonts and styles\n copyAssets(document, newWindow.document);\n\n const element = newWindow.document.createElement('div');\n newWindow.document.body.appendChild(element);\n setPortalElement(element);\n } else {\n onWindowClose();\n }\n\n const closeWindow = event => {\n onWindowClose();\n };\n\n window.addEventListener('unload', closeWindow);\n newWindow?.addEventListener('unload', closeWindow);\n\n return () => {\n window.removeEventListener('unload', closeWindow);\n newWindow?.removeEventListener('unload', closeWindow);\n newWindow?.close();\n };\n });\n\n if (!portalElement) {\n return null;\n }\n\n return ReactDOM.createPortal(<CanvasProvider>{children}</CanvasProvider>, portalElement);\n};\n\nconst PopupExternalWindow = createSubcomponent()({\n displayName: 'Popup.ExternalWindow',\n modelHook: usePopupModel,\n})<ExternalWindowPortalProps>(({children, ...elemProps}, Element, model) => {\n if (model.state.visibility === 'visible') {\n return (\n <ExternalWindowPortal onWindowClose={model.events.hide} {...elemProps}>\n {children}\n </ExternalWindowPortal>\n );\n }\n\n return null;\n});\n\nexport const ExternalWindow = () => {\n // useTheme is filling in the Canvas theme object if any keys are missing\n const canvasTheme: PartialEmotionCanvasTheme = useTheme({\n canvas: {\n // Switch to `ContentDirection.RTL` to change direction\n direction: ContentDirection.LTR,\n },\n });\n\n const model = usePopupModel();\n\n return (\n <CanvasProvider theme={canvasTheme}>\n <main className={mainContentStyles}>\n <p>Popup that opens a new Operating System Window</p>\n <Popup model={model}>\n <Tooltip title=\"Open External Window Tooltip\">\n <Popup.Target>Open External Window</Popup.Target>\n </Tooltip>\n <PopupExternalWindow>\n <p>External Window Contents! Mouse over the info icon to get a tooltip</p>\n <Flex cs={{gap: system.gap.sm}}>\n <Tooltip title=\"More information\">\n <SecondaryButton icon={infoIcon} />\n </Tooltip>\n <Popup.CloseButton>Close Window</Popup.CloseButton>\n </Flex>\n </PopupExternalWindow>\n </Popup>\n <p>Popup visibility: {model.state.visibility}</p>\n </main>\n </CanvasProvider>\n );\n};\n```\n\n### RTL\n\nThe Popup component automatically handles right-to-left rendering.\n\n> **Note:** This example shows an inaccessible open card for demonstration purposes.\n```tsx\nimport {DeleteButton, SecondaryButton} from '@workday/canvas-kit-react/button';\nimport {CanvasProvider} from '@workday/canvas-kit-react/common';\nimport {Box, Flex} from '@workday/canvas-kit-react/layout';\nimport {Popup} from '@workday/canvas-kit-react/popup';\nimport {px2rem} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nexport const RTL = () => {\n return (\n <CanvasProvider dir=\"rtl\">\n <Popup.Card cs={{width: px2rem(400)}}>\n <Popup.CloseIcon aria-label=\"\u05E1\u05D2\u05D5\u05E8\" />\n <Popup.Heading>\u05DC\u05DE\u05D7\u05D5\u05E7 \u05E4\u05E8\u05D9\u05D8</Popup.Heading>\n <Popup.Body>\n <Box as=\"p\" cs={{marginBlock: '0'}}>\n \u05D4\u05D0\u05DD \u05D1\u05E8\u05E6\u05D5\u05E0\u05DA \u05DC\u05DE\u05D7\u05D5\u05E7 \u05E4\u05E8\u05D9\u05D8 \u05D6\u05D4\n </Box>\n </Popup.Body>\n <Popup.ButtonGroup>\n <SecondaryButton>\u05DC\u05B0\u05D1\u05B7\u05D8\u05B5\u05DC</SecondaryButton>\n <DeleteButton>\u05DC\u05B4\u05DE\u05B0\u05D7\u05D5\u05B9\u05E7</DeleteButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </CanvasProvider>\n );\n};\n```\n\n## Accessibility\n\nPopup content is usually portaled to the bottom of the `document.body`, which can affect **reading\norder for screen readers** and **keyboard focus order**. For more information about Popup\naccessibility, check out our documentation at\n[Guides > Accessibility > Inline Popups](https://workday.github.io/canvas-kit/?path=/docs/guides-accessibility-inline-popups--docs).\n\n- For non-modal dialogs with `aria-owns` built-in to improve reading order for screen readers (that\n support it), check out the [**Dialog**](/components/popups/dialog/) component.\n- For modal dialogs with built-in overlays and focus traps, check out the\n [**Modal**](/components/popups/modal/) component.\n\n## Component API\n\n<>\n \n\n \n</>\n\n## Hooks\n\n<>\n \n\n{' '}\n\n{' '}\n\n{' '}\n\n{' '}\n\n{' '}\n\n{' '}\n\n{' '}\n\n{' '}\n\n{' '}\n\n{' '}\n\n{' '}\n\n{' '}\n\n \n</>\n\n## Specifications\n\n",
|
|
428
|
-
accessibilityProse: "## Accessibility\n\nPopup content is usually portaled to the bottom of the `document.body`, which can affect **reading\norder for screen readers** and **keyboard focus order**. For more information about Popup\naccessibility, check out our documentation at\n[Guides > Accessibility > Inline Popups](https://workday.github.io/canvas-kit/?path=/docs/guides-accessibility-inline-popups--docs).\n\n- For non-modal dialogs with `aria-owns` built-in to improve reading order for screen readers (that\n support it), check out the [**Dialog**](/components/popups/dialog/) component.\n- For modal dialogs with built-in overlays and focus traps, check out the\n [**Modal**](/components/popups/modal/) component."
|
|
427
|
+
mdxProse: "# Canvas Kit Popups\n\nA \"popup\" is a classification for a type of stacked UI element that appears \"on top\" of statically\npositioned content. Tooltips, Modals, Dropdown menus, etc are all examples of \"popups\". Canvas Kit\nhas a \"stack manager\" system for managing these popups. Different types of popups have different\nrequirements of behavior for UX and accessibility - we can call them behaviors, capabilities, or\ntraits. Canvas Kit comes with a number of [behavioral hooks](#hooks) in the form of React Hooks.\n\nYou should use the most semantic component for your use-case before using `Popup` directly, like\n`Modal`, which already has the correct behaviors built-in. If no component already exists that\nmatches your use case, you can use `Popup` and use our [hooks](#hooks). The `Popup` component comes\nwith a `Popup.Popper` subcomponent that positions a popup using [PopperJS](https://popper.js.org/)\nthat registers a popup with the `PopupStack` automatically and sets the popup model's `placement`\nproperty. `Popup.Popper` component and hooks work with the stack management system for correct\nrendering and accessibility behavior. If you cannot use `Popup.Popper`, use the\n[usePopupStack](#usepoupstack) hook to properly register and deregister the popup at the correct\ntime. If you cannot use our hooks, consider upgrading your component to use Hooks. If you cannot do\nthat, you'll have to look up the `PopupStack` package for the direct API and have a look at the\nsource code for our hooks into the `PopupStack` API.\n\nThis package comes with everything you need to build Popup UIs.\n\n[Buttons](/components/buttons/button)\n\n## Installation\n\n```sh\nyarn add @workday/canvas-kit-react\n```\n\n## Usage\n\nThe `Popup` component is a generic\n[Compound Component](/get-started/for-developers/documentation/compound-components/) that is used to\nbuild popup UIs that are not already covered by Canvas Kit.\n\n### Basic Example\n\nThe Popup has no pre-defined behaviors built in, therefore the `usePopupModel` must always be used\nto create a new `model`. This `model` is then used by all behavior hooks to apply additional popup\nbehaviors to the compound component group. The following example creates a typical popup around a\ntarget element and adds `useCloseOnOutsideClick`, `useCloseOnEscape`, `useInitialFocus`,\n`useReturnFocus`, and `useFocusRedirect` behaviors. You can read through the [hooks](#hooks) section\nto learn about all the popup behaviors. For accessibility, these behaviors should be included most\nof the time.\n```tsx\nimport {DeleteButton} from '@workday/canvas-kit-react/button';\nimport {Box} from '@workday/canvas-kit-react/layout';\nimport {\n Popup,\n useCloseOnEscape,\n useCloseOnOutsideClick,\n useFocusRedirect,\n useInitialFocus,\n usePopupModel,\n useReturnFocus,\n} from '@workday/canvas-kit-react/popup';\nimport {createStyles, px2rem} from '@workday/canvas-kit-styling';\n\nconst cardStyles = createStyles({\n width: px2rem(400),\n});\n\nconst bodyStyles = createStyles({\n marginBlock: '0',\n});\n\nexport const Basic = () => {\n const model = usePopupModel();\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusRedirect(model);\n\n const handleDelete = () => {\n console.log('Delete Item');\n };\n\n return (\n <Popup model={model}>\n <Popup.Target as={DeleteButton}>Delete Item</Popup.Target>\n <Popup.Popper placement=\"top\">\n <Popup.Card cs={cardStyles}>\n <Popup.CloseIcon aria-label=\"Close\" />\n <Popup.Heading>Delete Item</Popup.Heading>\n <Popup.Body>\n <Box as=\"p\" cs={bodyStyles}>\n Are you sure you'd like to delete the item titled 'My Item'?\n </Box>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton>Cancel</Popup.CloseButton>\n <Popup.CloseButton as={DeleteButton} onClick={handleDelete}>\n Delete\n </Popup.CloseButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n );\n};\n```\n\n### Initial Focus\n\nIf you want focus to move to a specific element when the popup is opened, set the `initialFocusRef`\nof the model. This is useful for popups that don't have a Close icon button near the top right of\nthe popup. In general, we recommend setting focus to the first interactive component inside the\npopup that is the least destructive action.\n```tsx\nimport React from 'react';\n\nimport {PrimaryButton} from '@workday/canvas-kit-react/button';\nimport {useUniqueId} from '@workday/canvas-kit-react/common';\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {\n Popup,\n useCloseOnEscape,\n useCloseOnOutsideClick,\n useFocusRedirect,\n useInitialFocus,\n usePopupModel,\n useReturnFocus,\n} from '@workday/canvas-kit-react/popup';\nimport {Text} from '@workday/canvas-kit-react/text';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\nimport {createStyles, px2rem} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst cardStyles = createStyles({\n width: px2rem(400),\n});\n\nconst bodyStyles = createStyles({\n marginBlock: '0',\n});\n\nconst columnStyles = createStyles({\n gap: system.gap.md,\n alignItems: 'flex-start',\n});\n\nconst InitialFocusOnButton = () => {\n const messageId = useUniqueId();\n const initialFocusRef = React.useRef(null);\n const model = usePopupModel({\n initialFocusRef,\n });\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusRedirect(model);\n\n return (\n <Popup model={model}>\n <Popup.Target>Initial focus: OK button</Popup.Target>\n <Popup.Popper placement={'bottom'}>\n <Popup.Card cs={cardStyles} aria-describedby={messageId}>\n <Popup.Heading>Confirmation</Popup.Heading>\n <Popup.Body>\n <Text cs={bodyStyles} id={messageId}>\n Your message has been sent!\n </Text>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton as={PrimaryButton} ref={initialFocusRef}>\n OK\n </Popup.CloseButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n );\n};\n\nconst InitialFocusOnTextInput = () => {\n const descriptionId = useUniqueId();\n const initialFocusRef = React.useRef<HTMLInputElement>(null);\n const model = usePopupModel({\n initialFocusRef,\n });\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusRedirect(model);\n\n return (\n <Popup model={model}>\n <Popup.Target>Initial focus: text input</Popup.Target>\n <Popup.Popper placement={'bottom'}>\n <Popup.Card cs={cardStyles} aria-describedby={descriptionId}>\n <Popup.Heading>Quick reply</Popup.Heading>\n <Popup.Body>\n <FormField>\n <FormField.Label>Message</FormField.Label>\n <FormField.Input as={TextInput} ref={initialFocusRef} />\n </FormField>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton>Cancel</Popup.CloseButton>\n <Popup.CloseButton as={PrimaryButton}>Send</Popup.CloseButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n );\n};\n\nconst InitialFocusOnHeading = () => {\n const headingFocusRef = React.useRef<HTMLHeadingElement>(null);\n const model = usePopupModel({\n initialFocusRef: headingFocusRef,\n });\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusRedirect(model);\n\n return (\n <Popup model={model}>\n <Popup.Target>Initial focus: heading</Popup.Target>\n <Popup.Popper placement={'bottom'}>\n <Popup.Card cs={cardStyles}>\n <Popup.Heading ref={headingFocusRef} tabIndex={-1}>\n Important notice\n </Popup.Heading>\n <Popup.Body>\n <Text cs={bodyStyles}>Review the summary below before continuing.</Text>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton as={PrimaryButton}>Continue</Popup.CloseButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n );\n};\n\nexport const InitialFocus = () => {\n return (\n <Flex cs={columnStyles}>\n <InitialFocusOnButton />\n <InitialFocusOnTextInput />\n <InitialFocusOnHeading />\n </Flex>\n );\n};\n```\n\n> **Accessibility Note**: When initial focus lands on a control **below** the title (such as the OK\n> button in the example above), assign a unique `id` to supplementary text and pass\n> `aria-describedby` on `Popup.Card`. This augments the included `aria-labelledby` reference to\n> `Popup.Heading` so screen readers can announce both the heading and any supplementary text\n> automatically. When initial focus is on the heading itself, add `tabIndex={-1}` to `Popup.Heading`\n> so the title can receive programmatic focus. Choose where focus goes based on your product and\n> accessibility requirements.\n\n### Focus Redirect\n\nFocus management is important to accessibility of popup contents. The following example shows\n`useFocusRedirect` being used to manage focus in and out of a Popup. This is very useful for\nnon-modal popups. Focus redirection tries to treat the Popup as if it were inline to the document.\nTabbing out of the Popup will close the Popup and move focus to an adjacent focusable element.\n```tsx\nimport * as React from 'react';\n\nimport {DeleteButton, SecondaryButton} from '@workday/canvas-kit-react/button';\nimport {useUniqueId} from '@workday/canvas-kit-react/common';\nimport {Box, Flex} from '@workday/canvas-kit-react/layout';\nimport {\n Popup,\n useCloseOnEscape,\n useCloseOnOutsideClick,\n useFocusRedirect,\n useInitialFocus,\n usePopupModel,\n useReturnFocus,\n} from '@workday/canvas-kit-react/popup';\nimport {createStyles, px2rem} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst cardStyles = createStyles({\n width: px2rem(400),\n});\n\nconst bodyStyles = createStyles({\n marginBlock: '0',\n});\n\nconst flexStyles = createStyles({\n gap: system.gap.md,\n padding: system.padding.xs,\n});\n\nexport const FocusRedirect = () => {\n const model = usePopupModel();\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusRedirect(model);\n\n const handleDelete = () => {\n console.log('Delete Item');\n };\n\n const popupId = useUniqueId();\n const visible = model.state.visibility !== 'hidden';\n React.useLayoutEffect(() => {\n if (visible && model.state.stackRef.current) {\n model.state.stackRef.current.setAttribute('id', popupId);\n }\n }, [model.state.stackRef, visible, popupId]);\n\n return (\n <Popup model={model}>\n <Flex cs={flexStyles}>\n <Popup.Target as={DeleteButton}>Delete Item</Popup.Target>\n <div aria-owns={popupId} style={{position: 'absolute'}}></div>\n <Popup.Popper>\n <Popup.Card cs={cardStyles}>\n <Popup.CloseIcon aria-label=\"Close\" />\n <Popup.Heading>Delete Item</Popup.Heading>\n <Popup.Body>\n <Box as=\"p\" cs={bodyStyles}>\n Are you sure you'd like to delete the item titled 'My Item'?\n </Box>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton>Cancel</Popup.CloseButton>\n <Popup.CloseButton as={DeleteButton} onClick={handleDelete}>\n Delete\n </Popup.CloseButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n <SecondaryButton>Next Focusable Button</SecondaryButton>\n <SecondaryButton>Focusable Button After Popup</SecondaryButton>\n </Flex>\n </Popup>\n );\n};\n```\n\n> **Accessibility Note**: The `useFocusRedirect` hook **will not** have any effect on the reading\n> order of a screen reader. Screen reader users may get confused or disoriented when popups are\n> portalled to the bottom of the document body. In this example, we're testing the use of\n> `aria-owns` on a sibling `<div>` element pointing to the `Popup.Card` component. This remaps the\n> hierarchy of the accessibility tree (in supported browsers) to address the reading order problem.\n> For more information, see\n> [Guides > Accessibility > Inline Popups](https://workday.github.io/canvas-kit/?path=/docs/guides-accessibility-inline-popups--docs).\n\n### Focus Trapping\n\nFocus trapping is similar to the [Focus Redirect](#focus-redirect) example, but will trap focus\ninside the popup instead of redirecting focus to adjacent focusable elements. This is necessary for\nmodal dialogs where users must focus on the contents of the dialog before proceeding.\n```tsx\nimport * as React from 'react';\n\nimport {DeleteButton, SecondaryButton} from '@workday/canvas-kit-react/button';\nimport {Box, Flex} from '@workday/canvas-kit-react/layout';\nimport {\n Popup,\n useCloseOnEscape,\n useCloseOnOutsideClick,\n useFocusTrap,\n useInitialFocus,\n usePopupModel,\n useReturnFocus,\n} from '@workday/canvas-kit-react/popup';\nimport {px2rem} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nexport const FocusTrap = () => {\n const model = usePopupModel();\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusTrap(model);\n\n const handleDelete = () => {\n console.log('Delete Item');\n };\n\n const popupId = 'popup-test-id';\n const visible = model.state.visibility !== 'hidden';\n React.useLayoutEffect(() => {\n if (visible && model.state.stackRef.current) {\n model.state.stackRef.current.setAttribute('id', popupId);\n }\n }, [model.state.stackRef, visible]);\n\n return (\n <Popup model={model}>\n <Flex cs={{gap: system.gap.sm}}>\n <Popup.Target as={DeleteButton}>Delete Item</Popup.Target>\n <div aria-owns={popupId} style={{position: 'absolute'}} />\n <Popup.Popper>\n <Popup.Card cs={{width: px2rem(400)}}>\n <Popup.CloseIcon aria-label=\"Close\" />\n <Popup.Heading>Delete Item</Popup.Heading>\n <Popup.Body>\n <Box as=\"p\" cs={{marginBlock: '0'}}>\n Are you sure you'd like to delete the item titled 'My Item'?\n </Box>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton>Cancel</Popup.CloseButton>\n <Popup.CloseButton as={DeleteButton} onClick={handleDelete}>\n Delete\n </Popup.CloseButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n <SecondaryButton>Next Focusable Button</SecondaryButton>\n <SecondaryButton>Focusable Button After Popup</SecondaryButton>\n </Flex>\n </Popup>\n );\n};\n```\n\n> **Accessibility Note**: Focus trapping will not prevent mouse users from breaking out of a focus\n> trap, nor will it prevent screen reader users from using virtual reading cursors from breaking\n> out. Consider using [Modal](/components/popups/modal/) instead when you need to focus users'\n> attention on a specific task inside of a popup..\n\n### Multiple Popups\n\nYou can render more than one `Popup` in the same view by giving each its own model. This example\npairs `Popup` with `useDialogModel` and `useModalModel` so you can compare **focus redirection**\n(Tab / Shift + Tab can move focus out of the first popup) and **focus trapping** (focus stays inside\nthe second popup until it closes). Opening one does not close the other.\n```tsx\nimport {useDialogModel} from '@workday/canvas-kit-react/dialog';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {useModalModel} from '@workday/canvas-kit-react/modal';\nimport {Popup} from '@workday/canvas-kit-react/popup';\nimport {createStyles, px2rem} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst flexStyles = createStyles({\n gap: system.gap.md,\n});\n\nconst popupStyles = createStyles({\n width: px2rem(400),\n});\n\nexport const MultiplePopups = () => {\n const dialogModel = useDialogModel();\n const modalModel = useModalModel();\n\n return (\n <Flex cs={flexStyles}>\n <Popup model={dialogModel}>\n <Popup.Target>Focus Redirect Popup</Popup.Target>\n <Popup.Popper>\n <Popup.Card cs={popupStyles}>\n <Popup.CloseIcon aria-label=\"Close\" size=\"small\" />\n <Popup.Heading>Focus Redirect Popup</Popup.Heading>\n <Popup.Body>\n <p>\n This popup uses the dialog model and will allow keyboard focus to escape when users\n press Tab or Shift + Tab.\n </p>\n </Popup.Body>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n <Popup model={modalModel}>\n <Popup.Target>Focus Trap Popup</Popup.Target>\n <Popup.Popper>\n <Popup.Card cs={popupStyles}>\n <Popup.CloseIcon aria-label=\"Close\" size=\"small\" />\n <Popup.Heading>Focus Trap Popup</Popup.Heading>\n <Popup.Body>\n <p>\n This popup uses the modal model and will trap keyboard focus when users press Tab or\n Shift + Tab.\n </p>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton>OK</Popup.CloseButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n </Flex>\n );\n};\n```\n\n### Nested Popups\n\nIf you need nested Popups within the same component, you can create multiple models and pass a\nunique model to each Popup. Popup comes with a `Popup.CloseButton` that uses a `Button` and adds\nprops via the `usePopupCloseButton` hook to ensure the popups hides and focus is returned. The `as`\ncan be used in a powerful way to do this by using `<Popup.CloseButton as={Popup.CloseButton}>` which\nwill mix in click handlers from both popups. This is not very intuitive, however. You can create\nprops that merge a click handler for both Popups by using `usePopupCloseButton` directly. The second\nparameter is props to be merged which will effectively hide both popups. Focus management is\npreserved.\n```tsx\nimport {SecondaryButton} from '@workday/canvas-kit-react/button';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {\n Popup,\n useCloseOnEscape,\n useCloseOnOutsideClick,\n useInitialFocus,\n usePopupCloseButton,\n usePopupModel,\n useReturnFocus,\n} from '@workday/canvas-kit-react/popup';\nimport {system} from '@workday/canvas-tokens-web';\n\nexport const NestedPopups = () => {\n const popup1 = usePopupModel();\n const popup2 = usePopupModel();\n\n useCloseOnOutsideClick(popup1);\n useCloseOnEscape(popup1);\n useInitialFocus(popup1);\n useReturnFocus(popup1);\n\n useCloseOnOutsideClick(popup2);\n useCloseOnEscape(popup2);\n useInitialFocus(popup2);\n useReturnFocus(popup2);\n\n const closeBothProps = usePopupCloseButton(popup1, usePopupCloseButton(popup2));\n\n return (\n <>\n <Popup model={popup1}>\n <Popup.Target>Open Popup 1</Popup.Target>\n <Popup.Popper>\n <Popup.Card aria-label=\"Popup 1\">\n <Popup.CloseIcon aria-label=\"Close\" size=\"small\" />\n <Popup.Body>\n <p style={{marginBlockStart: 0, marginBlockEnd: 0}}>Contents of Popup 1</p>\n </Popup.Body>\n <Flex cs={{gap: system.gap.md, padding: system.padding.xs}}>\n <Popup model={popup2}>\n <Popup.Target>Open Popup 2</Popup.Target>\n <Popup.Popper>\n <Popup.Card aria-label=\"Popup 2\">\n <Popup.CloseIcon aria-label=\"Close\" size=\"small\" />\n <Popup.Body>\n <p style={{marginBlockStart: 0, marginBlockEnd: 0}}>Contents of Popup 2</p>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton as={Popup.CloseButton} model={popup1}>\n Close Both (as)\n </Popup.CloseButton>\n <SecondaryButton {...closeBothProps}>Close Both (props)</SecondaryButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n </Flex>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n </>\n );\n};\n```\n\n> **Accessibility Note**: In this example, observe how users can traverse both opened popups using\n> the keyboard. This is likely to be a confusing experience for users and may necessitate focus\n> trapping inside each popup with careful consideration for setting initial focus and returning\n> focus.\n\n### Custom Target\n\nIt is common to have a custom target for your popup. Use the `as` prop to use your custom component.\nThe `Popup.Target` element will add `onClick` and `ref` to the provided component. Your provided\ntarget component must forward the `onClick` to an element for the Popup to open. The `as` will cause\n`Popup.Target` to inherit the interface of your custom target component. This means any props your\ntarget requires, `Popup.Target` now also requires. The example below has a `MyTarget` component that\nrequires a `label` prop.\n\n> **Note**: If your application needs to programmatically open a Popup without the user interacting\n> with the target button first, you'll also need to use `React.forwardRef` in your target component.\n> Without this, the Popup will open at the top-left of the window instead of around the target.\n```tsx\nimport React from 'react';\n\nimport {\n Popup,\n useCloseOnEscape,\n useCloseOnOutsideClick,\n useInitialFocus,\n usePopupModel,\n useReturnFocus,\n} from '@workday/canvas-kit-react/popup';\nimport {px2rem} from '@workday/canvas-kit-styling';\n\ninterface MyTargetProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n label: string;\n}\n\nconst MyTarget = React.forwardRef<HTMLButtonElement, MyTargetProps>(({label, ...props}, ref) => {\n return (\n <button {...props} ref={ref}>\n {label}\n </button>\n );\n});\n\nexport const CustomTarget = () => {\n const model = usePopupModel();\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n\n return (\n <Popup model={model}>\n <Popup.Target as={MyTarget} label=\"Open\" />\n <Popup.Popper>\n <Popup.Card cs={{minWidth: px2rem(320)}}>\n <Popup.CloseIcon aria-label=\"Close\" />\n <Popup.Heading>Popup</Popup.Heading>\n <Popup.Body>Contents</Popup.Body>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n );\n};\n```\n\n> **Accessibility Note**: Custom targets must be keyboard focusable, otherwise users will not be\n> able to access the popup. Bear in mind that click handlers only work with the keyboard when\n> applied to HTML `<button>` elements and it is **strongly recommended** to base your custom target\n> on a `<button>` element. Otherwise, you will be required to build in your own custom keyboard\n> event handlers for invoking the popup.\n\n### Full Screen API\n\nBy default, popups are created as children of the `document.body` element, but the `PopupStack`\nsupports the [Fullscreen API](https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API). When\nfullscreen is entered, the `PopupStack` will automatically create a new stacking context for all\nfuture popups. Any existing popups will disappear, but not be removed. They disappear because the\nfullscreen API is only showing content within the fullscreen element. There are instances where a\npopup may not close when fullscreen is exited:\n\n- The escape key is used to exit fullscreen\n- There is a button to exit fullscreen, but the popup doesn't use `useCloseOnOutsideClick`\n\nIf fullscreen is exited, popups within the fullscreen stacking context are not removed or\ntransferred automatically. If you do not handle this case, the popup may not render correctly. This\nexample shows a popup that closes when fullscreen is entered/exited and another popup that transfers\nthe popup's stack context when entering/exiting fullscreen.\n```tsx\nimport * as React from 'react';\nimport screenfull from 'screenfull';\n\nimport {SecondaryButton} from '@workday/canvas-kit-react/button';\nimport {useIsFullscreen} from '@workday/canvas-kit-react/common';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {\n Popup,\n useCloseOnEscape,\n useCloseOnFullscreenExit,\n useCloseOnOutsideClick,\n useFocusTrap,\n useInitialFocus,\n usePopupModel,\n useReturnFocus,\n useTransferOnFullscreenEnter,\n useTransferOnFullscreenExit,\n} from '@workday/canvas-kit-react/popup';\nimport {px2rem} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst SelfClosePopup = () => {\n const model = usePopupModel();\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusTrap(model);\n useCloseOnFullscreenExit(model);\n\n return (\n <Popup model={model}>\n <Popup.Target>Open Self-close Popup</Popup.Target>\n <Popup.Popper>\n <Popup.Card cs={{width: px2rem(400), padding: system.padding.md}}>\n <Popup.CloseIcon aria-label=\"Close\" />\n <Popup.Heading>Self-close Popup</Popup.Heading>\n <Popup.Body>\n <p>\n When in fullscreen, the escape key will be highjacked by the browser to exit\n fullscreen and <code>useCloseOnEscape</code> hook will not receive the escape key. To\n close when fullscreen is exited, use the <code>useCloseOnFullscreenExit</code> hook.\n </p>\n </Popup.Body>\n <Popup.CloseButton>Close</Popup.CloseButton>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n );\n};\n\nconst TransferClosePopup = () => {\n const model = usePopupModel();\n\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusTrap(model);\n useTransferOnFullscreenEnter(model);\n useTransferOnFullscreenExit(model);\n\n return (\n <Popup model={model}>\n <Popup.Target>Open Transfer Popup</Popup.Target>\n <Popup.Popper>\n <Popup.Card cs={{width: px2rem(400), padding: system.padding.md}}>\n <Popup.CloseIcon aria-label=\"Close\" />\n <Popup.Heading>Transfer Popup</Popup.Heading>\n <Popup.Body>\n <p>\n When in fullscreen, the escape key will be highjacked by the browser to exit\n fullscreen and <code>useCloseOnEscape</code> hook will not receive the escape key. To\n close when fullscreen is exited, use the <code>useTransferOnFullscreenExit</code>{' '}\n hook.\n </p>\n </Popup.Body>\n <Popup.CloseButton>Close</Popup.CloseButton>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n );\n};\n\nexport const FullScreen = () => {\n // you could make this a hook depending on which fullscreen library your application uses\n const fullscreenElementRef = React.useRef<HTMLDivElement>();\n const isFullscreen = useIsFullscreen();\n\n const enterFullScreen = () => {\n screenfull.request(fullscreenElementRef.current);\n };\n\n const exitFullscreen = () => {\n screenfull.exit();\n };\n\n return (\n <>\n <SecondaryButton onClick={enterFullScreen}>Open Fullscreen</SecondaryButton>\n <Flex\n ref={fullscreenElementRef}\n cs={{alignItems: 'center', justifyContent: 'center', background: system.color.bg.default}}\n >\n <Flex cs={{gap: system.gap.md}}>\n <SelfClosePopup />\n <TransferClosePopup />\n {isFullscreen ? (\n <SecondaryButton onClick={exitFullscreen}>Exit fullscreen</SecondaryButton>\n ) : null}\n </Flex>\n </Flex>\n </>\n );\n};\n```\n\n### Opening an External Window\n\nA popup can open an external window. This isn't supported directly. The `Popup.Popper` subcomponent\nis replaced with a custom subcomponent that connects to the Popup model and controls the lifecycle\nof the extenal window. Be sure to connect the `unload` event of both the parent `window` and the\nexternal child `window` to the lifecycle of the Popup model to prevent memory leaks or zombie\nwindows.\n```tsx\nimport React from 'react';\nimport ReactDOM from 'react-dom';\n\nimport {SecondaryButton} from '@workday/canvas-kit-react/button';\nimport {\n CanvasProvider,\n ContentDirection,\n PartialEmotionCanvasTheme,\n createSubcomponent,\n useMount,\n useTheme,\n} from '@workday/canvas-kit-react/common';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {Popup, usePopupModel} from '@workday/canvas-kit-react/popup';\nimport {Tooltip} from '@workday/canvas-kit-react/tooltip';\nimport {createStyles} from '@workday/canvas-kit-styling';\nimport {infoIcon} from '@workday/canvas-system-icons-web';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst mainContentStyles = createStyles({\n padding: system.padding.md,\n});\n\nexport interface ExternalWindowPortalProps {\n /**\n * Child components of WindowPortal\n */\n children: React.ReactNode;\n /**\n * Callback to close the popup\n */\n onWindowClose?: () => void;\n /**\n * Width of the popup window\n */\n width?: number;\n /**\n * Height of the popup window\n */\n height?: number;\n /**\n * The name of the popup window. If another popup opens with the same name, that instance will\n * be reused. Use caution with setting this value\n */\n target?: string;\n}\n\nasync function copyAssets(sourceDoc: Document, targetDoc: Document) {\n for (const font of (sourceDoc as any).fonts.values()) {\n (targetDoc as any).fonts.add(font);\n\n font.load();\n }\n\n await (targetDoc as any).fonts.ready;\n\n // The current ES lib version doesn't include iterable interfaces, so we cast as an iterable\n for (const styleSheet of sourceDoc.styleSheets as StyleSheetList & Iterable<CSSStyleSheet>) {\n if (styleSheet.cssRules) {\n // text based styles\n const styleEl = targetDoc.createElement('style');\n for (const cssRule of styleSheet.cssRules as CSSRuleList & Iterable<CSSRule>) {\n styleEl.appendChild(targetDoc.createTextNode(cssRule.cssText));\n }\n targetDoc.head.appendChild(styleEl);\n } else if (styleSheet.href) {\n // link based styles\n const linkEl = targetDoc.createElement('link');\n\n linkEl.rel = 'stylesheet';\n linkEl.href = styleSheet.href;\n targetDoc.head.appendChild(linkEl);\n }\n }\n}\n\nconst ExternalWindowPortal = ({\n children,\n width = 300,\n height = 500,\n target = '',\n onWindowClose,\n}: ExternalWindowPortalProps) => {\n const [portalElement, setPortalElement] = React.useState<HTMLDivElement | null>(null);\n\n useMount(() => {\n const newWindow = window.open(\n '', // url\n target,\n `width=${width},height=${height},left=100,top=100,popup=true`\n );\n\n if (newWindow) {\n // copy fonts and styles\n copyAssets(document, newWindow.document);\n\n const element = newWindow.document.createElement('div');\n newWindow.document.body.appendChild(element);\n setPortalElement(element);\n } else {\n onWindowClose();\n }\n\n const closeWindow = event => {\n onWindowClose();\n };\n\n window.addEventListener('unload', closeWindow);\n newWindow?.addEventListener('unload', closeWindow);\n\n return () => {\n window.removeEventListener('unload', closeWindow);\n newWindow?.removeEventListener('unload', closeWindow);\n newWindow?.close();\n };\n });\n\n if (!portalElement) {\n return null;\n }\n\n return ReactDOM.createPortal(<CanvasProvider>{children}</CanvasProvider>, portalElement);\n};\n\nconst PopupExternalWindow = createSubcomponent()({\n displayName: 'Popup.ExternalWindow',\n modelHook: usePopupModel,\n})<ExternalWindowPortalProps>(({children, ...elemProps}, Element, model) => {\n if (model.state.visibility === 'visible') {\n return (\n <ExternalWindowPortal onWindowClose={model.events.hide} {...elemProps}>\n {children}\n </ExternalWindowPortal>\n );\n }\n\n return null;\n});\n\nexport const ExternalWindow = () => {\n // useTheme is filling in the Canvas theme object if any keys are missing\n const canvasTheme: PartialEmotionCanvasTheme = useTheme({\n canvas: {\n // Switch to `ContentDirection.RTL` to change direction\n direction: ContentDirection.LTR,\n },\n });\n\n const model = usePopupModel();\n\n return (\n <CanvasProvider theme={canvasTheme}>\n <main className={mainContentStyles}>\n <p>Popup that opens a new Operating System Window</p>\n <Popup model={model}>\n <Tooltip title=\"Open External Window Tooltip\">\n <Popup.Target>Open External Window</Popup.Target>\n </Tooltip>\n <PopupExternalWindow>\n <p>External Window Contents! Mouse over the info icon to get a tooltip</p>\n <Flex cs={{gap: system.gap.sm}}>\n <Tooltip title=\"More information\">\n <SecondaryButton icon={infoIcon} />\n </Tooltip>\n <Popup.CloseButton>Close Window</Popup.CloseButton>\n </Flex>\n </PopupExternalWindow>\n </Popup>\n <p>Popup visibility: {model.state.visibility}</p>\n </main>\n </CanvasProvider>\n );\n};\n```\n\n### RTL\n\nThe Popup component automatically handles right-to-left rendering.\n\n> **Note:** This example shows an inaccessible open card for demonstration purposes.\n```tsx\nimport {DeleteButton, SecondaryButton} from '@workday/canvas-kit-react/button';\nimport {CanvasProvider} from '@workday/canvas-kit-react/common';\nimport {Box, Flex} from '@workday/canvas-kit-react/layout';\nimport {Popup} from '@workday/canvas-kit-react/popup';\nimport {px2rem} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nexport const RTL = () => {\n return (\n <CanvasProvider dir=\"rtl\">\n <Popup.Card cs={{width: px2rem(400)}}>\n <Popup.CloseIcon aria-label=\"\u05E1\u05D2\u05D5\u05E8\" />\n <Popup.Heading>\u05DC\u05DE\u05D7\u05D5\u05E7 \u05E4\u05E8\u05D9\u05D8</Popup.Heading>\n <Popup.Body>\n <Box as=\"p\" cs={{marginBlock: '0'}}>\n \u05D4\u05D0\u05DD \u05D1\u05E8\u05E6\u05D5\u05E0\u05DA \u05DC\u05DE\u05D7\u05D5\u05E7 \u05E4\u05E8\u05D9\u05D8 \u05D6\u05D4\n </Box>\n </Popup.Body>\n <Popup.ButtonGroup>\n <SecondaryButton>\u05DC\u05B0\u05D1\u05B7\u05D8\u05B5\u05DC</SecondaryButton>\n <DeleteButton>\u05DC\u05B4\u05DE\u05B0\u05D7\u05D5\u05B9\u05E7</DeleteButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </CanvasProvider>\n );\n};\n```\n\n## Accessibility\n\nEnsure users of assistive technology can discover, name, and operate a popup that is typically\nportaled to the end of `document.body`: the popup has an accessible name that matches its visible\nheading, keyboard users can open and dismiss it predictably, and focus and reading order remain\nusable despite portal placement (see\n[Guides > Accessibility > Inline Popups](https://workday.github.io/canvas-kit/?path=/docs/guides-accessibility-inline-popups--docs)).\nPrefer a semantic component before composing **Popup** directly:\n[**Dialog**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-dialog--docs) for a\nstandard non-modal dialog (behaviors and `aria-owns` built in), or\n[**Modal**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs) for\nblocking tasks with focus trapping and assistive sibling hiding (see also the W3C\n[Dialog (Modal) Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/)). Use **Popup** with\ncomposed hooks when you need a custom popup stack or behavior set that those components do not\nprovide.\n\n### Minimum Accessible Structure\n\nThe following matches the [Basic Example](#basic-example): hoist **`usePopupModel`**, compose the\nnon-modal behavior hooks on that model, place **`Popup.CloseIcon`** before **`Popup.Heading`** so\ndefault open focus lands on the dismiss control first, and use **`Popup.CloseButton`** for actions\nthat should also close the popup.\n\n```tsx\n\nconst Example = () => {\n const model = usePopupModel();\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusRedirect(model);\n\n return (\n <Popup model={model}>\n <Popup.Target as={DeleteButton}>Delete Item</Popup.Target>\n <Popup.Popper>\n <Popup.Card>\n <Popup.CloseIcon aria-label=\"Close\" />\n <Popup.Heading>Delete Item</Popup.Heading>\n <Popup.Body>\n <p>Are you sure you'd like to delete the item titled 'My Item'?</p>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton>Cancel</Popup.CloseButton>\n <Popup.CloseButton as={DeleteButton}>Delete</Popup.CloseButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n );\n};\n```\n\nInclude a dismiss control: **`Popup.CloseButton`** with visible text (for example \"Cancel\" or\n\"Close\"), and/or **`Popup.CloseIcon`** when the design uses an icon-only dismiss (requires\n**`aria-label`** or **`Tooltip`**). Pass the same **`model`** instance to **`Popup`** and every\nbehavior hook so focus and dismiss wiring share one stack.\n\n### Built-in Behaviors\n\nCanvas Kit applies ARIA and DOM wiring automatically via Popup subcomponents when you compose them.\nBehavioral hooks are **not** applied by `usePopupModel` alone\u2014you must call them (as in the Basic\nExample). Once applied, **do not duplicate them** in consuming code.\n\n**Popup behaviors** (_compose on the model; recommended for non-modal dialogs_):\n\n- `useInitialFocus` \u2014 moves focus into the popup when it opens (default: first focusable element in\n DOM order; optional override via `initialFocusRef` on the model)\n- `useReturnFocus` \u2014 returns focus to `Popup.Target` (or configured return target) when it closes\n- `useCloseOnEscape` \u2014 <kbd>Escape</kbd> closes the popup\n- `useCloseOnOutsideClick` \u2014 pointer interaction outside closes the popup\n- `useFocusRedirect` \u2014 <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> at the first or last\n focusable element inside the popup closes it and moves focus to the next or previous focusable\n element on the page (non-modal; **not** a focus trap; does **not** change screen reader reading\n order; does **not** provide `aria-owns`)\n\n**ARIA and DOM** (_applied by hooks/subcomponents_):\n\n- `Popup.Card`: `role=\"dialog\"`, `aria-labelledby` referencing the heading `id` (non-modal by\n default; page content is not hidden with `aria-hidden` unless you compose\n `useAssistiveHideSiblings`)\n- `Popup.Heading`: `id` wired to `Popup.Card`'s `aria-labelledby`\n- `Popup.Popper`: positions and registers the popup with the stack; unlike\n [**Dialog.Popper**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-dialog--docs),\n it does **not** set `aria-owns`\n- `Popup.CloseIcon` / `Popup.CloseButton`: `onClick` that calls `model.events.hide()`\n- `Popup.Target`: `ref` and `onClick` to open and to receive return focus\n\n**Implementation note on `aria-owns`:** `useFocusRedirect` does not provide `aria-owns`. When you\nneed remapped reading order for portaled content, add it yourself (see **Reading order** in\nAccessibility Requirements) or prefer **Dialog**, which wires `aria-owns` automatically. Support\nvaries by browser and screen reader.\n\n**Keyboard** (_trigger is `Popup.Target`, default `SecondaryButton`_):\n\n- <kbd>Enter</kbd> / <kbd>Space</kbd> on the trigger opens the popup (standard button behavior)\n- On open and close, focus is managed by **`useInitialFocus`** and **`useReturnFocus`** when those\n hooks are composed (application overrides: see **Focus management** in Accessibility Requirements)\n- <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> move focus forward and backward through\n interactive elements inside the popup (standard sequential focus behavior)\n- With **`useFocusRedirect`**, tabbing past the last or before the first focusable element closes\n the popup\n- <kbd>Escape</kbd> closes the popup when **`useCloseOnEscape`** is composed and returns focus per\n `useReturnFocus`\n\n**Screen reader expectations** (_when built-in behaviors and recommended hooks are used as\nintended_):\n\n- On open, assistive technology should announce the first focused control (often a dismiss control),\n the popup name (`Popup.Heading`), and `dialog` role\n- Background page content remains available to assistive technology unless you compose\n **`useAssistiveHideSiblings`** (prefer\n [**Modal**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs) for\n that pattern)\n- Reading order may still follow document order at the end of `body` unless `aria-owns` remapping is\n added and honored; support varies by browser and screen reader\n\n### Accessibility Requirements\n\nRequired in application code for an accessible Popup. Always hoist **`usePopupModel`** and pass the\nsame instance to **`Popup`** and behavior hooks. Rows marked _(conditional)_ apply only when the\nsituation matches\u2014otherwise omit.\n\n**If no design spec is provided:** compose the Basic Example hooks (`useCloseOnOutsideClick`,\n`useCloseOnEscape`, `useInitialFocus`, `useReturnFocus`, `useFocusRedirect`); use default focus\nbehavior; omit **`initialFocusRef`**, **`returnFocusRef`**, **`aria-describedby`**,\n**`aria-expanded`**, **`aria-haspopup`**, **`useFocusTrap`**, and **`useAssistiveHideSiblings`**.\nPrefer **Dialog** or **Modal** when those components already match the product need.\n\n**Focus management \u2014 defaults and developer prompts:** When **`useInitialFocus`** /\n**`useReturnFocus`** are composed, Canvas Kit handles open and close focus automatically. **State\nthe default to the developer first.** Only set **`initialFocusRef`** or **`returnFocusRef`** after\nthe developer (or an explicit design spec) chooses a non-default target. **Do not generate focus\nrefs by default.**\n\n| When | Default behavior | Ask the developer before overriding |\n| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| 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`**. |\n| 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`**.) |\n\nIf close **removes the trigger from the DOM**, **`returnFocusRef`** alone is not enough\u2014move focus\nafter the UI updates (for example with **`useLayoutEffect`**). See\n[Modal > Return Focus](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs#return-focus).\n\n**Custom targets** _(conditional)_: Apply when using a custom **`as`** component on\n**`Popup.Target`**. **`Popup.Target`** adds **`onClick`** and **`ref`**. Custom targets must forward\nboth to a **keyboard-focusable** element (prefer a native **`<button>`** or\n**`as={SecondaryButton}`** / another Canvas Kit button). Wrap the component in\n**`React.forwardRef`** when it does not forward refs by default (required if the popup can open\nprogrammatically before the user clicks the target).\n\n**Reading order (`aria-owns`)** _(conditional)_:\n\nPopup content is portaled; **`useFocusRedirect`** alone does not fix screen reader reading order.\nWhen a design needs remapped sequential reading order and you are not using **Dialog**, set an `id`\non the stack element and point a sibling element's **`aria-owns`** at that `id` (see the\n[Focus Redirect](#focus-redirect) example). Prefer **Dialog** when that pattern is the product\ndefault\u2014Dialog wires **`aria-owns`** for you.\n\n**Modal-like focus trapping** _(conditional)_:\n\nPrefer [**Modal**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs)\nfor blocking tasks. If you must compose trapping on **Popup**, use **`useFocusTrap`** with\n**`useAssistiveHideSiblings`** (and typically **omit** **`useFocusRedirect`**). Focus trapping does\nnot stop mouse or virtual-cursor escape by itself.\n\n**Open focus below the heading** _(conditional; see supplementary copy row below)_:\n\nButton-focus variant (matches [Initial Focus](#initial-focus)): when open focus lands on a primary\naction below the heading, wire **`aria-describedby`** to the supplementary copy. For the form-field\nvariant (focus an input), see\n[Dialog](https://workday.github.io/canvas-kit/?path=/docs/components-popups-dialog--docs#accessibility-requirements)\nor\n[Modal](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs#accessibility-requirements)\n**Open focus below the heading**.\n\n```tsx\n\nconst Example = () => {\n const messageId = useUniqueId();\n const initialFocusRef = React.useRef(null);\n const model = usePopupModel({initialFocusRef});\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusRedirect(model);\n\n return (\n <Popup model={model}>\n <Popup.Target>Open</Popup.Target>\n <Popup.Popper>\n <Popup.Card aria-describedby={messageId}>\n <Popup.Heading>Confirmation</Popup.Heading>\n <Popup.Body>\n <p id={messageId}>Your message has been sent!</p>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton as={PrimaryButton} ref={initialFocusRef}>\n OK\n </Popup.CloseButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n );\n};\n```\n\nWhen open focus lands on **`Popup.Heading`** itself, add **`tabIndex={-1}`** so the heading can\nreceive programmatic focus.\n\n| Requirement | How to satisfy |\n| ------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| 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. |\n| 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. |\n| 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`**). |\n| Keyboard-operable trigger | See **Custom targets** above. |\n| 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). |\n| Reading order remapping _(conditional)_ | See **Reading order (`aria-owns`)** above, or use **Dialog**. |\n| Focus trapping / hide siblings _(conditional)_ | Prefer **Modal**. If composing on **Popup**, see **Modal-like focus trapping** above. |\n| Open/closed state on the trigger _(conditional)_ | See **Wiring aria-expanded** below. **Default:** omit **`aria-expanded`** and **`aria-haspopup`**. |\n\n**Summary for code generation:**\n\n- **REQUIRED:** shared `usePopupModel`, non-modal behavior hooks (unless design omits), accessible\n name, dismiss control, keyboard-operable trigger\n- **CONDITIONAL:** **`initialFocusRef`**, **`returnFocusRef`**, **`aria-describedby`**,\n **`tabIndex={-1}`** on heading focus, **`aria-owns`**, **`useFocusTrap`** /\n **`useAssistiveHideSiblings`**, **`aria-expanded`** / **`aria-haspopup`**, **`forwardRef`** on\n custom **`Popup.Target`**\n\n**Wiring aria-expanded** _(conditional)_:\n\nThe **`aria-expanded`** pattern is **uncommon** for dialog-like Popups\u2014omit **`aria-expanded`** and\n**`aria-haspopup`** unless a review deliberately keeps open focus on the trigger (for example\n**`initialFocusRef`** on the trigger per design spec). When required, on **`Popup.Target`** set\n**`aria-expanded={model.state.visibility !== 'hidden'}`** and **`aria-haspopup=\"dialog\"`**. See\n**Focus management** and the open/closed-state row above.\n\n### Anti-Patterns\n\nDo **not** generate code that does the following (see **Accessibility Requirements** above for what\nto supply instead):\n\n- Manually set `role=\"dialog\"`, `aria-labelledby`, or the heading `id` on **`Popup.Card`** or\n **`Popup.Heading`** \u2014 Canvas Kit hooks wire these\n- Call behavior hooks on a **different** model instance than the one passed to **`Popup`**, or omit\n **`model={model}`** after composing hooks outside the container\n- Assume **`usePopupModel`** alone provides focus, escape, outside-click, or redirect behaviors \u2014\n compose the hooks (or use **Dialog** / **Modal**)\n- Omit **`Popup.Popper`**, render **`Popup.Card`** outside it, or add a custom portal/restructure\n instead of **`Popup` \u2192 `Popup.Popper` \u2192 `Popup.Card`** without using **`usePopupStack`**\n- Use **`open`** / **`onClose`** props on **`Popup`** \u2014 Popup has no controlled visibility props;\n use **`usePopupModel`** and **`model.events.show()`** / **`model.events.hide()`**\n- Reach for **Popup** + **`useFocusTrap`** / **`useAssistiveHideSiblings`** when **Modal** already\n matches the product need, or for non-modal UX when **Dialog** already matches\n- Set **`initialFocusRef`** or **`returnFocusRef`** by default \u2014 state the default focus behavior\n first and ask the developer before overriding (see **Focus management** in Accessibility\n Requirements)\n- Add **`aria-expanded`** / **`aria-haspopup`** on the default dialog-like Popup path, or bind\n **`aria-expanded`** to a static value (see **Wiring aria-expanded** in Accessibility Requirements)\n- Use a custom **`Popup.Target`** **`as`** component that does not forward **`ref`** to a focusable\n element \u2014 use **`React.forwardRef`** or a Canvas Kit button component instead\n- Rely on **`returnFocusRef`** alone when close **removes the trigger from the DOM** (see\n [Modal > Return Focus](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs#return-focus))\n- Nest multiple **Popup** instances without deliberate initial focus and return-focus planning\n- Assume **`useFocusRedirect`** fixes screen reader reading order, or that **`aria-owns`** remapping\n works in all browser and screen reader combinations \u2014 test your supported combinations\n- Expect **`Popup.Popper`** to set **`aria-owns`** like **Dialog.Popper** \u2014 it does not\n\n## Component API\n\n<>\n \n\n \n</>\n\n## Hooks\n\n<>\n \n\n{' '}\n\n{' '}\n\n{' '}\n\n{' '}\n\n{' '}\n\n{' '}\n\n{' '}\n\n{' '}\n\n{' '}\n\n{' '}\n\n{' '}\n\n{' '}\n\n \n</>\n\n## Specifications\n\n",
|
|
428
|
+
accessibilityProse: '## Accessibility\n\nEnsure users of assistive technology can discover, name, and operate a popup that is typically\nportaled to the end of `document.body`: the popup has an accessible name that matches its visible\nheading, keyboard users can open and dismiss it predictably, and focus and reading order remain\nusable despite portal placement (see\n[Guides > Accessibility > Inline Popups](https://workday.github.io/canvas-kit/?path=/docs/guides-accessibility-inline-popups--docs)).\nPrefer a semantic component before composing **Popup** directly:\n[**Dialog**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-dialog--docs) for a\nstandard non-modal dialog (behaviors and `aria-owns` built in), or\n[**Modal**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs) for\nblocking tasks with focus trapping and assistive sibling hiding (see also the W3C\n[Dialog (Modal) Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/)). Use **Popup** with\ncomposed hooks when you need a custom popup stack or behavior set that those components do not\nprovide.\n\n### Minimum Accessible Structure\n\nThe following matches the [Basic Example](#basic-example): hoist **`usePopupModel`**, compose the\nnon-modal behavior hooks on that model, place **`Popup.CloseIcon`** before **`Popup.Heading`** so\ndefault open focus lands on the dismiss control first, and use **`Popup.CloseButton`** for actions\nthat should also close the popup.\n\n```tsx\n\nconst Example = () => {\n const model = usePopupModel();\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusRedirect(model);\n\n return (\n <Popup model={model}>\n <Popup.Target as={DeleteButton}>Delete Item</Popup.Target>\n <Popup.Popper>\n <Popup.Card>\n <Popup.CloseIcon aria-label="Close" />\n <Popup.Heading>Delete Item</Popup.Heading>\n <Popup.Body>\n <p>Are you sure you\'d like to delete the item titled \'My Item\'?</p>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton>Cancel</Popup.CloseButton>\n <Popup.CloseButton as={DeleteButton}>Delete</Popup.CloseButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n );\n};\n```\n\nInclude a dismiss control: **`Popup.CloseButton`** with visible text (for example "Cancel" or\n"Close"), and/or **`Popup.CloseIcon`** when the design uses an icon-only dismiss (requires\n**`aria-label`** or **`Tooltip`**). Pass the same **`model`** instance to **`Popup`** and every\nbehavior hook so focus and dismiss wiring share one stack.\n\n### Built-in Behaviors\n\nCanvas Kit applies ARIA and DOM wiring automatically via Popup subcomponents when you compose them.\nBehavioral hooks are **not** applied by `usePopupModel` alone\u2014you must call them (as in the Basic\nExample). Once applied, **do not duplicate them** in consuming code.\n\n**Popup behaviors** (_compose on the model; recommended for non-modal dialogs_):\n\n- `useInitialFocus` \u2014 moves focus into the popup when it opens (default: first focusable element in\n DOM order; optional override via `initialFocusRef` on the model)\n- `useReturnFocus` \u2014 returns focus to `Popup.Target` (or configured return target) when it closes\n- `useCloseOnEscape` \u2014 <kbd>Escape</kbd> closes the popup\n- `useCloseOnOutsideClick` \u2014 pointer interaction outside closes the popup\n- `useFocusRedirect` \u2014 <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> at the first or last\n focusable element inside the popup closes it and moves focus to the next or previous focusable\n element on the page (non-modal; **not** a focus trap; does **not** change screen reader reading\n order; does **not** provide `aria-owns`)\n\n**ARIA and DOM** (_applied by hooks/subcomponents_):\n\n- `Popup.Card`: `role="dialog"`, `aria-labelledby` referencing the heading `id` (non-modal by\n default; page content is not hidden with `aria-hidden` unless you compose\n `useAssistiveHideSiblings`)\n- `Popup.Heading`: `id` wired to `Popup.Card`\'s `aria-labelledby`\n- `Popup.Popper`: positions and registers the popup with the stack; unlike\n [**Dialog.Popper**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-dialog--docs),\n it does **not** set `aria-owns`\n- `Popup.CloseIcon` / `Popup.CloseButton`: `onClick` that calls `model.events.hide()`\n- `Popup.Target`: `ref` and `onClick` to open and to receive return focus\n\n**Implementation note on `aria-owns`:** `useFocusRedirect` does not provide `aria-owns`. When you\nneed remapped reading order for portaled content, add it yourself (see **Reading order** in\nAccessibility Requirements) or prefer **Dialog**, which wires `aria-owns` automatically. Support\nvaries by browser and screen reader.\n\n**Keyboard** (_trigger is `Popup.Target`, default `SecondaryButton`_):\n\n- <kbd>Enter</kbd> / <kbd>Space</kbd> on the trigger opens the popup (standard button behavior)\n- On open and close, focus is managed by **`useInitialFocus`** and **`useReturnFocus`** when those\n hooks are composed (application overrides: see **Focus management** in Accessibility Requirements)\n- <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> move focus forward and backward through\n interactive elements inside the popup (standard sequential focus behavior)\n- With **`useFocusRedirect`**, tabbing past the last or before the first focusable element closes\n the popup\n- <kbd>Escape</kbd> closes the popup when **`useCloseOnEscape`** is composed and returns focus per\n `useReturnFocus`\n\n**Screen reader expectations** (_when built-in behaviors and recommended hooks are used as\nintended_):\n\n- On open, assistive technology should announce the first focused control (often a dismiss control),\n the popup name (`Popup.Heading`), and `dialog` role\n- Background page content remains available to assistive technology unless you compose\n **`useAssistiveHideSiblings`** (prefer\n [**Modal**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs) for\n that pattern)\n- Reading order may still follow document order at the end of `body` unless `aria-owns` remapping is\n added and honored; support varies by browser and screen reader\n\n### Accessibility Requirements\n\nRequired in application code for an accessible Popup. Always hoist **`usePopupModel`** and pass the\nsame instance to **`Popup`** and behavior hooks. Rows marked _(conditional)_ apply only when the\nsituation matches\u2014otherwise omit.\n\n**If no design spec is provided:** compose the Basic Example hooks (`useCloseOnOutsideClick`,\n`useCloseOnEscape`, `useInitialFocus`, `useReturnFocus`, `useFocusRedirect`); use default focus\nbehavior; omit **`initialFocusRef`**, **`returnFocusRef`**, **`aria-describedby`**,\n**`aria-expanded`**, **`aria-haspopup`**, **`useFocusTrap`**, and **`useAssistiveHideSiblings`**.\nPrefer **Dialog** or **Modal** when those components already match the product need.\n\n**Focus management \u2014 defaults and developer prompts:** When **`useInitialFocus`** /\n**`useReturnFocus`** are composed, Canvas Kit handles open and close focus automatically. **State\nthe default to the developer first.** Only set **`initialFocusRef`** or **`returnFocusRef`** after\nthe developer (or an explicit design spec) chooses a non-default target. **Do not generate focus\nrefs by default.**\n\n| When | Default behavior | Ask the developer before overriding |\n| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| 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`**. |\n| 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`**.) |\n\nIf close **removes the trigger from the DOM**, **`returnFocusRef`** alone is not enough\u2014move focus\nafter the UI updates (for example with **`useLayoutEffect`**). See\n[Modal > Return Focus](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs#return-focus).\n\n**Custom targets** _(conditional)_: Apply when using a custom **`as`** component on\n**`Popup.Target`**. **`Popup.Target`** adds **`onClick`** and **`ref`**. Custom targets must forward\nboth to a **keyboard-focusable** element (prefer a native **`<button>`** or\n**`as={SecondaryButton}`** / another Canvas Kit button). Wrap the component in\n**`React.forwardRef`** when it does not forward refs by default (required if the popup can open\nprogrammatically before the user clicks the target).\n\n**Reading order (`aria-owns`)** _(conditional)_:\n\nPopup content is portaled; **`useFocusRedirect`** alone does not fix screen reader reading order.\nWhen a design needs remapped sequential reading order and you are not using **Dialog**, set an `id`\non the stack element and point a sibling element\'s **`aria-owns`** at that `id` (see the\n[Focus Redirect](#focus-redirect) example). Prefer **Dialog** when that pattern is the product\ndefault\u2014Dialog wires **`aria-owns`** for you.\n\n**Modal-like focus trapping** _(conditional)_:\n\nPrefer [**Modal**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs)\nfor blocking tasks. If you must compose trapping on **Popup**, use **`useFocusTrap`** with\n**`useAssistiveHideSiblings`** (and typically **omit** **`useFocusRedirect`**). Focus trapping does\nnot stop mouse or virtual-cursor escape by itself.\n\n**Open focus below the heading** _(conditional; see supplementary copy row below)_:\n\nButton-focus variant (matches [Initial Focus](#initial-focus)): when open focus lands on a primary\naction below the heading, wire **`aria-describedby`** to the supplementary copy. For the form-field\nvariant (focus an input), see\n[Dialog](https://workday.github.io/canvas-kit/?path=/docs/components-popups-dialog--docs#accessibility-requirements)\nor\n[Modal](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs#accessibility-requirements)\n**Open focus below the heading**.\n\n```tsx\n\nconst Example = () => {\n const messageId = useUniqueId();\n const initialFocusRef = React.useRef(null);\n const model = usePopupModel({initialFocusRef});\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusRedirect(model);\n\n return (\n <Popup model={model}>\n <Popup.Target>Open</Popup.Target>\n <Popup.Popper>\n <Popup.Card aria-describedby={messageId}>\n <Popup.Heading>Confirmation</Popup.Heading>\n <Popup.Body>\n <p id={messageId}>Your message has been sent!</p>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton as={PrimaryButton} ref={initialFocusRef}>\n OK\n </Popup.CloseButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n );\n};\n```\n\nWhen open focus lands on **`Popup.Heading`** itself, add **`tabIndex={-1}`** so the heading can\nreceive programmatic focus.\n\n| Requirement | How to satisfy |\n| ------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| 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. |\n| 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. |\n| 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`**). |\n| Keyboard-operable trigger | See **Custom targets** above. |\n| 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). |\n| Reading order remapping _(conditional)_ | See **Reading order (`aria-owns`)** above, or use **Dialog**. |\n| Focus trapping / hide siblings _(conditional)_ | Prefer **Modal**. If composing on **Popup**, see **Modal-like focus trapping** above. |\n| Open/closed state on the trigger _(conditional)_ | See **Wiring aria-expanded** below. **Default:** omit **`aria-expanded`** and **`aria-haspopup`**. |\n\n**Summary for code generation:**\n\n- **REQUIRED:** shared `usePopupModel`, non-modal behavior hooks (unless design omits), accessible\n name, dismiss control, keyboard-operable trigger\n- **CONDITIONAL:** **`initialFocusRef`**, **`returnFocusRef`**, **`aria-describedby`**,\n **`tabIndex={-1}`** on heading focus, **`aria-owns`**, **`useFocusTrap`** /\n **`useAssistiveHideSiblings`**, **`aria-expanded`** / **`aria-haspopup`**, **`forwardRef`** on\n custom **`Popup.Target`**\n\n**Wiring aria-expanded** _(conditional)_:\n\nThe **`aria-expanded`** pattern is **uncommon** for dialog-like Popups\u2014omit **`aria-expanded`** and\n**`aria-haspopup`** unless a review deliberately keeps open focus on the trigger (for example\n**`initialFocusRef`** on the trigger per design spec). When required, on **`Popup.Target`** set\n**`aria-expanded={model.state.visibility !== \'hidden\'}`** and **`aria-haspopup="dialog"`**. See\n**Focus management** and the open/closed-state row above.\n\n### Anti-Patterns\n\nDo **not** generate code that does the following (see **Accessibility Requirements** above for what\nto supply instead):\n\n- Manually set `role="dialog"`, `aria-labelledby`, or the heading `id` on **`Popup.Card`** or\n **`Popup.Heading`** \u2014 Canvas Kit hooks wire these\n- Call behavior hooks on a **different** model instance than the one passed to **`Popup`**, or omit\n **`model={model}`** after composing hooks outside the container\n- Assume **`usePopupModel`** alone provides focus, escape, outside-click, or redirect behaviors \u2014\n compose the hooks (or use **Dialog** / **Modal**)\n- Omit **`Popup.Popper`**, render **`Popup.Card`** outside it, or add a custom portal/restructure\n instead of **`Popup` \u2192 `Popup.Popper` \u2192 `Popup.Card`** without using **`usePopupStack`**\n- Use **`open`** / **`onClose`** props on **`Popup`** \u2014 Popup has no controlled visibility props;\n use **`usePopupModel`** and **`model.events.show()`** / **`model.events.hide()`**\n- Reach for **Popup** + **`useFocusTrap`** / **`useAssistiveHideSiblings`** when **Modal** already\n matches the product need, or for non-modal UX when **Dialog** already matches\n- Set **`initialFocusRef`** or **`returnFocusRef`** by default \u2014 state the default focus behavior\n first and ask the developer before overriding (see **Focus management** in Accessibility\n Requirements)\n- Add **`aria-expanded`** / **`aria-haspopup`** on the default dialog-like Popup path, or bind\n **`aria-expanded`** to a static value (see **Wiring aria-expanded** in Accessibility Requirements)\n- Use a custom **`Popup.Target`** **`as`** component that does not forward **`ref`** to a focusable\n element \u2014 use **`React.forwardRef`** or a Canvas Kit button component instead\n- Rely on **`returnFocusRef`** alone when close **removes the trigger from the DOM** (see\n [Modal > Return Focus](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs#return-focus))\n- Nest multiple **Popup** instances without deliberate initial focus and return-focus planning\n- Assume **`useFocusRedirect`** fixes screen reader reading order, or that **`aria-owns`** remapping\n works in all browser and screen reader combinations \u2014 test your supported combinations\n- Expect **`Popup.Popper`** to set **`aria-owns`** like **Dialog.Popper** \u2014 it does not'
|
|
429
429
|
},
|
|
430
430
|
popper: {
|
|
431
431
|
title: "Components/Popups/Popper",
|
|
432
432
|
storybookUrl: "https://workday.github.io/canvas-kit/?path=/docs/components-popups-popper--docs",
|
|
433
433
|
mdxPath: "modules/react/popup/stories/Popup.mdx",
|
|
434
|
-
mdxProse: "# Canvas Kit Popups\n\nA \"popup\" is a classification for a type of stacked UI element that appears \"on top\" of statically\npositioned content. Tooltips, Modals, Dropdown menus, etc are all examples of \"popups\". Canvas Kit\nhas a \"stack manager\" system for managing these popups. Different types of popups have different\nrequirements of behavior for UX and accessibility - we can call them behaviors, capabilities, or\ntraits. Canvas Kit comes with a number of [behavioral hooks](#hooks) in the form of React Hooks.\n\nYou should use the most semantic component for your use-case before using `Popup` directly, like\n`Modal`, which already has the correct behaviors built-in. If no component already exists that\nmatches your use case, you can use `Popup` and use our [hooks](#hooks). The `Popup` component comes\nwith a `Popup.Popper` subcomponent that positions a popup using [PopperJS](https://popper.js.org/)\nthat registers a popup with the `PopupStack` automatically and sets the popup model's `placement`\nproperty. `Popup.Popper` component and hooks work with the stack management system for correct\nrendering and accessibility behavior. If you cannot use `Popup.Popper`, use the\n[usePopupStack](#usepoupstack) hook to properly register and deregister the popup at the correct\ntime. If you cannot use our hooks, consider upgrading your component to use Hooks. If you cannot do\nthat, you'll have to look up the `PopupStack` package for the direct API and have a look at the\nsource code for our hooks into the `PopupStack` API.\n\nThis package comes with everything you need to build Popup UIs.\n\n[Buttons](/components/buttons/button)\n\n## Installation\n\n```sh\nyarn add @workday/canvas-kit-react\n```\n\n## Usage\n\nThe `Popup` component is a generic\n[Compound Component](/get-started/for-developers/documentation/compound-components/) that is used to\nbuild popup UIs that are not already covered by Canvas Kit.\n\n### Basic Example\n\nThe Popup has no pre-defined behaviors built in, therefore the `usePopupModel` must always be used\nto create a new `model`. This `model` is then used by all behavior hooks to apply additional popup\nbehaviors to the compound component group. The following example creates a typical popup around a\ntarget element and adds `useCloseOnOutsideClick`, `useCloseOnEscape`, `useInitialFocus`, and\n`useReturnFocus` behaviors. You can read through the [hooks](#hooks) section to learn about all the\npopup behaviors. For accessibility, these behaviors should be included most of the time.\n```tsx\nimport {DeleteButton} from '@workday/canvas-kit-react/button';\nimport {Box} from '@workday/canvas-kit-react/layout';\nimport {\n Popup,\n useCloseOnEscape,\n useCloseOnOutsideClick,\n useFocusRedirect,\n useInitialFocus,\n usePopupModel,\n useReturnFocus,\n} from '@workday/canvas-kit-react/popup';\nimport {createStyles, px2rem} from '@workday/canvas-kit-styling';\n\nconst cardStyles = createStyles({\n width: px2rem(400),\n});\n\nconst bodyStyles = createStyles({\n marginBlock: '0',\n});\n\nexport const Basic = () => {\n const model = usePopupModel();\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusRedirect(model);\n\n const handleDelete = () => {\n console.log('Delete Item');\n };\n\n return (\n <Popup model={model}>\n <Popup.Target as={DeleteButton}>Delete Item</Popup.Target>\n <Popup.Popper placement=\"top\">\n <Popup.Card cs={cardStyles}>\n <Popup.CloseIcon aria-label=\"Close\" />\n <Popup.Heading>Delete Item</Popup.Heading>\n <Popup.Body>\n <Box as=\"p\" cs={bodyStyles}>\n Are you sure you'd like to delete the item titled 'My Item'?\n </Box>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton>Cancel</Popup.CloseButton>\n <Popup.CloseButton as={DeleteButton} onClick={handleDelete}>\n Delete\n </Popup.CloseButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n );\n};\n```\n\n### Initial Focus\n\nIf you want focus to move to a specific element when the popup is opened, set the `initialFocusRef`\nof the model. This is useful for popups that don't have a Close icon button near the top right of\nthe popup. In general, we recommend setting focus to the first interactive component inside the\npopup that is the least destructive action.\n```tsx\nimport React from 'react';\n\nimport {PrimaryButton} from '@workday/canvas-kit-react/button';\nimport {useUniqueId} from '@workday/canvas-kit-react/common';\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {\n Popup,\n useCloseOnEscape,\n useCloseOnOutsideClick,\n useFocusRedirect,\n useInitialFocus,\n usePopupModel,\n useReturnFocus,\n} from '@workday/canvas-kit-react/popup';\nimport {Text} from '@workday/canvas-kit-react/text';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\nimport {createStyles, px2rem} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst cardStyles = createStyles({\n width: px2rem(400),\n});\n\nconst bodyStyles = createStyles({\n marginBlock: '0',\n});\n\nconst columnStyles = createStyles({\n gap: system.gap.md,\n alignItems: 'flex-start',\n});\n\nconst InitialFocusOnButton = () => {\n const messageId = useUniqueId();\n const initialFocusRef = React.useRef(null);\n const model = usePopupModel({\n initialFocusRef,\n });\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusRedirect(model);\n\n return (\n <Popup model={model}>\n <Popup.Target>Initial focus: OK button</Popup.Target>\n <Popup.Popper placement={'bottom'}>\n <Popup.Card cs={cardStyles} aria-describedby={messageId}>\n <Popup.Heading>Confirmation</Popup.Heading>\n <Popup.Body>\n <Text cs={bodyStyles} id={messageId}>\n Your message has been sent!\n </Text>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton as={PrimaryButton} ref={initialFocusRef}>\n OK\n </Popup.CloseButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n );\n};\n\nconst InitialFocusOnTextInput = () => {\n const descriptionId = useUniqueId();\n const initialFocusRef = React.useRef<HTMLInputElement>(null);\n const model = usePopupModel({\n initialFocusRef,\n });\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusRedirect(model);\n\n return (\n <Popup model={model}>\n <Popup.Target>Initial focus: text input</Popup.Target>\n <Popup.Popper placement={'bottom'}>\n <Popup.Card cs={cardStyles} aria-describedby={descriptionId}>\n <Popup.Heading>Quick reply</Popup.Heading>\n <Popup.Body>\n <FormField>\n <FormField.Label>Message</FormField.Label>\n <FormField.Input as={TextInput} ref={initialFocusRef} />\n </FormField>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton>Cancel</Popup.CloseButton>\n <Popup.CloseButton as={PrimaryButton}>Send</Popup.CloseButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n );\n};\n\nconst InitialFocusOnHeading = () => {\n const headingFocusRef = React.useRef<HTMLHeadingElement>(null);\n const model = usePopupModel({\n initialFocusRef: headingFocusRef,\n });\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusRedirect(model);\n\n return (\n <Popup model={model}>\n <Popup.Target>Initial focus: heading</Popup.Target>\n <Popup.Popper placement={'bottom'}>\n <Popup.Card cs={cardStyles}>\n <Popup.Heading ref={headingFocusRef} tabIndex={-1}>\n Important notice\n </Popup.Heading>\n <Popup.Body>\n <Text cs={bodyStyles}>Review the summary below before continuing.</Text>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton as={PrimaryButton}>Continue</Popup.CloseButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n );\n};\n\nexport const InitialFocus = () => {\n return (\n <Flex cs={columnStyles}>\n <InitialFocusOnButton />\n <InitialFocusOnTextInput />\n <InitialFocusOnHeading />\n </Flex>\n );\n};\n```\n\n> **Accessibility Note**: When initial focus lands on a control **below** the title (such as the OK\n> button in the example above), assign a unique `id` to supplementary text and pass\n> `aria-describedby` on `Popup.Card`. This augments the included `aria-labelledby` reference to\n> `Popup.Heading` so screen readers can announce both the heading and any supplementary text\n> automatically. When initial focus is on the heading itself, add `tabIndex={-1}` to `Popup.Heading`\n> so the title can receive programmatic focus. Choose where focus goes based on your product and\n> accessibility requirements.\n\n### Focus Redirect\n\nFocus management is important to accessibility of popup contents. The following example shows\n`useFocusRedirect` being used to manage focus in and out of a Popup. This is very useful for\nnon-modal popups. Focus redirection tries to treat the Popup as if it were inline to the document.\nTabbing out of the Popup will close the Popup and move focus to an adjacent focusable element.\n```tsx\nimport * as React from 'react';\n\nimport {DeleteButton, SecondaryButton} from '@workday/canvas-kit-react/button';\nimport {useUniqueId} from '@workday/canvas-kit-react/common';\nimport {Box, Flex} from '@workday/canvas-kit-react/layout';\nimport {\n Popup,\n useCloseOnEscape,\n useCloseOnOutsideClick,\n useFocusRedirect,\n useInitialFocus,\n usePopupModel,\n useReturnFocus,\n} from '@workday/canvas-kit-react/popup';\nimport {createStyles, px2rem} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst cardStyles = createStyles({\n width: px2rem(400),\n});\n\nconst bodyStyles = createStyles({\n marginBlock: '0',\n});\n\nconst flexStyles = createStyles({\n gap: system.gap.md,\n padding: system.padding.xs,\n});\n\nexport const FocusRedirect = () => {\n const model = usePopupModel();\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusRedirect(model);\n\n const handleDelete = () => {\n console.log('Delete Item');\n };\n\n const popupId = useUniqueId();\n const visible = model.state.visibility !== 'hidden';\n React.useLayoutEffect(() => {\n if (visible && model.state.stackRef.current) {\n model.state.stackRef.current.setAttribute('id', popupId);\n }\n }, [model.state.stackRef, visible, popupId]);\n\n return (\n <Popup model={model}>\n <Flex cs={flexStyles}>\n <Popup.Target as={DeleteButton}>Delete Item</Popup.Target>\n <div aria-owns={popupId} style={{position: 'absolute'}}></div>\n <Popup.Popper>\n <Popup.Card cs={cardStyles}>\n <Popup.CloseIcon aria-label=\"Close\" />\n <Popup.Heading>Delete Item</Popup.Heading>\n <Popup.Body>\n <Box as=\"p\" cs={bodyStyles}>\n Are you sure you'd like to delete the item titled 'My Item'?\n </Box>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton>Cancel</Popup.CloseButton>\n <Popup.CloseButton as={DeleteButton} onClick={handleDelete}>\n Delete\n </Popup.CloseButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n <SecondaryButton>Next Focusable Button</SecondaryButton>\n <SecondaryButton>Focusable Button After Popup</SecondaryButton>\n </Flex>\n </Popup>\n );\n};\n```\n\n> **Accessibility Note**: The `useFocusRedirect` hook **will not** have any effect on the reading\n> order of a screen reader. Screen reader users may get confused or disoriented when popups are\n> portalled to the bottom of the document body. In this example, we're testing the use of\n> `aria-owns` on a sibling `<div>` element pointing to the `Popup.Card` component. This remaps the\n> hierarchy of the accessibility tree (in supported browsers) to address the reading order problem.\n> For more information, see\n> [Guides > Accessibility > Inline Popups](https://workday.github.io/canvas-kit/?path=/docs/guides-accessibility-inline-popups--docs).\n\n### Focus Trapping\n\nFocus trapping is similar to the [Focus Redirect](#focus-redirect) example, but will trap focus\ninside the popup instead of redirecting focus to adjacent focusable elements. This is necessary for\nmodal dialogs where users must focus on the contents of the dialog before proceeding.\n```tsx\nimport * as React from 'react';\n\nimport {DeleteButton, SecondaryButton} from '@workday/canvas-kit-react/button';\nimport {Box, Flex} from '@workday/canvas-kit-react/layout';\nimport {\n Popup,\n useCloseOnEscape,\n useCloseOnOutsideClick,\n useFocusTrap,\n useInitialFocus,\n usePopupModel,\n useReturnFocus,\n} from '@workday/canvas-kit-react/popup';\nimport {px2rem} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nexport const FocusTrap = () => {\n const model = usePopupModel();\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusTrap(model);\n\n const handleDelete = () => {\n console.log('Delete Item');\n };\n\n const popupId = 'popup-test-id';\n const visible = model.state.visibility !== 'hidden';\n React.useLayoutEffect(() => {\n if (visible && model.state.stackRef.current) {\n model.state.stackRef.current.setAttribute('id', popupId);\n }\n }, [model.state.stackRef, visible]);\n\n return (\n <Popup model={model}>\n <Flex cs={{gap: system.gap.sm}}>\n <Popup.Target as={DeleteButton}>Delete Item</Popup.Target>\n <div aria-owns={popupId} style={{position: 'absolute'}} />\n <Popup.Popper>\n <Popup.Card cs={{width: px2rem(400)}}>\n <Popup.CloseIcon aria-label=\"Close\" />\n <Popup.Heading>Delete Item</Popup.Heading>\n <Popup.Body>\n <Box as=\"p\" cs={{marginBlock: '0'}}>\n Are you sure you'd like to delete the item titled 'My Item'?\n </Box>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton>Cancel</Popup.CloseButton>\n <Popup.CloseButton as={DeleteButton} onClick={handleDelete}>\n Delete\n </Popup.CloseButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n <SecondaryButton>Next Focusable Button</SecondaryButton>\n <SecondaryButton>Focusable Button After Popup</SecondaryButton>\n </Flex>\n </Popup>\n );\n};\n```\n\n> **Accessibility Note**: Focus trapping will not prevent mouse users from breaking out of a focus\n> trap, nor will it prevent screen reader users from using virtual reading cursors from breaking\n> out. Consider using [Modal](/components/popups/modal/) instead when you need to focus users'\n> attention on a specific task inside of a popup..\n\n### Multiple Popups\n\nYou can render more than one `Popup` in the same view by giving each its own model. This example\npairs `Popup` with `useDialogModel` and `useModalModel` so you can compare **focus redirection**\n(Tab / Shift + Tab can move focus out of the first popup) and **focus trapping** (focus stays inside\nthe second popup until it closes). Opening one does not close the other.\n```tsx\nimport {useDialogModel} from '@workday/canvas-kit-react/dialog';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {useModalModel} from '@workday/canvas-kit-react/modal';\nimport {Popup} from '@workday/canvas-kit-react/popup';\nimport {createStyles, px2rem} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst flexStyles = createStyles({\n gap: system.gap.md,\n});\n\nconst popupStyles = createStyles({\n width: px2rem(400),\n});\n\nexport const MultiplePopups = () => {\n const dialogModel = useDialogModel();\n const modalModel = useModalModel();\n\n return (\n <Flex cs={flexStyles}>\n <Popup model={dialogModel}>\n <Popup.Target>Focus Redirect Popup</Popup.Target>\n <Popup.Popper>\n <Popup.Card cs={popupStyles}>\n <Popup.CloseIcon aria-label=\"Close\" size=\"small\" />\n <Popup.Heading>Focus Redirect Popup</Popup.Heading>\n <Popup.Body>\n <p>\n This popup uses the dialog model and will allow keyboard focus to escape when users\n press Tab or Shift + Tab.\n </p>\n </Popup.Body>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n <Popup model={modalModel}>\n <Popup.Target>Focus Trap Popup</Popup.Target>\n <Popup.Popper>\n <Popup.Card cs={popupStyles}>\n <Popup.CloseIcon aria-label=\"Close\" size=\"small\" />\n <Popup.Heading>Focus Trap Popup</Popup.Heading>\n <Popup.Body>\n <p>\n This popup uses the modal model and will trap keyboard focus when users press Tab or\n Shift + Tab.\n </p>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton>OK</Popup.CloseButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n </Flex>\n );\n};\n```\n\n### Nested Popups\n\nIf you need nested Popups within the same component, you can create multiple models and pass a\nunique model to each Popup. Popup comes with a `Popup.CloseButton` that uses a `Button` and adds\nprops via the `usePopupCloseButton` hook to ensure the popups hides and focus is returned. The `as`\ncan be used in a powerful way to do this by using `<Popup.CloseButton as={Popup.CloseButton}>` which\nwill mix in click handlers from both popups. This is not very intuitive, however. You can create\nprops that merge a click handler for both Popups by using `usePopupCloseButton` directly. The second\nparameter is props to be merged which will effectively hide both popups. Focus management is\npreserved.\n```tsx\nimport {SecondaryButton} from '@workday/canvas-kit-react/button';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {\n Popup,\n useCloseOnEscape,\n useCloseOnOutsideClick,\n useInitialFocus,\n usePopupCloseButton,\n usePopupModel,\n useReturnFocus,\n} from '@workday/canvas-kit-react/popup';\nimport {system} from '@workday/canvas-tokens-web';\n\nexport const NestedPopups = () => {\n const popup1 = usePopupModel();\n const popup2 = usePopupModel();\n\n useCloseOnOutsideClick(popup1);\n useCloseOnEscape(popup1);\n useInitialFocus(popup1);\n useReturnFocus(popup1);\n\n useCloseOnOutsideClick(popup2);\n useCloseOnEscape(popup2);\n useInitialFocus(popup2);\n useReturnFocus(popup2);\n\n const closeBothProps = usePopupCloseButton(popup1, usePopupCloseButton(popup2));\n\n return (\n <>\n <Popup model={popup1}>\n <Popup.Target>Open Popup 1</Popup.Target>\n <Popup.Popper>\n <Popup.Card aria-label=\"Popup 1\">\n <Popup.CloseIcon aria-label=\"Close\" size=\"small\" />\n <Popup.Body>\n <p style={{marginBlockStart: 0, marginBlockEnd: 0}}>Contents of Popup 1</p>\n </Popup.Body>\n <Flex cs={{gap: system.gap.md, padding: system.padding.xs}}>\n <Popup model={popup2}>\n <Popup.Target>Open Popup 2</Popup.Target>\n <Popup.Popper>\n <Popup.Card aria-label=\"Popup 2\">\n <Popup.CloseIcon aria-label=\"Close\" size=\"small\" />\n <Popup.Body>\n <p style={{marginBlockStart: 0, marginBlockEnd: 0}}>Contents of Popup 2</p>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton as={Popup.CloseButton} model={popup1}>\n Close Both (as)\n </Popup.CloseButton>\n <SecondaryButton {...closeBothProps}>Close Both (props)</SecondaryButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n </Flex>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n </>\n );\n};\n```\n\n> **Accessibility Note**: In this example, observe how users can traverse both opened popups using\n> the keyboard. This is likely to be a confusing experience for users and may necessitate focus\n> trapping inside each popup with careful consideration for setting initial focus and returning\n> focus.\n\n### Custom Target\n\nIt is common to have a custom target for your popup. Use the `as` prop to use your custom component.\nThe `Popup.Target` element will add `onClick` and `ref` to the provided component. Your provided\ntarget component must forward the `onClick` to an element for the Popup to open. The `as` will cause\n`Popup.Target` to inherit the interface of your custom target component. This means any props your\ntarget requires, `Popup.Target` now also requires. The example below has a `MyTarget` component that\nrequires a `label` prop.\n\n> **Note**: If your application needs to programmatically open a Popup without the user interacting\n> with the target button first, you'll also need to use `React.forwardRef` in your target component.\n> Without this, the Popup will open at the top-left of the window instead of around the target.\n```tsx\nimport React from 'react';\n\nimport {\n Popup,\n useCloseOnEscape,\n useCloseOnOutsideClick,\n useInitialFocus,\n usePopupModel,\n useReturnFocus,\n} from '@workday/canvas-kit-react/popup';\nimport {px2rem} from '@workday/canvas-kit-styling';\n\ninterface MyTargetProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n label: string;\n}\n\nconst MyTarget = React.forwardRef<HTMLButtonElement, MyTargetProps>(({label, ...props}, ref) => {\n return (\n <button {...props} ref={ref}>\n {label}\n </button>\n );\n});\n\nexport const CustomTarget = () => {\n const model = usePopupModel();\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n\n return (\n <Popup model={model}>\n <Popup.Target as={MyTarget} label=\"Open\" />\n <Popup.Popper>\n <Popup.Card cs={{minWidth: px2rem(320)}}>\n <Popup.CloseIcon aria-label=\"Close\" />\n <Popup.Heading>Popup</Popup.Heading>\n <Popup.Body>Contents</Popup.Body>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n );\n};\n```\n\n> **Accessibility Note**: Custom targets must be keyboard focusable, otherwise users will not be\n> able to access the popup. Bear in mind that click handlers only work with the keyboard when\n> applied to HTML `<button>` elements and it is **strongly recommended** to base your custom target\n> on a `<button>` element. Otherwise, you will be required to build in your own custom keyboard\n> event handlers for invoking the popup.\n\n### Full Screen API\n\nBy default, popups are created as children of the `document.body` element, but the `PopupStack`\nsupports the [Fullscreen API](https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API). When\nfullscreen is entered, the `PopupStack` will automatically create a new stacking context for all\nfuture popups. Any existing popups will disappear, but not be removed. They disappear because the\nfullscreen API is only showing content within the fullscreen element. There are instances where a\npopup may not close when fullscreen is exited:\n\n- The escape key is used to exit fullscreen\n- There is a button to exit fullscreen, but the popup doesn't use `useCloseOnOutsideClick`\n\nIf fullscreen is exited, popups within the fullscreen stacking context are not removed or\ntransferred automatically. If you do not handle this case, the popup may not render correctly. This\nexample shows a popup that closes when fullscreen is entered/exited and another popup that transfers\nthe popup's stack context when entering/exiting fullscreen.\n```tsx\nimport * as React from 'react';\nimport screenfull from 'screenfull';\n\nimport {SecondaryButton} from '@workday/canvas-kit-react/button';\nimport {useIsFullscreen} from '@workday/canvas-kit-react/common';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {\n Popup,\n useCloseOnEscape,\n useCloseOnFullscreenExit,\n useCloseOnOutsideClick,\n useFocusTrap,\n useInitialFocus,\n usePopupModel,\n useReturnFocus,\n useTransferOnFullscreenEnter,\n useTransferOnFullscreenExit,\n} from '@workday/canvas-kit-react/popup';\nimport {px2rem} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst SelfClosePopup = () => {\n const model = usePopupModel();\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusTrap(model);\n useCloseOnFullscreenExit(model);\n\n return (\n <Popup model={model}>\n <Popup.Target>Open Self-close Popup</Popup.Target>\n <Popup.Popper>\n <Popup.Card cs={{width: px2rem(400), padding: system.padding.md}}>\n <Popup.CloseIcon aria-label=\"Close\" />\n <Popup.Heading>Self-close Popup</Popup.Heading>\n <Popup.Body>\n <p>\n When in fullscreen, the escape key will be highjacked by the browser to exit\n fullscreen and <code>useCloseOnEscape</code> hook will not receive the escape key. To\n close when fullscreen is exited, use the <code>useCloseOnFullscreenExit</code> hook.\n </p>\n </Popup.Body>\n <Popup.CloseButton>Close</Popup.CloseButton>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n );\n};\n\nconst TransferClosePopup = () => {\n const model = usePopupModel();\n\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusTrap(model);\n useTransferOnFullscreenEnter(model);\n useTransferOnFullscreenExit(model);\n\n return (\n <Popup model={model}>\n <Popup.Target>Open Transfer Popup</Popup.Target>\n <Popup.Popper>\n <Popup.Card cs={{width: px2rem(400), padding: system.padding.md}}>\n <Popup.CloseIcon aria-label=\"Close\" />\n <Popup.Heading>Transfer Popup</Popup.Heading>\n <Popup.Body>\n <p>\n When in fullscreen, the escape key will be highjacked by the browser to exit\n fullscreen and <code>useCloseOnEscape</code> hook will not receive the escape key. To\n close when fullscreen is exited, use the <code>useTransferOnFullscreenExit</code>{' '}\n hook.\n </p>\n </Popup.Body>\n <Popup.CloseButton>Close</Popup.CloseButton>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n );\n};\n\nexport const FullScreen = () => {\n // you could make this a hook depending on which fullscreen library your application uses\n const fullscreenElementRef = React.useRef<HTMLDivElement>();\n const isFullscreen = useIsFullscreen();\n\n const enterFullScreen = () => {\n screenfull.request(fullscreenElementRef.current);\n };\n\n const exitFullscreen = () => {\n screenfull.exit();\n };\n\n return (\n <>\n <SecondaryButton onClick={enterFullScreen}>Open Fullscreen</SecondaryButton>\n <Flex\n ref={fullscreenElementRef}\n cs={{alignItems: 'center', justifyContent: 'center', background: system.color.bg.default}}\n >\n <Flex cs={{gap: system.gap.md}}>\n <SelfClosePopup />\n <TransferClosePopup />\n {isFullscreen ? (\n <SecondaryButton onClick={exitFullscreen}>Exit fullscreen</SecondaryButton>\n ) : null}\n </Flex>\n </Flex>\n </>\n );\n};\n```\n\n### Opening an External Window\n\nA popup can open an external window. This isn't supported directly. The `Popup.Popper` subcomponent\nis replaced with a custom subcomponent that connects to the Popup model and controls the lifecycle\nof the extenal window. Be sure to connect the `unload` event of both the parent `window` and the\nexternal child `window` to the lifecycle of the Popup model to prevent memory leaks or zombie\nwindows.\n```tsx\nimport React from 'react';\nimport ReactDOM from 'react-dom';\n\nimport {SecondaryButton} from '@workday/canvas-kit-react/button';\nimport {\n CanvasProvider,\n ContentDirection,\n PartialEmotionCanvasTheme,\n createSubcomponent,\n useMount,\n useTheme,\n} from '@workday/canvas-kit-react/common';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {Popup, usePopupModel} from '@workday/canvas-kit-react/popup';\nimport {Tooltip} from '@workday/canvas-kit-react/tooltip';\nimport {createStyles} from '@workday/canvas-kit-styling';\nimport {infoIcon} from '@workday/canvas-system-icons-web';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst mainContentStyles = createStyles({\n padding: system.padding.md,\n});\n\nexport interface ExternalWindowPortalProps {\n /**\n * Child components of WindowPortal\n */\n children: React.ReactNode;\n /**\n * Callback to close the popup\n */\n onWindowClose?: () => void;\n /**\n * Width of the popup window\n */\n width?: number;\n /**\n * Height of the popup window\n */\n height?: number;\n /**\n * The name of the popup window. If another popup opens with the same name, that instance will\n * be reused. Use caution with setting this value\n */\n target?: string;\n}\n\nasync function copyAssets(sourceDoc: Document, targetDoc: Document) {\n for (const font of (sourceDoc as any).fonts.values()) {\n (targetDoc as any).fonts.add(font);\n\n font.load();\n }\n\n await (targetDoc as any).fonts.ready;\n\n // The current ES lib version doesn't include iterable interfaces, so we cast as an iterable\n for (const styleSheet of sourceDoc.styleSheets as StyleSheetList & Iterable<CSSStyleSheet>) {\n if (styleSheet.cssRules) {\n // text based styles\n const styleEl = targetDoc.createElement('style');\n for (const cssRule of styleSheet.cssRules as CSSRuleList & Iterable<CSSRule>) {\n styleEl.appendChild(targetDoc.createTextNode(cssRule.cssText));\n }\n targetDoc.head.appendChild(styleEl);\n } else if (styleSheet.href) {\n // link based styles\n const linkEl = targetDoc.createElement('link');\n\n linkEl.rel = 'stylesheet';\n linkEl.href = styleSheet.href;\n targetDoc.head.appendChild(linkEl);\n }\n }\n}\n\nconst ExternalWindowPortal = ({\n children,\n width = 300,\n height = 500,\n target = '',\n onWindowClose,\n}: ExternalWindowPortalProps) => {\n const [portalElement, setPortalElement] = React.useState<HTMLDivElement | null>(null);\n\n useMount(() => {\n const newWindow = window.open(\n '', // url\n target,\n `width=${width},height=${height},left=100,top=100,popup=true`\n );\n\n if (newWindow) {\n // copy fonts and styles\n copyAssets(document, newWindow.document);\n\n const element = newWindow.document.createElement('div');\n newWindow.document.body.appendChild(element);\n setPortalElement(element);\n } else {\n onWindowClose();\n }\n\n const closeWindow = event => {\n onWindowClose();\n };\n\n window.addEventListener('unload', closeWindow);\n newWindow?.addEventListener('unload', closeWindow);\n\n return () => {\n window.removeEventListener('unload', closeWindow);\n newWindow?.removeEventListener('unload', closeWindow);\n newWindow?.close();\n };\n });\n\n if (!portalElement) {\n return null;\n }\n\n return ReactDOM.createPortal(<CanvasProvider>{children}</CanvasProvider>, portalElement);\n};\n\nconst PopupExternalWindow = createSubcomponent()({\n displayName: 'Popup.ExternalWindow',\n modelHook: usePopupModel,\n})<ExternalWindowPortalProps>(({children, ...elemProps}, Element, model) => {\n if (model.state.visibility === 'visible') {\n return (\n <ExternalWindowPortal onWindowClose={model.events.hide} {...elemProps}>\n {children}\n </ExternalWindowPortal>\n );\n }\n\n return null;\n});\n\nexport const ExternalWindow = () => {\n // useTheme is filling in the Canvas theme object if any keys are missing\n const canvasTheme: PartialEmotionCanvasTheme = useTheme({\n canvas: {\n // Switch to `ContentDirection.RTL` to change direction\n direction: ContentDirection.LTR,\n },\n });\n\n const model = usePopupModel();\n\n return (\n <CanvasProvider theme={canvasTheme}>\n <main className={mainContentStyles}>\n <p>Popup that opens a new Operating System Window</p>\n <Popup model={model}>\n <Tooltip title=\"Open External Window Tooltip\">\n <Popup.Target>Open External Window</Popup.Target>\n </Tooltip>\n <PopupExternalWindow>\n <p>External Window Contents! Mouse over the info icon to get a tooltip</p>\n <Flex cs={{gap: system.gap.sm}}>\n <Tooltip title=\"More information\">\n <SecondaryButton icon={infoIcon} />\n </Tooltip>\n <Popup.CloseButton>Close Window</Popup.CloseButton>\n </Flex>\n </PopupExternalWindow>\n </Popup>\n <p>Popup visibility: {model.state.visibility}</p>\n </main>\n </CanvasProvider>\n );\n};\n```\n\n### RTL\n\nThe Popup component automatically handles right-to-left rendering.\n\n> **Note:** This example shows an inaccessible open card for demonstration purposes.\n```tsx\nimport {DeleteButton, SecondaryButton} from '@workday/canvas-kit-react/button';\nimport {CanvasProvider} from '@workday/canvas-kit-react/common';\nimport {Box, Flex} from '@workday/canvas-kit-react/layout';\nimport {Popup} from '@workday/canvas-kit-react/popup';\nimport {px2rem} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nexport const RTL = () => {\n return (\n <CanvasProvider dir=\"rtl\">\n <Popup.Card cs={{width: px2rem(400)}}>\n <Popup.CloseIcon aria-label=\"\u05E1\u05D2\u05D5\u05E8\" />\n <Popup.Heading>\u05DC\u05DE\u05D7\u05D5\u05E7 \u05E4\u05E8\u05D9\u05D8</Popup.Heading>\n <Popup.Body>\n <Box as=\"p\" cs={{marginBlock: '0'}}>\n \u05D4\u05D0\u05DD \u05D1\u05E8\u05E6\u05D5\u05E0\u05DA \u05DC\u05DE\u05D7\u05D5\u05E7 \u05E4\u05E8\u05D9\u05D8 \u05D6\u05D4\n </Box>\n </Popup.Body>\n <Popup.ButtonGroup>\n <SecondaryButton>\u05DC\u05B0\u05D1\u05B7\u05D8\u05B5\u05DC</SecondaryButton>\n <DeleteButton>\u05DC\u05B4\u05DE\u05B0\u05D7\u05D5\u05B9\u05E7</DeleteButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </CanvasProvider>\n );\n};\n```\n\n## Accessibility\n\nPopup content is usually portaled to the bottom of the `document.body`, which can affect **reading\norder for screen readers** and **keyboard focus order**. For more information about Popup\naccessibility, check out our documentation at\n[Guides > Accessibility > Inline Popups](https://workday.github.io/canvas-kit/?path=/docs/guides-accessibility-inline-popups--docs).\n\n- For non-modal dialogs with `aria-owns` built-in to improve reading order for screen readers (that\n support it), check out the [**Dialog**](/components/popups/dialog/) component.\n- For modal dialogs with built-in overlays and focus traps, check out the\n [**Modal**](/components/popups/modal/) component.\n\n## Component API\n\n<>\n \n\n \n</>\n\n## Hooks\n\n<>\n \n\n{' '}\n\n{' '}\n\n{' '}\n\n{' '}\n\n{' '}\n\n{' '}\n\n{' '}\n\n{' '}\n\n{' '}\n\n{' '}\n\n{' '}\n\n{' '}\n\n \n</>\n\n## Specifications\n\n",
|
|
435
|
-
accessibilityProse: "## Accessibility\n\nPopup content is usually portaled to the bottom of the `document.body`, which can affect **reading\norder for screen readers** and **keyboard focus order**. For more information about Popup\naccessibility, check out our documentation at\n[Guides > Accessibility > Inline Popups](https://workday.github.io/canvas-kit/?path=/docs/guides-accessibility-inline-popups--docs).\n\n- For non-modal dialogs with `aria-owns` built-in to improve reading order for screen readers (that\n support it), check out the [**Dialog**](/components/popups/dialog/) component.\n- For modal dialogs with built-in overlays and focus traps, check out the\n [**Modal**](/components/popups/modal/) component."
|
|
434
|
+
mdxProse: "# Canvas Kit Popups\n\nA \"popup\" is a classification for a type of stacked UI element that appears \"on top\" of statically\npositioned content. Tooltips, Modals, Dropdown menus, etc are all examples of \"popups\". Canvas Kit\nhas a \"stack manager\" system for managing these popups. Different types of popups have different\nrequirements of behavior for UX and accessibility - we can call them behaviors, capabilities, or\ntraits. Canvas Kit comes with a number of [behavioral hooks](#hooks) in the form of React Hooks.\n\nYou should use the most semantic component for your use-case before using `Popup` directly, like\n`Modal`, which already has the correct behaviors built-in. If no component already exists that\nmatches your use case, you can use `Popup` and use our [hooks](#hooks). The `Popup` component comes\nwith a `Popup.Popper` subcomponent that positions a popup using [PopperJS](https://popper.js.org/)\nthat registers a popup with the `PopupStack` automatically and sets the popup model's `placement`\nproperty. `Popup.Popper` component and hooks work with the stack management system for correct\nrendering and accessibility behavior. If you cannot use `Popup.Popper`, use the\n[usePopupStack](#usepoupstack) hook to properly register and deregister the popup at the correct\ntime. If you cannot use our hooks, consider upgrading your component to use Hooks. If you cannot do\nthat, you'll have to look up the `PopupStack` package for the direct API and have a look at the\nsource code for our hooks into the `PopupStack` API.\n\nThis package comes with everything you need to build Popup UIs.\n\n[Buttons](/components/buttons/button)\n\n## Installation\n\n```sh\nyarn add @workday/canvas-kit-react\n```\n\n## Usage\n\nThe `Popup` component is a generic\n[Compound Component](/get-started/for-developers/documentation/compound-components/) that is used to\nbuild popup UIs that are not already covered by Canvas Kit.\n\n### Basic Example\n\nThe Popup has no pre-defined behaviors built in, therefore the `usePopupModel` must always be used\nto create a new `model`. This `model` is then used by all behavior hooks to apply additional popup\nbehaviors to the compound component group. The following example creates a typical popup around a\ntarget element and adds `useCloseOnOutsideClick`, `useCloseOnEscape`, `useInitialFocus`,\n`useReturnFocus`, and `useFocusRedirect` behaviors. You can read through the [hooks](#hooks) section\nto learn about all the popup behaviors. For accessibility, these behaviors should be included most\nof the time.\n```tsx\nimport {DeleteButton} from '@workday/canvas-kit-react/button';\nimport {Box} from '@workday/canvas-kit-react/layout';\nimport {\n Popup,\n useCloseOnEscape,\n useCloseOnOutsideClick,\n useFocusRedirect,\n useInitialFocus,\n usePopupModel,\n useReturnFocus,\n} from '@workday/canvas-kit-react/popup';\nimport {createStyles, px2rem} from '@workday/canvas-kit-styling';\n\nconst cardStyles = createStyles({\n width: px2rem(400),\n});\n\nconst bodyStyles = createStyles({\n marginBlock: '0',\n});\n\nexport const Basic = () => {\n const model = usePopupModel();\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusRedirect(model);\n\n const handleDelete = () => {\n console.log('Delete Item');\n };\n\n return (\n <Popup model={model}>\n <Popup.Target as={DeleteButton}>Delete Item</Popup.Target>\n <Popup.Popper placement=\"top\">\n <Popup.Card cs={cardStyles}>\n <Popup.CloseIcon aria-label=\"Close\" />\n <Popup.Heading>Delete Item</Popup.Heading>\n <Popup.Body>\n <Box as=\"p\" cs={bodyStyles}>\n Are you sure you'd like to delete the item titled 'My Item'?\n </Box>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton>Cancel</Popup.CloseButton>\n <Popup.CloseButton as={DeleteButton} onClick={handleDelete}>\n Delete\n </Popup.CloseButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n );\n};\n```\n\n### Initial Focus\n\nIf you want focus to move to a specific element when the popup is opened, set the `initialFocusRef`\nof the model. This is useful for popups that don't have a Close icon button near the top right of\nthe popup. In general, we recommend setting focus to the first interactive component inside the\npopup that is the least destructive action.\n```tsx\nimport React from 'react';\n\nimport {PrimaryButton} from '@workday/canvas-kit-react/button';\nimport {useUniqueId} from '@workday/canvas-kit-react/common';\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {\n Popup,\n useCloseOnEscape,\n useCloseOnOutsideClick,\n useFocusRedirect,\n useInitialFocus,\n usePopupModel,\n useReturnFocus,\n} from '@workday/canvas-kit-react/popup';\nimport {Text} from '@workday/canvas-kit-react/text';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\nimport {createStyles, px2rem} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst cardStyles = createStyles({\n width: px2rem(400),\n});\n\nconst bodyStyles = createStyles({\n marginBlock: '0',\n});\n\nconst columnStyles = createStyles({\n gap: system.gap.md,\n alignItems: 'flex-start',\n});\n\nconst InitialFocusOnButton = () => {\n const messageId = useUniqueId();\n const initialFocusRef = React.useRef(null);\n const model = usePopupModel({\n initialFocusRef,\n });\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusRedirect(model);\n\n return (\n <Popup model={model}>\n <Popup.Target>Initial focus: OK button</Popup.Target>\n <Popup.Popper placement={'bottom'}>\n <Popup.Card cs={cardStyles} aria-describedby={messageId}>\n <Popup.Heading>Confirmation</Popup.Heading>\n <Popup.Body>\n <Text cs={bodyStyles} id={messageId}>\n Your message has been sent!\n </Text>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton as={PrimaryButton} ref={initialFocusRef}>\n OK\n </Popup.CloseButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n );\n};\n\nconst InitialFocusOnTextInput = () => {\n const descriptionId = useUniqueId();\n const initialFocusRef = React.useRef<HTMLInputElement>(null);\n const model = usePopupModel({\n initialFocusRef,\n });\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusRedirect(model);\n\n return (\n <Popup model={model}>\n <Popup.Target>Initial focus: text input</Popup.Target>\n <Popup.Popper placement={'bottom'}>\n <Popup.Card cs={cardStyles} aria-describedby={descriptionId}>\n <Popup.Heading>Quick reply</Popup.Heading>\n <Popup.Body>\n <FormField>\n <FormField.Label>Message</FormField.Label>\n <FormField.Input as={TextInput} ref={initialFocusRef} />\n </FormField>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton>Cancel</Popup.CloseButton>\n <Popup.CloseButton as={PrimaryButton}>Send</Popup.CloseButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n );\n};\n\nconst InitialFocusOnHeading = () => {\n const headingFocusRef = React.useRef<HTMLHeadingElement>(null);\n const model = usePopupModel({\n initialFocusRef: headingFocusRef,\n });\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusRedirect(model);\n\n return (\n <Popup model={model}>\n <Popup.Target>Initial focus: heading</Popup.Target>\n <Popup.Popper placement={'bottom'}>\n <Popup.Card cs={cardStyles}>\n <Popup.Heading ref={headingFocusRef} tabIndex={-1}>\n Important notice\n </Popup.Heading>\n <Popup.Body>\n <Text cs={bodyStyles}>Review the summary below before continuing.</Text>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton as={PrimaryButton}>Continue</Popup.CloseButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n );\n};\n\nexport const InitialFocus = () => {\n return (\n <Flex cs={columnStyles}>\n <InitialFocusOnButton />\n <InitialFocusOnTextInput />\n <InitialFocusOnHeading />\n </Flex>\n );\n};\n```\n\n> **Accessibility Note**: When initial focus lands on a control **below** the title (such as the OK\n> button in the example above), assign a unique `id` to supplementary text and pass\n> `aria-describedby` on `Popup.Card`. This augments the included `aria-labelledby` reference to\n> `Popup.Heading` so screen readers can announce both the heading and any supplementary text\n> automatically. When initial focus is on the heading itself, add `tabIndex={-1}` to `Popup.Heading`\n> so the title can receive programmatic focus. Choose where focus goes based on your product and\n> accessibility requirements.\n\n### Focus Redirect\n\nFocus management is important to accessibility of popup contents. The following example shows\n`useFocusRedirect` being used to manage focus in and out of a Popup. This is very useful for\nnon-modal popups. Focus redirection tries to treat the Popup as if it were inline to the document.\nTabbing out of the Popup will close the Popup and move focus to an adjacent focusable element.\n```tsx\nimport * as React from 'react';\n\nimport {DeleteButton, SecondaryButton} from '@workday/canvas-kit-react/button';\nimport {useUniqueId} from '@workday/canvas-kit-react/common';\nimport {Box, Flex} from '@workday/canvas-kit-react/layout';\nimport {\n Popup,\n useCloseOnEscape,\n useCloseOnOutsideClick,\n useFocusRedirect,\n useInitialFocus,\n usePopupModel,\n useReturnFocus,\n} from '@workday/canvas-kit-react/popup';\nimport {createStyles, px2rem} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst cardStyles = createStyles({\n width: px2rem(400),\n});\n\nconst bodyStyles = createStyles({\n marginBlock: '0',\n});\n\nconst flexStyles = createStyles({\n gap: system.gap.md,\n padding: system.padding.xs,\n});\n\nexport const FocusRedirect = () => {\n const model = usePopupModel();\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusRedirect(model);\n\n const handleDelete = () => {\n console.log('Delete Item');\n };\n\n const popupId = useUniqueId();\n const visible = model.state.visibility !== 'hidden';\n React.useLayoutEffect(() => {\n if (visible && model.state.stackRef.current) {\n model.state.stackRef.current.setAttribute('id', popupId);\n }\n }, [model.state.stackRef, visible, popupId]);\n\n return (\n <Popup model={model}>\n <Flex cs={flexStyles}>\n <Popup.Target as={DeleteButton}>Delete Item</Popup.Target>\n <div aria-owns={popupId} style={{position: 'absolute'}}></div>\n <Popup.Popper>\n <Popup.Card cs={cardStyles}>\n <Popup.CloseIcon aria-label=\"Close\" />\n <Popup.Heading>Delete Item</Popup.Heading>\n <Popup.Body>\n <Box as=\"p\" cs={bodyStyles}>\n Are you sure you'd like to delete the item titled 'My Item'?\n </Box>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton>Cancel</Popup.CloseButton>\n <Popup.CloseButton as={DeleteButton} onClick={handleDelete}>\n Delete\n </Popup.CloseButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n <SecondaryButton>Next Focusable Button</SecondaryButton>\n <SecondaryButton>Focusable Button After Popup</SecondaryButton>\n </Flex>\n </Popup>\n );\n};\n```\n\n> **Accessibility Note**: The `useFocusRedirect` hook **will not** have any effect on the reading\n> order of a screen reader. Screen reader users may get confused or disoriented when popups are\n> portalled to the bottom of the document body. In this example, we're testing the use of\n> `aria-owns` on a sibling `<div>` element pointing to the `Popup.Card` component. This remaps the\n> hierarchy of the accessibility tree (in supported browsers) to address the reading order problem.\n> For more information, see\n> [Guides > Accessibility > Inline Popups](https://workday.github.io/canvas-kit/?path=/docs/guides-accessibility-inline-popups--docs).\n\n### Focus Trapping\n\nFocus trapping is similar to the [Focus Redirect](#focus-redirect) example, but will trap focus\ninside the popup instead of redirecting focus to adjacent focusable elements. This is necessary for\nmodal dialogs where users must focus on the contents of the dialog before proceeding.\n```tsx\nimport * as React from 'react';\n\nimport {DeleteButton, SecondaryButton} from '@workday/canvas-kit-react/button';\nimport {Box, Flex} from '@workday/canvas-kit-react/layout';\nimport {\n Popup,\n useCloseOnEscape,\n useCloseOnOutsideClick,\n useFocusTrap,\n useInitialFocus,\n usePopupModel,\n useReturnFocus,\n} from '@workday/canvas-kit-react/popup';\nimport {px2rem} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nexport const FocusTrap = () => {\n const model = usePopupModel();\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusTrap(model);\n\n const handleDelete = () => {\n console.log('Delete Item');\n };\n\n const popupId = 'popup-test-id';\n const visible = model.state.visibility !== 'hidden';\n React.useLayoutEffect(() => {\n if (visible && model.state.stackRef.current) {\n model.state.stackRef.current.setAttribute('id', popupId);\n }\n }, [model.state.stackRef, visible]);\n\n return (\n <Popup model={model}>\n <Flex cs={{gap: system.gap.sm}}>\n <Popup.Target as={DeleteButton}>Delete Item</Popup.Target>\n <div aria-owns={popupId} style={{position: 'absolute'}} />\n <Popup.Popper>\n <Popup.Card cs={{width: px2rem(400)}}>\n <Popup.CloseIcon aria-label=\"Close\" />\n <Popup.Heading>Delete Item</Popup.Heading>\n <Popup.Body>\n <Box as=\"p\" cs={{marginBlock: '0'}}>\n Are you sure you'd like to delete the item titled 'My Item'?\n </Box>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton>Cancel</Popup.CloseButton>\n <Popup.CloseButton as={DeleteButton} onClick={handleDelete}>\n Delete\n </Popup.CloseButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n <SecondaryButton>Next Focusable Button</SecondaryButton>\n <SecondaryButton>Focusable Button After Popup</SecondaryButton>\n </Flex>\n </Popup>\n );\n};\n```\n\n> **Accessibility Note**: Focus trapping will not prevent mouse users from breaking out of a focus\n> trap, nor will it prevent screen reader users from using virtual reading cursors from breaking\n> out. Consider using [Modal](/components/popups/modal/) instead when you need to focus users'\n> attention on a specific task inside of a popup..\n\n### Multiple Popups\n\nYou can render more than one `Popup` in the same view by giving each its own model. This example\npairs `Popup` with `useDialogModel` and `useModalModel` so you can compare **focus redirection**\n(Tab / Shift + Tab can move focus out of the first popup) and **focus trapping** (focus stays inside\nthe second popup until it closes). Opening one does not close the other.\n```tsx\nimport {useDialogModel} from '@workday/canvas-kit-react/dialog';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {useModalModel} from '@workday/canvas-kit-react/modal';\nimport {Popup} from '@workday/canvas-kit-react/popup';\nimport {createStyles, px2rem} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst flexStyles = createStyles({\n gap: system.gap.md,\n});\n\nconst popupStyles = createStyles({\n width: px2rem(400),\n});\n\nexport const MultiplePopups = () => {\n const dialogModel = useDialogModel();\n const modalModel = useModalModel();\n\n return (\n <Flex cs={flexStyles}>\n <Popup model={dialogModel}>\n <Popup.Target>Focus Redirect Popup</Popup.Target>\n <Popup.Popper>\n <Popup.Card cs={popupStyles}>\n <Popup.CloseIcon aria-label=\"Close\" size=\"small\" />\n <Popup.Heading>Focus Redirect Popup</Popup.Heading>\n <Popup.Body>\n <p>\n This popup uses the dialog model and will allow keyboard focus to escape when users\n press Tab or Shift + Tab.\n </p>\n </Popup.Body>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n <Popup model={modalModel}>\n <Popup.Target>Focus Trap Popup</Popup.Target>\n <Popup.Popper>\n <Popup.Card cs={popupStyles}>\n <Popup.CloseIcon aria-label=\"Close\" size=\"small\" />\n <Popup.Heading>Focus Trap Popup</Popup.Heading>\n <Popup.Body>\n <p>\n This popup uses the modal model and will trap keyboard focus when users press Tab or\n Shift + Tab.\n </p>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton>OK</Popup.CloseButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n </Flex>\n );\n};\n```\n\n### Nested Popups\n\nIf you need nested Popups within the same component, you can create multiple models and pass a\nunique model to each Popup. Popup comes with a `Popup.CloseButton` that uses a `Button` and adds\nprops via the `usePopupCloseButton` hook to ensure the popups hides and focus is returned. The `as`\ncan be used in a powerful way to do this by using `<Popup.CloseButton as={Popup.CloseButton}>` which\nwill mix in click handlers from both popups. This is not very intuitive, however. You can create\nprops that merge a click handler for both Popups by using `usePopupCloseButton` directly. The second\nparameter is props to be merged which will effectively hide both popups. Focus management is\npreserved.\n```tsx\nimport {SecondaryButton} from '@workday/canvas-kit-react/button';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {\n Popup,\n useCloseOnEscape,\n useCloseOnOutsideClick,\n useInitialFocus,\n usePopupCloseButton,\n usePopupModel,\n useReturnFocus,\n} from '@workday/canvas-kit-react/popup';\nimport {system} from '@workday/canvas-tokens-web';\n\nexport const NestedPopups = () => {\n const popup1 = usePopupModel();\n const popup2 = usePopupModel();\n\n useCloseOnOutsideClick(popup1);\n useCloseOnEscape(popup1);\n useInitialFocus(popup1);\n useReturnFocus(popup1);\n\n useCloseOnOutsideClick(popup2);\n useCloseOnEscape(popup2);\n useInitialFocus(popup2);\n useReturnFocus(popup2);\n\n const closeBothProps = usePopupCloseButton(popup1, usePopupCloseButton(popup2));\n\n return (\n <>\n <Popup model={popup1}>\n <Popup.Target>Open Popup 1</Popup.Target>\n <Popup.Popper>\n <Popup.Card aria-label=\"Popup 1\">\n <Popup.CloseIcon aria-label=\"Close\" size=\"small\" />\n <Popup.Body>\n <p style={{marginBlockStart: 0, marginBlockEnd: 0}}>Contents of Popup 1</p>\n </Popup.Body>\n <Flex cs={{gap: system.gap.md, padding: system.padding.xs}}>\n <Popup model={popup2}>\n <Popup.Target>Open Popup 2</Popup.Target>\n <Popup.Popper>\n <Popup.Card aria-label=\"Popup 2\">\n <Popup.CloseIcon aria-label=\"Close\" size=\"small\" />\n <Popup.Body>\n <p style={{marginBlockStart: 0, marginBlockEnd: 0}}>Contents of Popup 2</p>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton as={Popup.CloseButton} model={popup1}>\n Close Both (as)\n </Popup.CloseButton>\n <SecondaryButton {...closeBothProps}>Close Both (props)</SecondaryButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n </Flex>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n </>\n );\n};\n```\n\n> **Accessibility Note**: In this example, observe how users can traverse both opened popups using\n> the keyboard. This is likely to be a confusing experience for users and may necessitate focus\n> trapping inside each popup with careful consideration for setting initial focus and returning\n> focus.\n\n### Custom Target\n\nIt is common to have a custom target for your popup. Use the `as` prop to use your custom component.\nThe `Popup.Target` element will add `onClick` and `ref` to the provided component. Your provided\ntarget component must forward the `onClick` to an element for the Popup to open. The `as` will cause\n`Popup.Target` to inherit the interface of your custom target component. This means any props your\ntarget requires, `Popup.Target` now also requires. The example below has a `MyTarget` component that\nrequires a `label` prop.\n\n> **Note**: If your application needs to programmatically open a Popup without the user interacting\n> with the target button first, you'll also need to use `React.forwardRef` in your target component.\n> Without this, the Popup will open at the top-left of the window instead of around the target.\n```tsx\nimport React from 'react';\n\nimport {\n Popup,\n useCloseOnEscape,\n useCloseOnOutsideClick,\n useInitialFocus,\n usePopupModel,\n useReturnFocus,\n} from '@workday/canvas-kit-react/popup';\nimport {px2rem} from '@workday/canvas-kit-styling';\n\ninterface MyTargetProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n label: string;\n}\n\nconst MyTarget = React.forwardRef<HTMLButtonElement, MyTargetProps>(({label, ...props}, ref) => {\n return (\n <button {...props} ref={ref}>\n {label}\n </button>\n );\n});\n\nexport const CustomTarget = () => {\n const model = usePopupModel();\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n\n return (\n <Popup model={model}>\n <Popup.Target as={MyTarget} label=\"Open\" />\n <Popup.Popper>\n <Popup.Card cs={{minWidth: px2rem(320)}}>\n <Popup.CloseIcon aria-label=\"Close\" />\n <Popup.Heading>Popup</Popup.Heading>\n <Popup.Body>Contents</Popup.Body>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n );\n};\n```\n\n> **Accessibility Note**: Custom targets must be keyboard focusable, otherwise users will not be\n> able to access the popup. Bear in mind that click handlers only work with the keyboard when\n> applied to HTML `<button>` elements and it is **strongly recommended** to base your custom target\n> on a `<button>` element. Otherwise, you will be required to build in your own custom keyboard\n> event handlers for invoking the popup.\n\n### Full Screen API\n\nBy default, popups are created as children of the `document.body` element, but the `PopupStack`\nsupports the [Fullscreen API](https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API). When\nfullscreen is entered, the `PopupStack` will automatically create a new stacking context for all\nfuture popups. Any existing popups will disappear, but not be removed. They disappear because the\nfullscreen API is only showing content within the fullscreen element. There are instances where a\npopup may not close when fullscreen is exited:\n\n- The escape key is used to exit fullscreen\n- There is a button to exit fullscreen, but the popup doesn't use `useCloseOnOutsideClick`\n\nIf fullscreen is exited, popups within the fullscreen stacking context are not removed or\ntransferred automatically. If you do not handle this case, the popup may not render correctly. This\nexample shows a popup that closes when fullscreen is entered/exited and another popup that transfers\nthe popup's stack context when entering/exiting fullscreen.\n```tsx\nimport * as React from 'react';\nimport screenfull from 'screenfull';\n\nimport {SecondaryButton} from '@workday/canvas-kit-react/button';\nimport {useIsFullscreen} from '@workday/canvas-kit-react/common';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {\n Popup,\n useCloseOnEscape,\n useCloseOnFullscreenExit,\n useCloseOnOutsideClick,\n useFocusTrap,\n useInitialFocus,\n usePopupModel,\n useReturnFocus,\n useTransferOnFullscreenEnter,\n useTransferOnFullscreenExit,\n} from '@workday/canvas-kit-react/popup';\nimport {px2rem} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst SelfClosePopup = () => {\n const model = usePopupModel();\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusTrap(model);\n useCloseOnFullscreenExit(model);\n\n return (\n <Popup model={model}>\n <Popup.Target>Open Self-close Popup</Popup.Target>\n <Popup.Popper>\n <Popup.Card cs={{width: px2rem(400), padding: system.padding.md}}>\n <Popup.CloseIcon aria-label=\"Close\" />\n <Popup.Heading>Self-close Popup</Popup.Heading>\n <Popup.Body>\n <p>\n When in fullscreen, the escape key will be highjacked by the browser to exit\n fullscreen and <code>useCloseOnEscape</code> hook will not receive the escape key. To\n close when fullscreen is exited, use the <code>useCloseOnFullscreenExit</code> hook.\n </p>\n </Popup.Body>\n <Popup.CloseButton>Close</Popup.CloseButton>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n );\n};\n\nconst TransferClosePopup = () => {\n const model = usePopupModel();\n\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusTrap(model);\n useTransferOnFullscreenEnter(model);\n useTransferOnFullscreenExit(model);\n\n return (\n <Popup model={model}>\n <Popup.Target>Open Transfer Popup</Popup.Target>\n <Popup.Popper>\n <Popup.Card cs={{width: px2rem(400), padding: system.padding.md}}>\n <Popup.CloseIcon aria-label=\"Close\" />\n <Popup.Heading>Transfer Popup</Popup.Heading>\n <Popup.Body>\n <p>\n When in fullscreen, the escape key will be highjacked by the browser to exit\n fullscreen and <code>useCloseOnEscape</code> hook will not receive the escape key. To\n close when fullscreen is exited, use the <code>useTransferOnFullscreenExit</code>{' '}\n hook.\n </p>\n </Popup.Body>\n <Popup.CloseButton>Close</Popup.CloseButton>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n );\n};\n\nexport const FullScreen = () => {\n // you could make this a hook depending on which fullscreen library your application uses\n const fullscreenElementRef = React.useRef<HTMLDivElement>();\n const isFullscreen = useIsFullscreen();\n\n const enterFullScreen = () => {\n screenfull.request(fullscreenElementRef.current);\n };\n\n const exitFullscreen = () => {\n screenfull.exit();\n };\n\n return (\n <>\n <SecondaryButton onClick={enterFullScreen}>Open Fullscreen</SecondaryButton>\n <Flex\n ref={fullscreenElementRef}\n cs={{alignItems: 'center', justifyContent: 'center', background: system.color.bg.default}}\n >\n <Flex cs={{gap: system.gap.md}}>\n <SelfClosePopup />\n <TransferClosePopup />\n {isFullscreen ? (\n <SecondaryButton onClick={exitFullscreen}>Exit fullscreen</SecondaryButton>\n ) : null}\n </Flex>\n </Flex>\n </>\n );\n};\n```\n\n### Opening an External Window\n\nA popup can open an external window. This isn't supported directly. The `Popup.Popper` subcomponent\nis replaced with a custom subcomponent that connects to the Popup model and controls the lifecycle\nof the extenal window. Be sure to connect the `unload` event of both the parent `window` and the\nexternal child `window` to the lifecycle of the Popup model to prevent memory leaks or zombie\nwindows.\n```tsx\nimport React from 'react';\nimport ReactDOM from 'react-dom';\n\nimport {SecondaryButton} from '@workday/canvas-kit-react/button';\nimport {\n CanvasProvider,\n ContentDirection,\n PartialEmotionCanvasTheme,\n createSubcomponent,\n useMount,\n useTheme,\n} from '@workday/canvas-kit-react/common';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {Popup, usePopupModel} from '@workday/canvas-kit-react/popup';\nimport {Tooltip} from '@workday/canvas-kit-react/tooltip';\nimport {createStyles} from '@workday/canvas-kit-styling';\nimport {infoIcon} from '@workday/canvas-system-icons-web';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst mainContentStyles = createStyles({\n padding: system.padding.md,\n});\n\nexport interface ExternalWindowPortalProps {\n /**\n * Child components of WindowPortal\n */\n children: React.ReactNode;\n /**\n * Callback to close the popup\n */\n onWindowClose?: () => void;\n /**\n * Width of the popup window\n */\n width?: number;\n /**\n * Height of the popup window\n */\n height?: number;\n /**\n * The name of the popup window. If another popup opens with the same name, that instance will\n * be reused. Use caution with setting this value\n */\n target?: string;\n}\n\nasync function copyAssets(sourceDoc: Document, targetDoc: Document) {\n for (const font of (sourceDoc as any).fonts.values()) {\n (targetDoc as any).fonts.add(font);\n\n font.load();\n }\n\n await (targetDoc as any).fonts.ready;\n\n // The current ES lib version doesn't include iterable interfaces, so we cast as an iterable\n for (const styleSheet of sourceDoc.styleSheets as StyleSheetList & Iterable<CSSStyleSheet>) {\n if (styleSheet.cssRules) {\n // text based styles\n const styleEl = targetDoc.createElement('style');\n for (const cssRule of styleSheet.cssRules as CSSRuleList & Iterable<CSSRule>) {\n styleEl.appendChild(targetDoc.createTextNode(cssRule.cssText));\n }\n targetDoc.head.appendChild(styleEl);\n } else if (styleSheet.href) {\n // link based styles\n const linkEl = targetDoc.createElement('link');\n\n linkEl.rel = 'stylesheet';\n linkEl.href = styleSheet.href;\n targetDoc.head.appendChild(linkEl);\n }\n }\n}\n\nconst ExternalWindowPortal = ({\n children,\n width = 300,\n height = 500,\n target = '',\n onWindowClose,\n}: ExternalWindowPortalProps) => {\n const [portalElement, setPortalElement] = React.useState<HTMLDivElement | null>(null);\n\n useMount(() => {\n const newWindow = window.open(\n '', // url\n target,\n `width=${width},height=${height},left=100,top=100,popup=true`\n );\n\n if (newWindow) {\n // copy fonts and styles\n copyAssets(document, newWindow.document);\n\n const element = newWindow.document.createElement('div');\n newWindow.document.body.appendChild(element);\n setPortalElement(element);\n } else {\n onWindowClose();\n }\n\n const closeWindow = event => {\n onWindowClose();\n };\n\n window.addEventListener('unload', closeWindow);\n newWindow?.addEventListener('unload', closeWindow);\n\n return () => {\n window.removeEventListener('unload', closeWindow);\n newWindow?.removeEventListener('unload', closeWindow);\n newWindow?.close();\n };\n });\n\n if (!portalElement) {\n return null;\n }\n\n return ReactDOM.createPortal(<CanvasProvider>{children}</CanvasProvider>, portalElement);\n};\n\nconst PopupExternalWindow = createSubcomponent()({\n displayName: 'Popup.ExternalWindow',\n modelHook: usePopupModel,\n})<ExternalWindowPortalProps>(({children, ...elemProps}, Element, model) => {\n if (model.state.visibility === 'visible') {\n return (\n <ExternalWindowPortal onWindowClose={model.events.hide} {...elemProps}>\n {children}\n </ExternalWindowPortal>\n );\n }\n\n return null;\n});\n\nexport const ExternalWindow = () => {\n // useTheme is filling in the Canvas theme object if any keys are missing\n const canvasTheme: PartialEmotionCanvasTheme = useTheme({\n canvas: {\n // Switch to `ContentDirection.RTL` to change direction\n direction: ContentDirection.LTR,\n },\n });\n\n const model = usePopupModel();\n\n return (\n <CanvasProvider theme={canvasTheme}>\n <main className={mainContentStyles}>\n <p>Popup that opens a new Operating System Window</p>\n <Popup model={model}>\n <Tooltip title=\"Open External Window Tooltip\">\n <Popup.Target>Open External Window</Popup.Target>\n </Tooltip>\n <PopupExternalWindow>\n <p>External Window Contents! Mouse over the info icon to get a tooltip</p>\n <Flex cs={{gap: system.gap.sm}}>\n <Tooltip title=\"More information\">\n <SecondaryButton icon={infoIcon} />\n </Tooltip>\n <Popup.CloseButton>Close Window</Popup.CloseButton>\n </Flex>\n </PopupExternalWindow>\n </Popup>\n <p>Popup visibility: {model.state.visibility}</p>\n </main>\n </CanvasProvider>\n );\n};\n```\n\n### RTL\n\nThe Popup component automatically handles right-to-left rendering.\n\n> **Note:** This example shows an inaccessible open card for demonstration purposes.\n```tsx\nimport {DeleteButton, SecondaryButton} from '@workday/canvas-kit-react/button';\nimport {CanvasProvider} from '@workday/canvas-kit-react/common';\nimport {Box, Flex} from '@workday/canvas-kit-react/layout';\nimport {Popup} from '@workday/canvas-kit-react/popup';\nimport {px2rem} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nexport const RTL = () => {\n return (\n <CanvasProvider dir=\"rtl\">\n <Popup.Card cs={{width: px2rem(400)}}>\n <Popup.CloseIcon aria-label=\"\u05E1\u05D2\u05D5\u05E8\" />\n <Popup.Heading>\u05DC\u05DE\u05D7\u05D5\u05E7 \u05E4\u05E8\u05D9\u05D8</Popup.Heading>\n <Popup.Body>\n <Box as=\"p\" cs={{marginBlock: '0'}}>\n \u05D4\u05D0\u05DD \u05D1\u05E8\u05E6\u05D5\u05E0\u05DA \u05DC\u05DE\u05D7\u05D5\u05E7 \u05E4\u05E8\u05D9\u05D8 \u05D6\u05D4\n </Box>\n </Popup.Body>\n <Popup.ButtonGroup>\n <SecondaryButton>\u05DC\u05B0\u05D1\u05B7\u05D8\u05B5\u05DC</SecondaryButton>\n <DeleteButton>\u05DC\u05B4\u05DE\u05B0\u05D7\u05D5\u05B9\u05E7</DeleteButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </CanvasProvider>\n );\n};\n```\n\n## Accessibility\n\nEnsure users of assistive technology can discover, name, and operate a popup that is typically\nportaled to the end of `document.body`: the popup has an accessible name that matches its visible\nheading, keyboard users can open and dismiss it predictably, and focus and reading order remain\nusable despite portal placement (see\n[Guides > Accessibility > Inline Popups](https://workday.github.io/canvas-kit/?path=/docs/guides-accessibility-inline-popups--docs)).\nPrefer a semantic component before composing **Popup** directly:\n[**Dialog**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-dialog--docs) for a\nstandard non-modal dialog (behaviors and `aria-owns` built in), or\n[**Modal**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs) for\nblocking tasks with focus trapping and assistive sibling hiding (see also the W3C\n[Dialog (Modal) Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/)). Use **Popup** with\ncomposed hooks when you need a custom popup stack or behavior set that those components do not\nprovide.\n\n### Minimum Accessible Structure\n\nThe following matches the [Basic Example](#basic-example): hoist **`usePopupModel`**, compose the\nnon-modal behavior hooks on that model, place **`Popup.CloseIcon`** before **`Popup.Heading`** so\ndefault open focus lands on the dismiss control first, and use **`Popup.CloseButton`** for actions\nthat should also close the popup.\n\n```tsx\n\nconst Example = () => {\n const model = usePopupModel();\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusRedirect(model);\n\n return (\n <Popup model={model}>\n <Popup.Target as={DeleteButton}>Delete Item</Popup.Target>\n <Popup.Popper>\n <Popup.Card>\n <Popup.CloseIcon aria-label=\"Close\" />\n <Popup.Heading>Delete Item</Popup.Heading>\n <Popup.Body>\n <p>Are you sure you'd like to delete the item titled 'My Item'?</p>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton>Cancel</Popup.CloseButton>\n <Popup.CloseButton as={DeleteButton}>Delete</Popup.CloseButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n );\n};\n```\n\nInclude a dismiss control: **`Popup.CloseButton`** with visible text (for example \"Cancel\" or\n\"Close\"), and/or **`Popup.CloseIcon`** when the design uses an icon-only dismiss (requires\n**`aria-label`** or **`Tooltip`**). Pass the same **`model`** instance to **`Popup`** and every\nbehavior hook so focus and dismiss wiring share one stack.\n\n### Built-in Behaviors\n\nCanvas Kit applies ARIA and DOM wiring automatically via Popup subcomponents when you compose them.\nBehavioral hooks are **not** applied by `usePopupModel` alone\u2014you must call them (as in the Basic\nExample). Once applied, **do not duplicate them** in consuming code.\n\n**Popup behaviors** (_compose on the model; recommended for non-modal dialogs_):\n\n- `useInitialFocus` \u2014 moves focus into the popup when it opens (default: first focusable element in\n DOM order; optional override via `initialFocusRef` on the model)\n- `useReturnFocus` \u2014 returns focus to `Popup.Target` (or configured return target) when it closes\n- `useCloseOnEscape` \u2014 <kbd>Escape</kbd> closes the popup\n- `useCloseOnOutsideClick` \u2014 pointer interaction outside closes the popup\n- `useFocusRedirect` \u2014 <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> at the first or last\n focusable element inside the popup closes it and moves focus to the next or previous focusable\n element on the page (non-modal; **not** a focus trap; does **not** change screen reader reading\n order; does **not** provide `aria-owns`)\n\n**ARIA and DOM** (_applied by hooks/subcomponents_):\n\n- `Popup.Card`: `role=\"dialog\"`, `aria-labelledby` referencing the heading `id` (non-modal by\n default; page content is not hidden with `aria-hidden` unless you compose\n `useAssistiveHideSiblings`)\n- `Popup.Heading`: `id` wired to `Popup.Card`'s `aria-labelledby`\n- `Popup.Popper`: positions and registers the popup with the stack; unlike\n [**Dialog.Popper**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-dialog--docs),\n it does **not** set `aria-owns`\n- `Popup.CloseIcon` / `Popup.CloseButton`: `onClick` that calls `model.events.hide()`\n- `Popup.Target`: `ref` and `onClick` to open and to receive return focus\n\n**Implementation note on `aria-owns`:** `useFocusRedirect` does not provide `aria-owns`. When you\nneed remapped reading order for portaled content, add it yourself (see **Reading order** in\nAccessibility Requirements) or prefer **Dialog**, which wires `aria-owns` automatically. Support\nvaries by browser and screen reader.\n\n**Keyboard** (_trigger is `Popup.Target`, default `SecondaryButton`_):\n\n- <kbd>Enter</kbd> / <kbd>Space</kbd> on the trigger opens the popup (standard button behavior)\n- On open and close, focus is managed by **`useInitialFocus`** and **`useReturnFocus`** when those\n hooks are composed (application overrides: see **Focus management** in Accessibility Requirements)\n- <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> move focus forward and backward through\n interactive elements inside the popup (standard sequential focus behavior)\n- With **`useFocusRedirect`**, tabbing past the last or before the first focusable element closes\n the popup\n- <kbd>Escape</kbd> closes the popup when **`useCloseOnEscape`** is composed and returns focus per\n `useReturnFocus`\n\n**Screen reader expectations** (_when built-in behaviors and recommended hooks are used as\nintended_):\n\n- On open, assistive technology should announce the first focused control (often a dismiss control),\n the popup name (`Popup.Heading`), and `dialog` role\n- Background page content remains available to assistive technology unless you compose\n **`useAssistiveHideSiblings`** (prefer\n [**Modal**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs) for\n that pattern)\n- Reading order may still follow document order at the end of `body` unless `aria-owns` remapping is\n added and honored; support varies by browser and screen reader\n\n### Accessibility Requirements\n\nRequired in application code for an accessible Popup. Always hoist **`usePopupModel`** and pass the\nsame instance to **`Popup`** and behavior hooks. Rows marked _(conditional)_ apply only when the\nsituation matches\u2014otherwise omit.\n\n**If no design spec is provided:** compose the Basic Example hooks (`useCloseOnOutsideClick`,\n`useCloseOnEscape`, `useInitialFocus`, `useReturnFocus`, `useFocusRedirect`); use default focus\nbehavior; omit **`initialFocusRef`**, **`returnFocusRef`**, **`aria-describedby`**,\n**`aria-expanded`**, **`aria-haspopup`**, **`useFocusTrap`**, and **`useAssistiveHideSiblings`**.\nPrefer **Dialog** or **Modal** when those components already match the product need.\n\n**Focus management \u2014 defaults and developer prompts:** When **`useInitialFocus`** /\n**`useReturnFocus`** are composed, Canvas Kit handles open and close focus automatically. **State\nthe default to the developer first.** Only set **`initialFocusRef`** or **`returnFocusRef`** after\nthe developer (or an explicit design spec) chooses a non-default target. **Do not generate focus\nrefs by default.**\n\n| When | Default behavior | Ask the developer before overriding |\n| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| 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`**. |\n| 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`**.) |\n\nIf close **removes the trigger from the DOM**, **`returnFocusRef`** alone is not enough\u2014move focus\nafter the UI updates (for example with **`useLayoutEffect`**). See\n[Modal > Return Focus](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs#return-focus).\n\n**Custom targets** _(conditional)_: Apply when using a custom **`as`** component on\n**`Popup.Target`**. **`Popup.Target`** adds **`onClick`** and **`ref`**. Custom targets must forward\nboth to a **keyboard-focusable** element (prefer a native **`<button>`** or\n**`as={SecondaryButton}`** / another Canvas Kit button). Wrap the component in\n**`React.forwardRef`** when it does not forward refs by default (required if the popup can open\nprogrammatically before the user clicks the target).\n\n**Reading order (`aria-owns`)** _(conditional)_:\n\nPopup content is portaled; **`useFocusRedirect`** alone does not fix screen reader reading order.\nWhen a design needs remapped sequential reading order and you are not using **Dialog**, set an `id`\non the stack element and point a sibling element's **`aria-owns`** at that `id` (see the\n[Focus Redirect](#focus-redirect) example). Prefer **Dialog** when that pattern is the product\ndefault\u2014Dialog wires **`aria-owns`** for you.\n\n**Modal-like focus trapping** _(conditional)_:\n\nPrefer [**Modal**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs)\nfor blocking tasks. If you must compose trapping on **Popup**, use **`useFocusTrap`** with\n**`useAssistiveHideSiblings`** (and typically **omit** **`useFocusRedirect`**). Focus trapping does\nnot stop mouse or virtual-cursor escape by itself.\n\n**Open focus below the heading** _(conditional; see supplementary copy row below)_:\n\nButton-focus variant (matches [Initial Focus](#initial-focus)): when open focus lands on a primary\naction below the heading, wire **`aria-describedby`** to the supplementary copy. For the form-field\nvariant (focus an input), see\n[Dialog](https://workday.github.io/canvas-kit/?path=/docs/components-popups-dialog--docs#accessibility-requirements)\nor\n[Modal](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs#accessibility-requirements)\n**Open focus below the heading**.\n\n```tsx\n\nconst Example = () => {\n const messageId = useUniqueId();\n const initialFocusRef = React.useRef(null);\n const model = usePopupModel({initialFocusRef});\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusRedirect(model);\n\n return (\n <Popup model={model}>\n <Popup.Target>Open</Popup.Target>\n <Popup.Popper>\n <Popup.Card aria-describedby={messageId}>\n <Popup.Heading>Confirmation</Popup.Heading>\n <Popup.Body>\n <p id={messageId}>Your message has been sent!</p>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton as={PrimaryButton} ref={initialFocusRef}>\n OK\n </Popup.CloseButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n );\n};\n```\n\nWhen open focus lands on **`Popup.Heading`** itself, add **`tabIndex={-1}`** so the heading can\nreceive programmatic focus.\n\n| Requirement | How to satisfy |\n| ------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| 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. |\n| 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. |\n| 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`**). |\n| Keyboard-operable trigger | See **Custom targets** above. |\n| 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). |\n| Reading order remapping _(conditional)_ | See **Reading order (`aria-owns`)** above, or use **Dialog**. |\n| Focus trapping / hide siblings _(conditional)_ | Prefer **Modal**. If composing on **Popup**, see **Modal-like focus trapping** above. |\n| Open/closed state on the trigger _(conditional)_ | See **Wiring aria-expanded** below. **Default:** omit **`aria-expanded`** and **`aria-haspopup`**. |\n\n**Summary for code generation:**\n\n- **REQUIRED:** shared `usePopupModel`, non-modal behavior hooks (unless design omits), accessible\n name, dismiss control, keyboard-operable trigger\n- **CONDITIONAL:** **`initialFocusRef`**, **`returnFocusRef`**, **`aria-describedby`**,\n **`tabIndex={-1}`** on heading focus, **`aria-owns`**, **`useFocusTrap`** /\n **`useAssistiveHideSiblings`**, **`aria-expanded`** / **`aria-haspopup`**, **`forwardRef`** on\n custom **`Popup.Target`**\n\n**Wiring aria-expanded** _(conditional)_:\n\nThe **`aria-expanded`** pattern is **uncommon** for dialog-like Popups\u2014omit **`aria-expanded`** and\n**`aria-haspopup`** unless a review deliberately keeps open focus on the trigger (for example\n**`initialFocusRef`** on the trigger per design spec). When required, on **`Popup.Target`** set\n**`aria-expanded={model.state.visibility !== 'hidden'}`** and **`aria-haspopup=\"dialog\"`**. See\n**Focus management** and the open/closed-state row above.\n\n### Anti-Patterns\n\nDo **not** generate code that does the following (see **Accessibility Requirements** above for what\nto supply instead):\n\n- Manually set `role=\"dialog\"`, `aria-labelledby`, or the heading `id` on **`Popup.Card`** or\n **`Popup.Heading`** \u2014 Canvas Kit hooks wire these\n- Call behavior hooks on a **different** model instance than the one passed to **`Popup`**, or omit\n **`model={model}`** after composing hooks outside the container\n- Assume **`usePopupModel`** alone provides focus, escape, outside-click, or redirect behaviors \u2014\n compose the hooks (or use **Dialog** / **Modal**)\n- Omit **`Popup.Popper`**, render **`Popup.Card`** outside it, or add a custom portal/restructure\n instead of **`Popup` \u2192 `Popup.Popper` \u2192 `Popup.Card`** without using **`usePopupStack`**\n- Use **`open`** / **`onClose`** props on **`Popup`** \u2014 Popup has no controlled visibility props;\n use **`usePopupModel`** and **`model.events.show()`** / **`model.events.hide()`**\n- Reach for **Popup** + **`useFocusTrap`** / **`useAssistiveHideSiblings`** when **Modal** already\n matches the product need, or for non-modal UX when **Dialog** already matches\n- Set **`initialFocusRef`** or **`returnFocusRef`** by default \u2014 state the default focus behavior\n first and ask the developer before overriding (see **Focus management** in Accessibility\n Requirements)\n- Add **`aria-expanded`** / **`aria-haspopup`** on the default dialog-like Popup path, or bind\n **`aria-expanded`** to a static value (see **Wiring aria-expanded** in Accessibility Requirements)\n- Use a custom **`Popup.Target`** **`as`** component that does not forward **`ref`** to a focusable\n element \u2014 use **`React.forwardRef`** or a Canvas Kit button component instead\n- Rely on **`returnFocusRef`** alone when close **removes the trigger from the DOM** (see\n [Modal > Return Focus](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs#return-focus))\n- Nest multiple **Popup** instances without deliberate initial focus and return-focus planning\n- Assume **`useFocusRedirect`** fixes screen reader reading order, or that **`aria-owns`** remapping\n works in all browser and screen reader combinations \u2014 test your supported combinations\n- Expect **`Popup.Popper`** to set **`aria-owns`** like **Dialog.Popper** \u2014 it does not\n\n## Component API\n\n<>\n \n\n \n</>\n\n## Hooks\n\n<>\n \n\n{' '}\n\n{' '}\n\n{' '}\n\n{' '}\n\n{' '}\n\n{' '}\n\n{' '}\n\n{' '}\n\n{' '}\n\n{' '}\n\n{' '}\n\n{' '}\n\n \n</>\n\n## Specifications\n\n",
|
|
435
|
+
accessibilityProse: '## Accessibility\n\nEnsure users of assistive technology can discover, name, and operate a popup that is typically\nportaled to the end of `document.body`: the popup has an accessible name that matches its visible\nheading, keyboard users can open and dismiss it predictably, and focus and reading order remain\nusable despite portal placement (see\n[Guides > Accessibility > Inline Popups](https://workday.github.io/canvas-kit/?path=/docs/guides-accessibility-inline-popups--docs)).\nPrefer a semantic component before composing **Popup** directly:\n[**Dialog**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-dialog--docs) for a\nstandard non-modal dialog (behaviors and `aria-owns` built in), or\n[**Modal**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs) for\nblocking tasks with focus trapping and assistive sibling hiding (see also the W3C\n[Dialog (Modal) Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/)). Use **Popup** with\ncomposed hooks when you need a custom popup stack or behavior set that those components do not\nprovide.\n\n### Minimum Accessible Structure\n\nThe following matches the [Basic Example](#basic-example): hoist **`usePopupModel`**, compose the\nnon-modal behavior hooks on that model, place **`Popup.CloseIcon`** before **`Popup.Heading`** so\ndefault open focus lands on the dismiss control first, and use **`Popup.CloseButton`** for actions\nthat should also close the popup.\n\n```tsx\n\nconst Example = () => {\n const model = usePopupModel();\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusRedirect(model);\n\n return (\n <Popup model={model}>\n <Popup.Target as={DeleteButton}>Delete Item</Popup.Target>\n <Popup.Popper>\n <Popup.Card>\n <Popup.CloseIcon aria-label="Close" />\n <Popup.Heading>Delete Item</Popup.Heading>\n <Popup.Body>\n <p>Are you sure you\'d like to delete the item titled \'My Item\'?</p>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton>Cancel</Popup.CloseButton>\n <Popup.CloseButton as={DeleteButton}>Delete</Popup.CloseButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n );\n};\n```\n\nInclude a dismiss control: **`Popup.CloseButton`** with visible text (for example "Cancel" or\n"Close"), and/or **`Popup.CloseIcon`** when the design uses an icon-only dismiss (requires\n**`aria-label`** or **`Tooltip`**). Pass the same **`model`** instance to **`Popup`** and every\nbehavior hook so focus and dismiss wiring share one stack.\n\n### Built-in Behaviors\n\nCanvas Kit applies ARIA and DOM wiring automatically via Popup subcomponents when you compose them.\nBehavioral hooks are **not** applied by `usePopupModel` alone\u2014you must call them (as in the Basic\nExample). Once applied, **do not duplicate them** in consuming code.\n\n**Popup behaviors** (_compose on the model; recommended for non-modal dialogs_):\n\n- `useInitialFocus` \u2014 moves focus into the popup when it opens (default: first focusable element in\n DOM order; optional override via `initialFocusRef` on the model)\n- `useReturnFocus` \u2014 returns focus to `Popup.Target` (or configured return target) when it closes\n- `useCloseOnEscape` \u2014 <kbd>Escape</kbd> closes the popup\n- `useCloseOnOutsideClick` \u2014 pointer interaction outside closes the popup\n- `useFocusRedirect` \u2014 <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> at the first or last\n focusable element inside the popup closes it and moves focus to the next or previous focusable\n element on the page (non-modal; **not** a focus trap; does **not** change screen reader reading\n order; does **not** provide `aria-owns`)\n\n**ARIA and DOM** (_applied by hooks/subcomponents_):\n\n- `Popup.Card`: `role="dialog"`, `aria-labelledby` referencing the heading `id` (non-modal by\n default; page content is not hidden with `aria-hidden` unless you compose\n `useAssistiveHideSiblings`)\n- `Popup.Heading`: `id` wired to `Popup.Card`\'s `aria-labelledby`\n- `Popup.Popper`: positions and registers the popup with the stack; unlike\n [**Dialog.Popper**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-dialog--docs),\n it does **not** set `aria-owns`\n- `Popup.CloseIcon` / `Popup.CloseButton`: `onClick` that calls `model.events.hide()`\n- `Popup.Target`: `ref` and `onClick` to open and to receive return focus\n\n**Implementation note on `aria-owns`:** `useFocusRedirect` does not provide `aria-owns`. When you\nneed remapped reading order for portaled content, add it yourself (see **Reading order** in\nAccessibility Requirements) or prefer **Dialog**, which wires `aria-owns` automatically. Support\nvaries by browser and screen reader.\n\n**Keyboard** (_trigger is `Popup.Target`, default `SecondaryButton`_):\n\n- <kbd>Enter</kbd> / <kbd>Space</kbd> on the trigger opens the popup (standard button behavior)\n- On open and close, focus is managed by **`useInitialFocus`** and **`useReturnFocus`** when those\n hooks are composed (application overrides: see **Focus management** in Accessibility Requirements)\n- <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> move focus forward and backward through\n interactive elements inside the popup (standard sequential focus behavior)\n- With **`useFocusRedirect`**, tabbing past the last or before the first focusable element closes\n the popup\n- <kbd>Escape</kbd> closes the popup when **`useCloseOnEscape`** is composed and returns focus per\n `useReturnFocus`\n\n**Screen reader expectations** (_when built-in behaviors and recommended hooks are used as\nintended_):\n\n- On open, assistive technology should announce the first focused control (often a dismiss control),\n the popup name (`Popup.Heading`), and `dialog` role\n- Background page content remains available to assistive technology unless you compose\n **`useAssistiveHideSiblings`** (prefer\n [**Modal**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs) for\n that pattern)\n- Reading order may still follow document order at the end of `body` unless `aria-owns` remapping is\n added and honored; support varies by browser and screen reader\n\n### Accessibility Requirements\n\nRequired in application code for an accessible Popup. Always hoist **`usePopupModel`** and pass the\nsame instance to **`Popup`** and behavior hooks. Rows marked _(conditional)_ apply only when the\nsituation matches\u2014otherwise omit.\n\n**If no design spec is provided:** compose the Basic Example hooks (`useCloseOnOutsideClick`,\n`useCloseOnEscape`, `useInitialFocus`, `useReturnFocus`, `useFocusRedirect`); use default focus\nbehavior; omit **`initialFocusRef`**, **`returnFocusRef`**, **`aria-describedby`**,\n**`aria-expanded`**, **`aria-haspopup`**, **`useFocusTrap`**, and **`useAssistiveHideSiblings`**.\nPrefer **Dialog** or **Modal** when those components already match the product need.\n\n**Focus management \u2014 defaults and developer prompts:** When **`useInitialFocus`** /\n**`useReturnFocus`** are composed, Canvas Kit handles open and close focus automatically. **State\nthe default to the developer first.** Only set **`initialFocusRef`** or **`returnFocusRef`** after\nthe developer (or an explicit design spec) chooses a non-default target. **Do not generate focus\nrefs by default.**\n\n| When | Default behavior | Ask the developer before overriding |\n| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| 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`**. |\n| 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`**.) |\n\nIf close **removes the trigger from the DOM**, **`returnFocusRef`** alone is not enough\u2014move focus\nafter the UI updates (for example with **`useLayoutEffect`**). See\n[Modal > Return Focus](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs#return-focus).\n\n**Custom targets** _(conditional)_: Apply when using a custom **`as`** component on\n**`Popup.Target`**. **`Popup.Target`** adds **`onClick`** and **`ref`**. Custom targets must forward\nboth to a **keyboard-focusable** element (prefer a native **`<button>`** or\n**`as={SecondaryButton}`** / another Canvas Kit button). Wrap the component in\n**`React.forwardRef`** when it does not forward refs by default (required if the popup can open\nprogrammatically before the user clicks the target).\n\n**Reading order (`aria-owns`)** _(conditional)_:\n\nPopup content is portaled; **`useFocusRedirect`** alone does not fix screen reader reading order.\nWhen a design needs remapped sequential reading order and you are not using **Dialog**, set an `id`\non the stack element and point a sibling element\'s **`aria-owns`** at that `id` (see the\n[Focus Redirect](#focus-redirect) example). Prefer **Dialog** when that pattern is the product\ndefault\u2014Dialog wires **`aria-owns`** for you.\n\n**Modal-like focus trapping** _(conditional)_:\n\nPrefer [**Modal**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs)\nfor blocking tasks. If you must compose trapping on **Popup**, use **`useFocusTrap`** with\n**`useAssistiveHideSiblings`** (and typically **omit** **`useFocusRedirect`**). Focus trapping does\nnot stop mouse or virtual-cursor escape by itself.\n\n**Open focus below the heading** _(conditional; see supplementary copy row below)_:\n\nButton-focus variant (matches [Initial Focus](#initial-focus)): when open focus lands on a primary\naction below the heading, wire **`aria-describedby`** to the supplementary copy. For the form-field\nvariant (focus an input), see\n[Dialog](https://workday.github.io/canvas-kit/?path=/docs/components-popups-dialog--docs#accessibility-requirements)\nor\n[Modal](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs#accessibility-requirements)\n**Open focus below the heading**.\n\n```tsx\n\nconst Example = () => {\n const messageId = useUniqueId();\n const initialFocusRef = React.useRef(null);\n const model = usePopupModel({initialFocusRef});\n\n useCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusRedirect(model);\n\n return (\n <Popup model={model}>\n <Popup.Target>Open</Popup.Target>\n <Popup.Popper>\n <Popup.Card aria-describedby={messageId}>\n <Popup.Heading>Confirmation</Popup.Heading>\n <Popup.Body>\n <p id={messageId}>Your message has been sent!</p>\n </Popup.Body>\n <Popup.ButtonGroup>\n <Popup.CloseButton as={PrimaryButton} ref={initialFocusRef}>\n OK\n </Popup.CloseButton>\n </Popup.ButtonGroup>\n </Popup.Card>\n </Popup.Popper>\n </Popup>\n );\n};\n```\n\nWhen open focus lands on **`Popup.Heading`** itself, add **`tabIndex={-1}`** so the heading can\nreceive programmatic focus.\n\n| Requirement | How to satisfy |\n| ------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| 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. |\n| 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. |\n| 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`**). |\n| Keyboard-operable trigger | See **Custom targets** above. |\n| 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). |\n| Reading order remapping _(conditional)_ | See **Reading order (`aria-owns`)** above, or use **Dialog**. |\n| Focus trapping / hide siblings _(conditional)_ | Prefer **Modal**. If composing on **Popup**, see **Modal-like focus trapping** above. |\n| Open/closed state on the trigger _(conditional)_ | See **Wiring aria-expanded** below. **Default:** omit **`aria-expanded`** and **`aria-haspopup`**. |\n\n**Summary for code generation:**\n\n- **REQUIRED:** shared `usePopupModel`, non-modal behavior hooks (unless design omits), accessible\n name, dismiss control, keyboard-operable trigger\n- **CONDITIONAL:** **`initialFocusRef`**, **`returnFocusRef`**, **`aria-describedby`**,\n **`tabIndex={-1}`** on heading focus, **`aria-owns`**, **`useFocusTrap`** /\n **`useAssistiveHideSiblings`**, **`aria-expanded`** / **`aria-haspopup`**, **`forwardRef`** on\n custom **`Popup.Target`**\n\n**Wiring aria-expanded** _(conditional)_:\n\nThe **`aria-expanded`** pattern is **uncommon** for dialog-like Popups\u2014omit **`aria-expanded`** and\n**`aria-haspopup`** unless a review deliberately keeps open focus on the trigger (for example\n**`initialFocusRef`** on the trigger per design spec). When required, on **`Popup.Target`** set\n**`aria-expanded={model.state.visibility !== \'hidden\'}`** and **`aria-haspopup="dialog"`**. See\n**Focus management** and the open/closed-state row above.\n\n### Anti-Patterns\n\nDo **not** generate code that does the following (see **Accessibility Requirements** above for what\nto supply instead):\n\n- Manually set `role="dialog"`, `aria-labelledby`, or the heading `id` on **`Popup.Card`** or\n **`Popup.Heading`** \u2014 Canvas Kit hooks wire these\n- Call behavior hooks on a **different** model instance than the one passed to **`Popup`**, or omit\n **`model={model}`** after composing hooks outside the container\n- Assume **`usePopupModel`** alone provides focus, escape, outside-click, or redirect behaviors \u2014\n compose the hooks (or use **Dialog** / **Modal**)\n- Omit **`Popup.Popper`**, render **`Popup.Card`** outside it, or add a custom portal/restructure\n instead of **`Popup` \u2192 `Popup.Popper` \u2192 `Popup.Card`** without using **`usePopupStack`**\n- Use **`open`** / **`onClose`** props on **`Popup`** \u2014 Popup has no controlled visibility props;\n use **`usePopupModel`** and **`model.events.show()`** / **`model.events.hide()`**\n- Reach for **Popup** + **`useFocusTrap`** / **`useAssistiveHideSiblings`** when **Modal** already\n matches the product need, or for non-modal UX when **Dialog** already matches\n- Set **`initialFocusRef`** or **`returnFocusRef`** by default \u2014 state the default focus behavior\n first and ask the developer before overriding (see **Focus management** in Accessibility\n Requirements)\n- Add **`aria-expanded`** / **`aria-haspopup`** on the default dialog-like Popup path, or bind\n **`aria-expanded`** to a static value (see **Wiring aria-expanded** in Accessibility Requirements)\n- Use a custom **`Popup.Target`** **`as`** component that does not forward **`ref`** to a focusable\n element \u2014 use **`React.forwardRef`** or a Canvas Kit button component instead\n- Rely on **`returnFocusRef`** alone when close **removes the trigger from the DOM** (see\n [Modal > Return Focus](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs#return-focus))\n- Nest multiple **Popup** instances without deliberate initial focus and return-focus planning\n- Assume **`useFocusRedirect`** fixes screen reader reading order, or that **`aria-owns`** remapping\n works in all browser and screen reader combinations \u2014 test your supported combinations\n- Expect **`Popup.Popper`** to set **`aria-owns`** like **Dialog.Popper** \u2014 it does not'
|
|
436
436
|
},
|
|
437
437
|
pill: {
|
|
438
438
|
title: "Components/Indicators/Pill",
|
|
@@ -452,15 +452,15 @@ var stories_config_default = {
|
|
|
452
452
|
title: "Components/Popups/Modal",
|
|
453
453
|
storybookUrl: "https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs",
|
|
454
454
|
mdxPath: "modules/react/modal/stories/Modal.mdx",
|
|
455
|
-
mdxProse: "# Canvas Kit Modal\n\nA Modal component is a type of Dialog that renders a translucent overlay that prevents user\ninteraction with the rest of the page. A Modal will render the rest of the page inert until the\nModal is dismissed. A Modal should be used when the user needs to be presented with important\ninformation that must be interacted with before continuing interaction with the rest of the page.\n\nFor tasks that do not require blocking the rest of the page, consider the non-modal\n[**Dialog**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-dialog--docs)\ncomponent instead.\n\n## Installation\n\n```sh\nyarn add @workday/canvas-kit-react\n```\n\n## Usage\n\n### Basic\n\nThe basic behavior of a modal is to hide all content from all users that is \"behind\" the modal\ndialog.\n```tsx\nimport {PrimaryButton} from '@workday/canvas-kit-react/button';\nimport {Box} from '@workday/canvas-kit-react/layout';\nimport {Modal} from '@workday/canvas-kit-react/modal';\n\nexport const Basic = () => {\n const handleAcknowledge = () => {\n console.log('License Acknowledged');\n };\n\n const handleCancel = () => {\n console.log('Cancel clicked');\n };\n\n return (\n <Modal>\n <Modal.Target as={PrimaryButton}>Open License</Modal.Target>\n <Modal.Overlay>\n <Modal.Card>\n <Modal.CloseIcon aria-label=\"Close\" />\n <Modal.Heading>MIT License</Modal.Heading>\n <Modal.Body>\n <Box as=\"p\" cs={{marginBlock: '0'}}>\n Permission is hereby granted, free of charge, to any person obtaining a copy of this\n software and associated documentation files (the \"Software\").\n </Box>\n </Modal.Body>\n <Modal.ButtonGroup>\n <Modal.CloseButton onClick={handleCancel}>Cancel</Modal.CloseButton>\n <Modal.CloseButton as={PrimaryButton} onClick={handleAcknowledge}>\n Acknowledge\n </Modal.CloseButton>\n </Modal.ButtonGroup>\n </Modal.Card>\n </Modal.Overlay>\n </Modal>\n );\n};\n```\n\n### Without Close Icon\n\nIf you wish to remove the close icon button, you can simply omit the `Modal.CloseButton`\nsubcomponent. If you have a modal dialog that requires the user to accept instead of dismiss through\nan escape key or clicking outside the modal, you must create a new `PopupModel` without those\nbehaviors and hand that model to the Modal dialog component.\n```tsx\nimport React from 'react';\n\nimport {DeleteButton} from '@workday/canvas-kit-react/button';\nimport {useUniqueId} from '@workday/canvas-kit-react/common';\nimport {Box} from '@workday/canvas-kit-react/layout';\nimport {Modal} from '@workday/canvas-kit-react/modal';\nimport {\n useAssistiveHideSiblings,\n useDisableBodyScroll,\n useFocusTrap,\n useInitialFocus,\n usePopupModel,\n useReturnFocus,\n} from '@workday/canvas-kit-react/popup';\n\nexport const WithoutCloseIcon = () => {\n const longDescId = useUniqueId();\n const cancelBtnRef = React.useRef(null);\n const model = usePopupModel({\n initialFocusRef: cancelBtnRef,\n });\n\n // disable useCloseOnEscape and useCloseOnOverlayClick\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusTrap(model);\n useAssistiveHideSiblings(model);\n useDisableBodyScroll(model);\n const handleDelete = () => {\n console.log('Deleted item');\n };\n\n return (\n <Modal model={model}>\n <Modal.Target as={DeleteButton}>Delete Item</Modal.Target>\n <Modal.Overlay>\n <Modal.Card aria-describedby={longDescId}>\n <Modal.Heading>Delete Item</Modal.Heading>\n <Modal.Body>\n <Box as=\"p\" id={longDescId} cs={{marginBlock: '0'}}>\n Are you sure you want to delete the item?\n </Box>\n </Modal.Body>\n <Modal.ButtonGroup>\n <Modal.CloseButton ref={cancelBtnRef}>Cancel</Modal.CloseButton>\n <Modal.CloseButton as={DeleteButton} onClick={handleDelete}>\n Delete\n </Modal.CloseButton>\n </Modal.ButtonGroup>\n </Modal.Card>\n </Modal.Overlay>\n </Modal>\n );\n};\n```\n\n### Custom Focus\n\nBy default, the Modal makes sure the first focusable element receives focus when the Modal is\nopened. Most of the time, this is the `Modal.CloseIcon` button. If that element isn't present, the\nModal will use the Modal Heading to make sure screen reader users have focus near the start of the\nModal's content. This allows screen reader users to discover the Modal's content more naturally\nwithout having to navigate back up again. Sometimes, it is a better user experience to focus on a\ndifferent element. The following example shows how `initialFocusRef` can be used to change which\nelement receives focus when the modal opens.\n```tsx\nimport React from 'react';\n\nimport {PrimaryButton} from '@workday/canvas-kit-react/button';\nimport {useUniqueId} from '@workday/canvas-kit-react/common';\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {Box} from '@workday/canvas-kit-react/layout';\nimport {Modal, useModalModel} from '@workday/canvas-kit-react/modal';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\nimport {system} from '@workday/canvas-tokens-web';\n\nexport const CustomFocus = () => {\n const longDescID = useUniqueId();\n const ref = React.useRef<HTMLInputElement>(null);\n const [value, setValue] = React.useState('');\n const model = useModalModel({\n initialFocusRef: ref,\n });\n\n const handleAcknowledge = () => {\n console.log('Acknowledged license');\n };\n\n return (\n <Modal model={model}>\n <Modal.Target as={PrimaryButton}>Acknowledge License</Modal.Target>\n <Modal.Overlay>\n <Modal.Card aria-describedby={longDescID}>\n <Modal.CloseIcon aria-label=\"Close\" />\n <Modal.Heading>Acknowledge License</Modal.Heading>\n <Modal.Body>\n <Box as=\"p\" id={longDescID} cs={{marginBlockStart: 0, marginBlockEnd: system.gap.md}}>\n Enter your initials to acknowledge the license.\n </Box>\n <FormField>\n <FormField.Label>Initials</FormField.Label>\n <FormField.Input\n as={TextInput}\n ref={ref}\n value={value}\n grow\n onChange={e => setValue(e.currentTarget.value)}\n />\n </FormField>\n </Modal.Body>\n <Modal.ButtonGroup>\n <Modal.CloseButton>Cancel</Modal.CloseButton>\n <Modal.CloseButton as={PrimaryButton} onClick={handleAcknowledge}>\n Acknowledge\n </Modal.CloseButton>\n </Modal.ButtonGroup>\n </Modal.Card>\n </Modal.Overlay>\n </Modal>\n );\n};\n```\n\n> **Accessibility Note**: When initial focus lands on a control **below** the heading (for example,\n> a text field instead of the close button), give supplementary copy a unique `id` and pass\n> **`aria-describedby`** on **`Modal.Card`** so screen readers can announce both the dialog name and\n> that text. For more examples of custom focus techniques, see\n> [Popup > Initial Focus](https://workday.github.io/canvas-kit/?path=/docs/components-popups-popup--docs#initial-focus).\n\n### Return Focus\n\nBy default, the Modal will return focus to the `Modal.Target` element. When you open the modal with\n`model.events.show()` (without `Modal.Target`), set **`returnFocusRef`** on the model to the element\nthat should receive focus when the modal closes\u2014for example the button that opened it. That covers\ncancel, Escape, and the close icon: focus returns to the control the user activated.\n\nIf confirming an action **removes** that control from the document (such as deleting the row that\nheld the delete button), `returnFocusRef` alone cannot land on a **new** target. The example below\nuses **`useLayoutEffect`** after the list updates to move focus to another row\u2019s delete control, or\nto empty-state text when no files remain.\n```tsx\nimport React from 'react';\n\nimport {DeleteButton} from '@workday/canvas-kit-react/button';\nimport {useUniqueId} from '@workday/canvas-kit-react/common';\nimport {Box, Flex} from '@workday/canvas-kit-react/layout';\nimport {Modal, useModalModel} from '@workday/canvas-kit-react/modal';\nimport {Heading, Text} from '@workday/canvas-kit-react/text';\nimport {Tooltip} from '@workday/canvas-kit-react/tooltip';\nimport {createStyles} from '@workday/canvas-kit-styling';\nimport {trashIcon} from '@workday/canvas-system-icons-web';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst INITIAL_FILES = ['Resume.docx', 'Cover_Letter.docx', 'References.docx'];\n\nconst headingStyles = createStyles({\n marginBlock: '0',\n});\n\nconst emptyStateStyles = createStyles({\n maxWidth: '28rem',\n outline: 'none',\n});\n\nconst listStyles = createStyles({\n flexDirection: 'column',\n gap: system.gap.md,\n marginBlock: '0',\n padding: '0',\n listStyle: 'none',\n maxWidth: '28rem',\n});\n\nconst rowStyles = createStyles({\n alignItems: 'center',\n justifyContent: 'space-between',\n gap: system.gap.md,\n width: '100%',\n});\n\nfunction fileNameId(name: string) {\n return `return-focus-file-${name.replace(/[^a-zA-Z0-9]/g, '_')}`;\n}\n\n/** Index of a delete button to focus after removing `deletedIndex`, or empty list. */\nfunction nextListFocusAfterDelete(deletedIndex: number, lengthBeforeDelete: number) {\n if (lengthBeforeDelete <= 1) {\n return 'empty' as const;\n }\n return deletedIndex < lengthBeforeDelete - 1 ? deletedIndex : deletedIndex - 1;\n}\n\nexport const ReturnFocus = () => {\n const [items, setItems] = React.useState<string[]>(() => [...INITIAL_FILES]);\n const [confirmingFileName, setConfirmingFileName] = React.useState<string | null>(null);\n const bodyTextId = useUniqueId();\n\n const returnFocusRef = React.useRef<HTMLButtonElement | null>(null);\n const cancelButtonRef = React.useRef<HTMLButtonElement>(null);\n const deleteButtonRefs = React.useRef<(HTMLButtonElement | null)[]>([]);\n const emptyStateRef = React.useRef<HTMLDivElement>(null);\n const pendingDeleteIndexRef = React.useRef<number | null>(null);\n const postDeleteFocusRef = React.useRef<number | 'empty' | null>(null);\n\n const model = useModalModel({\n returnFocusRef,\n initialFocusRef: cancelButtonRef,\n });\n\n React.useEffect(() => {\n if (model.state.visibility === 'hidden') {\n setConfirmingFileName(null);\n pendingDeleteIndexRef.current = null;\n }\n }, [model.state.visibility]);\n\n React.useLayoutEffect(() => {\n if (postDeleteFocusRef.current === null) {\n return;\n }\n if (postDeleteFocusRef.current === 'empty') {\n emptyStateRef.current?.focus();\n } else {\n deleteButtonRefs.current[postDeleteFocusRef.current]?.focus();\n }\n postDeleteFocusRef.current = null;\n }, [items]);\n\n const openDeleteModal = (index: number) => {\n pendingDeleteIndexRef.current = index;\n setConfirmingFileName(items[index]);\n returnFocusRef.current = deleteButtonRefs.current[index];\n model.events.show();\n };\n\n const handleConfirmDelete = () => {\n const idx = pendingDeleteIndexRef.current;\n if (idx === null) {\n return;\n }\n postDeleteFocusRef.current = nextListFocusAfterDelete(idx, items.length);\n pendingDeleteIndexRef.current = null;\n setItems(prev => prev.filter((_, i) => i !== idx));\n };\n\n return (\n <Modal model={model}>\n <Heading as=\"h4\" size=\"small\" cs={headingStyles}>\n Uploaded Files\n </Heading>\n <Box>\n {items.length > 0 ? (\n <Flex as=\"ul\" cs={listStyles}>\n {items.map((name, index) => (\n <Flex as=\"li\" key={name} cs={rowStyles}>\n <Text as=\"span\" id={fileNameId(name)}>\n {name}\n </Text>\n <Tooltip title=\"Delete\">\n <DeleteButton\n aria-describedby={fileNameId(name)}\n icon={trashIcon}\n ref={el => {\n deleteButtonRefs.current[index] = el;\n }}\n onClick={() => openDeleteModal(index)}\n />\n </Tooltip>\n </Flex>\n ))}\n </Flex>\n ) : (\n <Box ref={emptyStateRef} tabIndex={-1} cs={emptyStateStyles}>\n <Text>No files remaining.</Text>\n </Box>\n )}\n </Box>\n <Modal.Overlay>\n <Modal.Card aria-describedby={bodyTextId}>\n <Modal.Heading>Delete file?</Modal.Heading>\n <Modal.Body>\n <Text id={bodyTextId}>\n {confirmingFileName\n ? `Are you sure you want to delete ${confirmingFileName}?`\n : 'Are you sure you want to delete this file?'}\n </Text>\n </Modal.Body>\n <Modal.ButtonGroup>\n <Modal.CloseButton ref={cancelButtonRef}>Cancel</Modal.CloseButton>\n <Modal.CloseButton as={DeleteButton} onClick={handleConfirmDelete}>\n Delete\n </Modal.CloseButton>\n </Modal.ButtonGroup>\n </Modal.Card>\n </Modal.Overlay>\n </Modal>\n );\n};\n```\n\n> **Accessibility Note**: After an item is deleted, focus is returned to the next item in the list\n> or to the empty state text when no items remain.\n\n### Custom Target\n\nIt is common to have a custom target for your modal. Use the `as` prop to use your custom component.\nThe `Modal.Target` element will add `onClick` and `ref` to the provided component. Your provided\ntarget component must forward the `onClick` to an element for the Modal to open. The `as` will cause\n`Modal.Target` to inherit the interface of your custom target component. This means any props your\ntarget requires, `Modal.Target` now also requires. The example below has a `MyTarget` component that\nrequires a `label` prop.\n\n> **Note**: If your application needs to programmatically open a Modal without the user interacting\n> with the target button first, you'll also need to use `React.forwardRef` in your target component.\n> Without this, the Modal will open at the top-left of the window instead of around the target.\n```tsx\nimport React from 'react';\n\nimport {Modal} from '@workday/canvas-kit-react/modal';\n\ninterface MyTargetProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n label: string;\n}\n\nconst MyTarget = ({label, ...props}: MyTargetProps) => {\n return <button {...props}>{label}</button>;\n};\n\nexport const CustomTarget = () => {\n return (\n <Modal>\n <Modal.Target as={MyTarget} label=\"Open\" />\n <Modal.Overlay>\n <Modal.Card>\n <Modal.CloseIcon aria-label=\"Close\" />\n <Modal.Heading>Modal Heading</Modal.Heading>\n <Modal.Body>\n Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec semper facilisis dolor\n quis facilisis. Aenean tempor eget quam et semper. Nam malesuada rhoncus euismod.\n Quisque vel urna feugiat, dictum risus sed, pulvinar nulla. Sed gravida, elit non\n iaculis blandit, ligula tortor posuere mauris, vitae cursus turpis nunc non arcu.\n </Modal.Body>\n </Modal.Card>\n </Modal.Overlay>\n </Modal>\n );\n};\n```\n\n> **Accessibility Note**: Custom targets must be keyboard focusable, otherwise users will not be\n> able to access the modal. Bear in mind that click handlers only work with the keyboard when\n> applied to HTML `<button>` elements and it is **strongly recommended** to base your custom target\n> on a `<button>` element. Otherwise, you will be required to build in your own custom keyboard\n> event handlers for invoking the modal.\n\n### Body Content Overflow\n\nThe Modal automatically handles overflowing content inside the `Modal.Body` element. If contents are\nlarger than the browser's height will allow, the content will overflow with a scrollbar. You may\nneed to restrict the height of your browser to observe the overflow.\n```tsx\nimport {PrimaryButton} from '@workday/canvas-kit-react/button';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {Modal} from '@workday/canvas-kit-react/modal';\nimport {system} from '@workday/canvas-tokens-web';\n\nexport const BodyOverflow = () => {\n const handleAcknowledge = () => {\n console.log('License Acknowledged');\n };\n\n const handleCancel = () => {\n console.log('Cancel clicked');\n };\n\n return (\n <Modal>\n <Modal.Target as={PrimaryButton}>Open License</Modal.Target>\n <Modal.Overlay>\n <Modal.Card>\n <Modal.CloseIcon aria-label=\"Close\" />\n <Modal.Heading>MIT License</Modal.Heading>\n <Modal.Body tabIndex={0}>\n <p style={{marginBlockStart: 0}}>\n Permission is hereby granted, free of charge, to any person obtaining a copy of this\n software and associated documentation files (the \"Software\"), to deal in the Software\n without restriction, including without limitation the rights to use, copy, modify,\n merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n permit persons to whom the Software is furnished to do so, subject to the following\n conditions:\n </p>\n <p>\n The above copyright notice and this permission notice shall be included in all copies\n or substantial portions of the Software.\n </p>\n <p>\n THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE\n OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n </p>\n <p>\n Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor\n incididunt ut labore et dolore magna aliqua. Amet massa vitae tortor condimentum\n lacinia quis. Fermentum odio eu feugiat pretium nibh ipsum consequat nisl. Sed lectus\n vestibulum mattis ullamcorper velit sed. Rutrum tellus pellentesque eu tincidunt\n tortor aliquam nulla. Vitae turpis massa sed elementum tempus egestas sed sed risus.\n Cursus vitae congue mauris rhoncus aenean vel elit scelerisque mauris. Id neque\n aliquam vestibulum morbi blandit cursus risus at. Vel eros donec ac odio tempor orci.\n Ac felis donec et odio pellentesque diam volutpat. Laoreet non curabitur gravida arcu\n ac tortor dignissim. Rhoncus urna neque viverra justo nec ultrices dui. Bibendum arcu\n vitae elementum curabitur vitae nunc sed velit dignissim. Sed risus pretium quam\n vulputate dignissim suspendisse in est. Curabitur gravida arcu ac tortor. Nam libero\n justo laoreet sit amet cursus sit amet. Arcu dui vivamus arcu felis bibendum ut\n tristique et egestas. Eros donec ac odio tempor orci dapibus ultrices. At erat\n pellentesque adipiscing commodo elit at. Dignissim cras tincidunt lobortis feugiat\n vivamus at augue.\n </p>\n <p>\n Amet commodo nulla facilisi nullam vehicula ipsum. Blandit libero volutpat sed cras.\n Quam lacus suspendisse faucibus interdum posuere. Aenean euismod elementum nisi quis\n eleifend. Orci nulla pellentesque dignissim enim sit amet venenatis. Diam vel quam\n elementum pulvinar etiam non quam lacus. Sit amet dictum sit amet justo donec enim\n diam vulputate. Tincidunt ornare massa eget egestas purus. Pulvinar neque laoreet\n suspendisse interdum consectetur libero id faucibus. Morbi tincidunt augue interdum\n velit. Nullam non nisi est sit amet.\n </p>\n <p style={{marginBlockEnd: 0}}>\n Aliquet enim tortor at auctor urna nunc id cursus metus. Leo urna molestie at\n elementum eu facilisis. Consectetur purus ut faucibus pulvinar elementum integer.\n Volutpat est velit egestas dui id ornare arcu odio. At consectetur lorem donec massa\n sapien. Condimentum vitae sapien pellentesque habitant. Pellentesque habitant morbi\n tristique senectus. Et molestie ac feugiat sed lectus vestibulum. Arcu risus quis\n varius quam quisque. Turpis massa tincidunt dui ut ornare lectus sit amet. Magna eget\n est lorem ipsum dolor sit. Suspendisse faucibus interdum posuere lorem ipsum. Nisi\n vitae suscipit tellus mauris a diam maecenas sed. Ipsum dolor sit amet consectetur\n adipiscing. Ultricies integer quis auctor elit sed. Scelerisque varius morbi enim nunc\n faucibus a. Tortor consequat id porta nibh venenatis cras. Consectetur adipiscing elit\n ut aliquam purus sit.\n </p>\n </Modal.Body>\n <Modal.ButtonGroup>\n <Modal.CloseButton onClick={handleCancel}>Cancel</Modal.CloseButton>\n <Modal.CloseButton as={PrimaryButton} onClick={handleAcknowledge}>\n Acknowledge\n </Modal.CloseButton>\n </Modal.ButtonGroup>\n </Modal.Card>\n </Modal.Overlay>\n </Modal>\n );\n};\n```\n\n> **Accessibility Note**: When body content overflows, ensure users can scroll that region **using\n> only the keyboard**. Mouse users can drag scrollbars, but keyboard users need another path. In\n> this example, **`tabIndex={0}`** is set on **`Modal.Body`** so the scrollable area can receive\n> focus; once focused, **arrow keys** move the viewport within the overflowing content.\n\n### Full overlay scrolling\n\nIf content is large, scrolling the entire overlay container is an option. Use the\n`Modal.OverflowOverlay` component instead of the `Modal.Overlay` component. The `Modal.Card`'s\n`maxHeight` and `height` will need to be reset to `inherit` to prevent any internal overflow.\n\nThis has the effect of scrolling the heading, close button, and any action buttons. If this type of\nscrolling behavior is not desired, try the [Body Content Overflow](#body-content-overflow) method.\n```tsx\nimport {PrimaryButton} from '@workday/canvas-kit-react/button';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {Modal} from '@workday/canvas-kit-react/modal';\nimport {system} from '@workday/canvas-tokens-web';\n\nexport const FullOverflow = () => {\n const handleAcknowledge = () => {\n console.log('License Acknowledged');\n };\n\n const handleCancel = () => {\n console.log('Cancel clicked');\n };\n\n return (\n <Modal>\n <Modal.Target as={PrimaryButton}>Open License</Modal.Target>\n <Modal.OverflowOverlay>\n <Modal.Card cs={{maxHeight: 'inherit', height: 'inherit'}}>\n <Modal.CloseIcon aria-label=\"Close\" />\n <Modal.Heading>MIT License</Modal.Heading>\n <Modal.Body tabIndex={0}>\n <p style={{marginBlockStart: 0}}>\n Permission is hereby granted, free of charge, to any person obtaining a copy of this\n software and associated documentation files (the \"Software\"), to deal in the Software\n without restriction, including without limitation the rights to use, copy, modify,\n merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n permit persons to whom the Software is furnished to do so, subject to the following\n conditions:\n </p>\n <p>\n The above copyright notice and this permission notice shall be included in all copies\n or substantial portions of the Software.\n </p>\n <p>\n THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE\n OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n </p>\n <p>\n Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor\n incididunt ut labore et dolore magna aliqua. Amet massa vitae tortor condimentum\n lacinia quis. Fermentum odio eu feugiat pretium nibh ipsum consequat nisl. Sed lectus\n vestibulum mattis ullamcorper velit sed. Rutrum tellus pellentesque eu tincidunt\n tortor aliquam nulla. Vitae turpis massa sed elementum tempus egestas sed sed risus.\n Cursus vitae congue mauris rhoncus aenean vel elit scelerisque mauris. Id neque\n aliquam vestibulum morbi blandit cursus risus at. Vel eros donec ac odio tempor orci.\n Ac felis donec et odio pellentesque diam volutpat. Laoreet non curabitur gravida arcu\n ac tortor dignissim. Rhoncus urna neque viverra justo nec ultrices dui. Bibendum arcu\n vitae elementum curabitur vitae nunc sed velit dignissim. Sed risus pretium quam\n vulputate dignissim suspendisse in est. Curabitur gravida arcu ac tortor. Nam libero\n justo laoreet sit amet cursus sit amet. Arcu dui vivamus arcu felis bibendum ut\n tristique et egestas. Eros donec ac odio tempor orci dapibus ultrices. At erat\n pellentesque adipiscing commodo elit at. Dignissim cras tincidunt lobortis feugiat\n vivamus at augue.\n </p>\n <p>\n Amet commodo nulla facilisi nullam vehicula ipsum. Blandit libero volutpat sed cras.\n Quam lacus suspendisse faucibus interdum posuere. Aenean euismod elementum nisi quis\n eleifend. Orci nulla pellentesque dignissim enim sit amet venenatis. Diam vel quam\n elementum pulvinar etiam non quam lacus. Sit amet dictum sit amet justo donec enim\n diam vulputate. Tincidunt ornare massa eget egestas purus. Pulvinar neque laoreet\n suspendisse interdum consectetur libero id faucibus. Morbi tincidunt augue interdum\n velit. Nullam non nisi est sit amet.\n </p>\n <p style={{marginBlockEnd: 0}}>\n Aliquet enim tortor at auctor urna nunc id cursus metus. Leo urna molestie at\n elementum eu facilisis. Consectetur purus ut faucibus pulvinar elementum integer.\n Volutpat est velit egestas dui id ornare arcu odio. At consectetur lorem donec massa\n sapien. Condimentum vitae sapien pellentesque habitant. Pellentesque habitant morbi\n tristique senectus. Et molestie ac feugiat sed lectus vestibulum. Arcu risus quis\n varius quam quisque. Turpis massa tincidunt dui ut ornare lectus sit amet. Magna eget\n est lorem ipsum dolor sit. Suspendisse faucibus interdum posuere lorem ipsum. Nisi\n vitae suscipit tellus mauris a diam maecenas sed. Ipsum dolor sit amet consectetur\n adipiscing. Ultricies integer quis auctor elit sed. Scelerisque varius morbi enim nunc\n faucibus a. Tortor consequat id porta nibh venenatis cras. Consectetur adipiscing elit\n ut aliquam purus sit.\n </p>\n </Modal.Body>\n <Modal.ButtonGroup>\n <Modal.CloseButton onClick={handleCancel}>Cancel</Modal.CloseButton>\n <Modal.CloseButton as={PrimaryButton} onClick={handleAcknowledge}>\n Acknowledge\n </Modal.CloseButton>\n </Modal.ButtonGroup>\n </Modal.Card>\n </Modal.OverflowOverlay>\n </Modal>\n );\n};\n```\n\n### Form Modal\n\nThe `Modal.Card` can be turned into a `form` element to make a form modal. The `model` should be\nhoisted to allow for form validation and allow you to control when the modal closes.\n```tsx\nimport React from 'react';\n\nimport {PrimaryButton} from '@workday/canvas-kit-react/button';\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {Modal, useModalModel} from '@workday/canvas-kit-react/modal';\nimport {Select} from '@workday/canvas-kit-react/select';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\nimport {plusIcon} from '@workday/canvas-system-icons-web';\n\nconst FAVORITE_COLOR_OPTIONS = ['Blue', 'Yellow'];\n\nexport const FormModal = () => {\n const model = useModalModel();\n\n const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault(); // prevent a page reload\n\n // do form validation here\n\n console.log('form data', {\n first: (event.currentTarget.elements.namedItem('first') as HTMLInputElement).value,\n last: (event.currentTarget.elements.namedItem('last') as HTMLInputElement).value,\n favoriteColor: (event.currentTarget.elements.namedItem('favoriteColor') as HTMLInputElement)\n .value,\n });\n\n // if it looks good, submit to the server and close the modal\n model.events.hide();\n };\n\n return (\n <Modal model={model}>\n <Modal.Target icon={plusIcon}>Create New User</Modal.Target>\n <Modal.Overlay>\n <Modal.Card as=\"form\" onSubmit={onSubmit}>\n <Modal.CloseIcon aria-label=\"Close\" />\n <Modal.Heading>New User</Modal.Heading>\n <Modal.Body>\n <FormField grow>\n <FormField.Label>First Name</FormField.Label>\n <FormField.Input as={TextInput} name=\"first\" />\n </FormField>\n <FormField grow>\n <FormField.Label>Last Name</FormField.Label>\n <FormField.Input as={TextInput} name=\"last\" />\n </FormField>\n <FormField grow>\n <FormField.Label>Favorite Color</FormField.Label>\n <FormField.Field>\n <Select items={FAVORITE_COLOR_OPTIONS}>\n <FormField.Input as={Select.Input} name=\"favoriteColor\" />\n <Select.Popper>\n <Select.Card>\n <Select.List>{item => <Select.Item>{item}</Select.Item>}</Select.List>\n </Select.Card>\n </Select.Popper>\n </Select>\n </FormField.Field>\n </FormField>\n </Modal.Body>\n <Modal.ButtonGroup>\n <Modal.CloseButton>Cancel</Modal.CloseButton>\n <PrimaryButton type=\"submit\">Submit</PrimaryButton>\n </Modal.ButtonGroup>\n </Modal.Card>\n </Modal.Overlay>\n </Modal>\n );\n};\n```\n\n## Accessibility\n\n`Modal` uses the default modal model (`useModalModel`), which composes **`useInitialFocus`**,\n**`useReturnFocus`**, **`useCloseOnOverlayClick`**, **`useCloseOnEscape`**, **`useFocusTrap`**,\n**`useAssistiveHideSiblings`**, and **`useDisableBodyScroll`**.\n\n**`Modal.Card`** exposes **`role=\"dialog\"`** and **`aria-labelledby`** referencing the `id` on\n**`Modal.Heading`**, so the dialog has an accessible name that matches the visible heading. If you\ndo not use **`Modal.Heading`**, add an **`aria-label`** on **`Modal.Card`** instead.\n\n**`aria-modal`:** The card sets **`aria-modal=\"false\"`**. When **`aria-modal`** is `true`, some\nassistive technologies hide everything outside the dialog\u2014including portaled UI owned by the dialog\n(such as a Select menu rendered as a sibling of the modal). Canvas Kit keeps\n**`aria-modal=\"false\"`** for a better VoiceOver experience while **`useAssistiveHideSiblings`**\napplies **`aria-hidden`** to siblings of the modal stack so background content stays hidden from\nassistive technology while the modal is open.\n\nUnlike [**Dialog**](/components/popups/dialog/), Modal does **not** add the sibling **`aria-owns`**\npattern used to remap reading order for portaled non-modal dialogs. Focus moves into the modal when\nit opens, and sibling hiding reduces exposure to content behind the overlay. For portals, reading\norder, and related tradeoffs, see\n[Guides > Accessibility > Inline Popups](https://workday.github.io/canvas-kit/?path=/docs/guides-accessibility-inline-popups--docs).\n\n[Modal Dialog Pattern | APG | WAI | W3C](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/)\n\n- Prefer **`Modal.Heading`** so the dialog is properly labelled; avoid leaving a dialog without an\n accessible name.\n- Ensure icon-only controls such as **`Modal.CloseIcon`** include an accessible name. Prefer the\n `Tooltip` component to provide a visible label, or a translated `aria-label` string is acceptable.\n\n### Navigation\n\n- **Enter** / **Space**: Open the modal (standard button behavior on the trigger). When it opens,\n focus moves to the **first focusable element** inside the modal in DOM order\u2014often the close\n control\u2014or to the element referenced by **`initialFocusRef`** on the model when set.\n- **Tab** / **Shift + Tab**: Move through focusable elements inside the modal; focus **stays**\n within the modal (**focus trap**).\n- **Escape**: Closes the modal and returns focus to **`Modal.Target`** (or the configured return\n target, such as **`returnFocusRef`**).\n\n### Screen Reader Experience\n\n- **When the modal opens:** Screen readers should announce the first focused control (often the\n close button), the dialog's name (**`Modal.Heading`**) and role.\n- **Background content:** Sibling elements of the modal stack receive **`aria-hidden=\"true\"`** while\n the modal is visible, which hides the rest of the page from many assistive technologies. Mouse\n users are blocked by the overlay and inert interaction expectations; always verify behavior in\n your supported browser and screen reader combinations.\n- **Focus trap limits:** Trapping **keyboard** focus does not stop mouse users from interacting\n outside the dialog card, and some screen reader users can move a virtual cursor outside the\n trapped region. Treat the trap as the primary keyboard affordance, not a hard security boundary.\n\n## Component API\n\n## Specifications\n\n",
|
|
456
|
-
accessibilityProse: '## Accessibility\n\n`Modal` uses the default modal model (`useModalModel`), which composes **`useInitialFocus`**,\n**`useReturnFocus`**, **`useCloseOnOverlayClick`**, **`useCloseOnEscape`**, **`useFocusTrap`**,\n**`useAssistiveHideSiblings`**, and **`useDisableBodyScroll`**.\n\n**`Modal.Card`** exposes **`role="dialog"`** and **`aria-labelledby`** referencing the `id` on\n**`Modal.Heading`**, so the dialog has an accessible name that matches the visible heading. If you\ndo not use **`Modal.Heading`**, add an **`aria-label`** on **`Modal.Card`** instead.\n\n**`aria-modal`:** The card sets **`aria-modal="false"`**. When **`aria-modal`** is `true`, some\nassistive technologies hide everything outside the dialog\u2014including portaled UI owned by the dialog\n(such as a Select menu rendered as a sibling of the modal). Canvas Kit keeps\n**`aria-modal="false"`** for a better VoiceOver experience while **`useAssistiveHideSiblings`**\napplies **`aria-hidden`** to siblings of the modal stack so background content stays hidden from\nassistive technology while the modal is open.\n\nUnlike [**Dialog**](/components/popups/dialog/), Modal does **not** add the sibling **`aria-owns`**\npattern used to remap reading order for portaled non-modal dialogs. Focus moves into the modal when\nit opens, and sibling hiding reduces exposure to content behind the overlay. For portals, reading\norder, and related tradeoffs, see\n[Guides > Accessibility > Inline Popups](https://workday.github.io/canvas-kit/?path=/docs/guides-accessibility-inline-popups--docs).\n\n[Modal Dialog Pattern | APG | WAI | W3C](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/)\n\n- Prefer **`Modal.Heading`** so the dialog is properly labelled; avoid leaving a dialog without an\n accessible name.\n- Ensure icon-only controls such as **`Modal.CloseIcon`** include an accessible name. Prefer the\n `Tooltip` component to provide a visible label, or a translated `aria-label` string is acceptable.\n\n### Navigation\n\n- **Enter** / **Space**: Open the modal (standard button behavior on the trigger). When it opens,\n focus moves to the **first focusable element** inside the modal in DOM order\u2014often the close\n control\u2014or to the element referenced by **`initialFocusRef`** on the model when set.\n- **Tab** / **Shift + Tab**: Move through focusable elements inside the modal; focus **stays**\n within the modal (**focus trap**).\n- **Escape**: Closes the modal and returns focus to **`Modal.Target`** (or the configured return\n target, such as **`returnFocusRef`**).\n\n### Screen Reader Experience\n\n- **When the modal opens:** Screen readers should announce the first focused control (often the\n close button), the dialog\'s name (**`Modal.Heading`**) and role.\n- **Background content:** Sibling elements of the modal stack receive **`aria-hidden="true"`** while\n the modal is visible, which hides the rest of the page from many assistive technologies. Mouse\n users are blocked by the overlay and inert interaction expectations; always verify behavior in\n your supported browser and screen reader combinations.\n- **Focus trap limits:** Trapping **keyboard** focus does not stop mouse users from interacting\n outside the dialog card, and some screen reader users can move a virtual cursor outside the\n trapped region. Treat the trap as the primary keyboard affordance, not a hard security boundary.'
|
|
455
|
+
mdxProse: "# Canvas Kit Modal\n\nA Modal component is a type of Dialog that renders a translucent overlay that prevents user\ninteraction with the rest of the page. A Modal will render the rest of the page inert until the\nModal is dismissed. A Modal should be used when the user needs to be presented with important\ninformation that must be interacted with before continuing interaction with the rest of the page.\n\nFor tasks that do not require blocking the rest of the page, consider the non-modal\n[**Dialog**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-dialog--docs)\ncomponent instead.\n\n## Installation\n\n```sh\nyarn add @workday/canvas-kit-react\n```\n\n## Usage\n\n### Basic Example\n\nThe basic behavior of a modal is to hide all content from all users that is \"behind\" the modal\ndialog.\n```tsx\nimport {PrimaryButton} from '@workday/canvas-kit-react/button';\nimport {Box} from '@workday/canvas-kit-react/layout';\nimport {Modal} from '@workday/canvas-kit-react/modal';\n\nexport const Basic = () => {\n const handleAcknowledge = () => {\n console.log('License Acknowledged');\n };\n\n const handleCancel = () => {\n console.log('Cancel clicked');\n };\n\n return (\n <Modal>\n <Modal.Target as={PrimaryButton}>Open License</Modal.Target>\n <Modal.Overlay>\n <Modal.Card>\n <Modal.CloseIcon aria-label=\"Close\" />\n <Modal.Heading>MIT License</Modal.Heading>\n <Modal.Body>\n <Box as=\"p\" cs={{marginBlock: '0'}}>\n Permission is hereby granted, free of charge, to any person obtaining a copy of this\n software and associated documentation files (the \"Software\").\n </Box>\n </Modal.Body>\n <Modal.ButtonGroup>\n <Modal.CloseButton onClick={handleCancel}>Cancel</Modal.CloseButton>\n <Modal.CloseButton as={PrimaryButton} onClick={handleAcknowledge}>\n Acknowledge\n </Modal.CloseButton>\n </Modal.ButtonGroup>\n </Modal.Card>\n </Modal.Overlay>\n </Modal>\n );\n};\n```\n\n### Without Close Icon\n\nIf you wish to remove the close icon button, you can simply omit the `Modal.CloseIcon` subcomponent.\nIf you have a modal dialog that requires the user to accept instead of dismiss through an escape key\nor clicking outside the modal, you must create a new `PopupModel` without those behaviors and hand\nthat model to the Modal dialog component.\n```tsx\nimport React from 'react';\n\nimport {DeleteButton} from '@workday/canvas-kit-react/button';\nimport {useUniqueId} from '@workday/canvas-kit-react/common';\nimport {Box} from '@workday/canvas-kit-react/layout';\nimport {Modal} from '@workday/canvas-kit-react/modal';\nimport {\n useAssistiveHideSiblings,\n useDisableBodyScroll,\n useFocusTrap,\n useInitialFocus,\n usePopupModel,\n useReturnFocus,\n} from '@workday/canvas-kit-react/popup';\n\nexport const WithoutCloseIcon = () => {\n const longDescId = useUniqueId();\n const cancelBtnRef = React.useRef(null);\n const model = usePopupModel({\n initialFocusRef: cancelBtnRef,\n });\n\n // disable useCloseOnEscape and useCloseOnOverlayClick\n useInitialFocus(model);\n useReturnFocus(model);\n useFocusTrap(model);\n useAssistiveHideSiblings(model);\n useDisableBodyScroll(model);\n const handleDelete = () => {\n console.log('Deleted item');\n };\n\n return (\n <Modal model={model}>\n <Modal.Target as={DeleteButton}>Delete Item</Modal.Target>\n <Modal.Overlay>\n <Modal.Card aria-describedby={longDescId}>\n <Modal.Heading>Delete Item</Modal.Heading>\n <Modal.Body>\n <Box as=\"p\" id={longDescId} cs={{marginBlock: '0'}}>\n Are you sure you want to delete the item?\n </Box>\n </Modal.Body>\n <Modal.ButtonGroup>\n <Modal.CloseButton ref={cancelBtnRef}>Cancel</Modal.CloseButton>\n <Modal.CloseButton as={DeleteButton} onClick={handleDelete}>\n Delete\n </Modal.CloseButton>\n </Modal.ButtonGroup>\n </Modal.Card>\n </Modal.Overlay>\n </Modal>\n );\n};\n```\n\n### Custom Focus\n\nBy default, the Modal makes sure the first focusable element receives focus when the Modal is\nopened. Most of the time, this is the `Modal.CloseIcon` button. If that element isn't present, the\nModal will use the Modal Heading to make sure screen reader users have focus near the start of the\nModal's content. This allows screen reader users to discover the Modal's content more naturally\nwithout having to navigate back up again. Sometimes, it is a better user experience to focus on a\ndifferent element. The following example shows how `initialFocusRef` can be used to change which\nelement receives focus when the modal opens.\n```tsx\nimport React from 'react';\n\nimport {PrimaryButton} from '@workday/canvas-kit-react/button';\nimport {useUniqueId} from '@workday/canvas-kit-react/common';\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {Box} from '@workday/canvas-kit-react/layout';\nimport {Modal, useModalModel} from '@workday/canvas-kit-react/modal';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\nimport {system} from '@workday/canvas-tokens-web';\n\nexport const CustomFocus = () => {\n const longDescID = useUniqueId();\n const ref = React.useRef<HTMLInputElement>(null);\n const [value, setValue] = React.useState('');\n const model = useModalModel({\n initialFocusRef: ref,\n });\n\n const handleAcknowledge = () => {\n console.log('Acknowledged license');\n };\n\n return (\n <Modal model={model}>\n <Modal.Target as={PrimaryButton}>Acknowledge License</Modal.Target>\n <Modal.Overlay>\n <Modal.Card aria-describedby={longDescID}>\n <Modal.CloseIcon aria-label=\"Close\" />\n <Modal.Heading>Acknowledge License</Modal.Heading>\n <Modal.Body>\n <Box as=\"p\" id={longDescID} cs={{marginBlockStart: 0, marginBlockEnd: system.gap.md}}>\n Enter your initials to acknowledge the license.\n </Box>\n <FormField>\n <FormField.Label>Initials</FormField.Label>\n <FormField.Input\n as={TextInput}\n ref={ref}\n value={value}\n grow\n onChange={e => setValue(e.currentTarget.value)}\n />\n </FormField>\n </Modal.Body>\n <Modal.ButtonGroup>\n <Modal.CloseButton>Cancel</Modal.CloseButton>\n <Modal.CloseButton as={PrimaryButton} onClick={handleAcknowledge}>\n Acknowledge\n </Modal.CloseButton>\n </Modal.ButtonGroup>\n </Modal.Card>\n </Modal.Overlay>\n </Modal>\n );\n};\n```\n\n> **Accessibility Note**: When initial focus lands on a control **below** the heading (for example,\n> a text field instead of the close button), give supplementary copy a unique `id` and pass\n> **`aria-describedby`** on **`Modal.Card`** so screen readers can announce both the dialog name and\n> that text. For more examples of custom focus techniques, see\n> [Popup > Initial Focus](https://workday.github.io/canvas-kit/?path=/docs/components-popups-popup--docs#initial-focus).\n\n### Return Focus\n\nBy default, the Modal will return focus to the `Modal.Target` element. When you open the modal with\n`model.events.show()` (without `Modal.Target`), set **`returnFocusRef`** on the model to the element\nthat should receive focus when the modal closes\u2014for example the button that opened it. That covers\ncancel, Escape, and the close icon: focus returns to the control the user activated.\n\nIf confirming an action **removes** that control from the document (such as deleting the row that\nheld the delete button), `returnFocusRef` alone cannot land on a **new** target. The example below\nuses **`useLayoutEffect`** after the list updates to move focus to another row\u2019s delete control, or\nto empty-state text when no files remain.\n```tsx\nimport React from 'react';\n\nimport {DeleteButton} from '@workday/canvas-kit-react/button';\nimport {useUniqueId} from '@workday/canvas-kit-react/common';\nimport {Box, Flex} from '@workday/canvas-kit-react/layout';\nimport {Modal, useModalModel} from '@workday/canvas-kit-react/modal';\nimport {Heading, Text} from '@workday/canvas-kit-react/text';\nimport {Tooltip} from '@workday/canvas-kit-react/tooltip';\nimport {createStyles} from '@workday/canvas-kit-styling';\nimport {trashIcon} from '@workday/canvas-system-icons-web';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst INITIAL_FILES = ['Resume.docx', 'Cover_Letter.docx', 'References.docx'];\n\nconst headingStyles = createStyles({\n marginBlock: '0',\n});\n\nconst emptyStateStyles = createStyles({\n maxWidth: '28rem',\n outline: 'none',\n});\n\nconst listStyles = createStyles({\n flexDirection: 'column',\n gap: system.gap.md,\n marginBlock: '0',\n padding: '0',\n listStyle: 'none',\n maxWidth: '28rem',\n});\n\nconst rowStyles = createStyles({\n alignItems: 'center',\n justifyContent: 'space-between',\n gap: system.gap.md,\n width: '100%',\n});\n\nfunction fileNameId(name: string) {\n return `return-focus-file-${name.replace(/[^a-zA-Z0-9]/g, '_')}`;\n}\n\n/** Index of a delete button to focus after removing `deletedIndex`, or empty list. */\nfunction nextListFocusAfterDelete(deletedIndex: number, lengthBeforeDelete: number) {\n if (lengthBeforeDelete <= 1) {\n return 'empty' as const;\n }\n return deletedIndex < lengthBeforeDelete - 1 ? deletedIndex : deletedIndex - 1;\n}\n\nexport const ReturnFocus = () => {\n const [items, setItems] = React.useState<string[]>(() => [...INITIAL_FILES]);\n const [confirmingFileName, setConfirmingFileName] = React.useState<string | null>(null);\n const bodyTextId = useUniqueId();\n\n const returnFocusRef = React.useRef<HTMLButtonElement | null>(null);\n const cancelButtonRef = React.useRef<HTMLButtonElement>(null);\n const deleteButtonRefs = React.useRef<(HTMLButtonElement | null)[]>([]);\n const emptyStateRef = React.useRef<HTMLDivElement>(null);\n const pendingDeleteIndexRef = React.useRef<number | null>(null);\n const postDeleteFocusRef = React.useRef<number | 'empty' | null>(null);\n\n const model = useModalModel({\n returnFocusRef,\n initialFocusRef: cancelButtonRef,\n });\n\n React.useEffect(() => {\n if (model.state.visibility === 'hidden') {\n setConfirmingFileName(null);\n pendingDeleteIndexRef.current = null;\n }\n }, [model.state.visibility]);\n\n React.useLayoutEffect(() => {\n if (postDeleteFocusRef.current === null) {\n return;\n }\n if (postDeleteFocusRef.current === 'empty') {\n emptyStateRef.current?.focus();\n } else {\n deleteButtonRefs.current[postDeleteFocusRef.current]?.focus();\n }\n postDeleteFocusRef.current = null;\n }, [items]);\n\n const openDeleteModal = (index: number) => {\n pendingDeleteIndexRef.current = index;\n setConfirmingFileName(items[index]);\n returnFocusRef.current = deleteButtonRefs.current[index];\n model.events.show();\n };\n\n const handleConfirmDelete = () => {\n const idx = pendingDeleteIndexRef.current;\n if (idx === null) {\n return;\n }\n postDeleteFocusRef.current = nextListFocusAfterDelete(idx, items.length);\n pendingDeleteIndexRef.current = null;\n setItems(prev => prev.filter((_, i) => i !== idx));\n };\n\n return (\n <Modal model={model}>\n <Heading as=\"h4\" size=\"small\" cs={headingStyles}>\n Uploaded Files\n </Heading>\n <Box>\n {items.length > 0 ? (\n <Flex as=\"ul\" cs={listStyles}>\n {items.map((name, index) => (\n <Flex as=\"li\" key={name} cs={rowStyles}>\n <Text as=\"span\" id={fileNameId(name)}>\n {name}\n </Text>\n <Tooltip title=\"Delete\">\n <DeleteButton\n aria-describedby={fileNameId(name)}\n icon={trashIcon}\n ref={el => {\n deleteButtonRefs.current[index] = el;\n }}\n onClick={() => openDeleteModal(index)}\n />\n </Tooltip>\n </Flex>\n ))}\n </Flex>\n ) : (\n <Box ref={emptyStateRef} tabIndex={-1} cs={emptyStateStyles}>\n <Text>No files remaining.</Text>\n </Box>\n )}\n </Box>\n <Modal.Overlay>\n <Modal.Card aria-describedby={bodyTextId}>\n <Modal.Heading>Delete file?</Modal.Heading>\n <Modal.Body>\n <Text id={bodyTextId}>\n {confirmingFileName\n ? `Are you sure you want to delete ${confirmingFileName}?`\n : 'Are you sure you want to delete this file?'}\n </Text>\n </Modal.Body>\n <Modal.ButtonGroup>\n <Modal.CloseButton ref={cancelButtonRef}>Cancel</Modal.CloseButton>\n <Modal.CloseButton as={DeleteButton} onClick={handleConfirmDelete}>\n Delete\n </Modal.CloseButton>\n </Modal.ButtonGroup>\n </Modal.Card>\n </Modal.Overlay>\n </Modal>\n );\n};\n```\n\n> **Accessibility Note**: After an item is deleted, focus is returned to the next item in the list\n> or to the empty state text when no items remain.\n\n### Custom Target\n\nIt is common to have a custom target for your modal. Use the `as` prop to use your custom component.\nThe `Modal.Target` element will add `onClick` and `ref` to the provided component. Your provided\ntarget component must forward the `onClick` to an element for the Modal to open. The `as` will cause\n`Modal.Target` to inherit the interface of your custom target component. This means any props your\ntarget requires, `Modal.Target` now also requires. The example below has a `MyTarget` component that\nrequires a `label` prop.\n\n> **Note**: If your application needs to programmatically open a Modal without the user interacting\n> with the target button first, you'll also need to use `React.forwardRef` in your target component.\n> Without this, the Modal will open at the top-left of the window instead of around the target.\n```tsx\nimport React from 'react';\n\nimport {Modal} from '@workday/canvas-kit-react/modal';\n\ninterface MyTargetProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n label: string;\n}\n\nconst MyTarget = ({label, ...props}: MyTargetProps) => {\n return <button {...props}>{label}</button>;\n};\n\nexport const CustomTarget = () => {\n return (\n <Modal>\n <Modal.Target as={MyTarget} label=\"Open\" />\n <Modal.Overlay>\n <Modal.Card>\n <Modal.CloseIcon aria-label=\"Close\" />\n <Modal.Heading>Modal Heading</Modal.Heading>\n <Modal.Body>\n Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec semper facilisis dolor\n quis facilisis. Aenean tempor eget quam et semper. Nam malesuada rhoncus euismod.\n Quisque vel urna feugiat, dictum risus sed, pulvinar nulla. Sed gravida, elit non\n iaculis blandit, ligula tortor posuere mauris, vitae cursus turpis nunc non arcu.\n </Modal.Body>\n </Modal.Card>\n </Modal.Overlay>\n </Modal>\n );\n};\n```\n\n> **Accessibility Note**: Custom targets must be keyboard focusable, otherwise users will not be\n> able to access the modal. Bear in mind that click handlers only work with the keyboard when\n> applied to HTML `<button>` elements and it is **strongly recommended** to base your custom target\n> on a `<button>` element. Otherwise, you will be required to build in your own custom keyboard\n> event handlers for invoking the modal.\n\n### Body Content Overflow\n\nThe Modal automatically handles overflowing content inside the `Modal.Body` element. If contents are\nlarger than the browser's height will allow, the content will overflow with a scrollbar. You may\nneed to restrict the height of your browser to observe the overflow.\n```tsx\nimport {PrimaryButton} from '@workday/canvas-kit-react/button';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {Modal} from '@workday/canvas-kit-react/modal';\nimport {system} from '@workday/canvas-tokens-web';\n\nexport const BodyOverflow = () => {\n const handleAcknowledge = () => {\n console.log('License Acknowledged');\n };\n\n const handleCancel = () => {\n console.log('Cancel clicked');\n };\n\n return (\n <Modal>\n <Modal.Target as={PrimaryButton}>Open License</Modal.Target>\n <Modal.Overlay>\n <Modal.Card>\n <Modal.CloseIcon aria-label=\"Close\" />\n <Modal.Heading>MIT License</Modal.Heading>\n <Modal.Body tabIndex={0}>\n <p style={{marginBlockStart: 0}}>\n Permission is hereby granted, free of charge, to any person obtaining a copy of this\n software and associated documentation files (the \"Software\"), to deal in the Software\n without restriction, including without limitation the rights to use, copy, modify,\n merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n permit persons to whom the Software is furnished to do so, subject to the following\n conditions:\n </p>\n <p>\n The above copyright notice and this permission notice shall be included in all copies\n or substantial portions of the Software.\n </p>\n <p>\n THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE\n OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n </p>\n <p>\n Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor\n incididunt ut labore et dolore magna aliqua. Amet massa vitae tortor condimentum\n lacinia quis. Fermentum odio eu feugiat pretium nibh ipsum consequat nisl. Sed lectus\n vestibulum mattis ullamcorper velit sed. Rutrum tellus pellentesque eu tincidunt\n tortor aliquam nulla. Vitae turpis massa sed elementum tempus egestas sed sed risus.\n Cursus vitae congue mauris rhoncus aenean vel elit scelerisque mauris. Id neque\n aliquam vestibulum morbi blandit cursus risus at. Vel eros donec ac odio tempor orci.\n Ac felis donec et odio pellentesque diam volutpat. Laoreet non curabitur gravida arcu\n ac tortor dignissim. Rhoncus urna neque viverra justo nec ultrices dui. Bibendum arcu\n vitae elementum curabitur vitae nunc sed velit dignissim. Sed risus pretium quam\n vulputate dignissim suspendisse in est. Curabitur gravida arcu ac tortor. Nam libero\n justo laoreet sit amet cursus sit amet. Arcu dui vivamus arcu felis bibendum ut\n tristique et egestas. Eros donec ac odio tempor orci dapibus ultrices. At erat\n pellentesque adipiscing commodo elit at. Dignissim cras tincidunt lobortis feugiat\n vivamus at augue.\n </p>\n <p>\n Amet commodo nulla facilisi nullam vehicula ipsum. Blandit libero volutpat sed cras.\n Quam lacus suspendisse faucibus interdum posuere. Aenean euismod elementum nisi quis\n eleifend. Orci nulla pellentesque dignissim enim sit amet venenatis. Diam vel quam\n elementum pulvinar etiam non quam lacus. Sit amet dictum sit amet justo donec enim\n diam vulputate. Tincidunt ornare massa eget egestas purus. Pulvinar neque laoreet\n suspendisse interdum consectetur libero id faucibus. Morbi tincidunt augue interdum\n velit. Nullam non nisi est sit amet.\n </p>\n <p style={{marginBlockEnd: 0}}>\n Aliquet enim tortor at auctor urna nunc id cursus metus. Leo urna molestie at\n elementum eu facilisis. Consectetur purus ut faucibus pulvinar elementum integer.\n Volutpat est velit egestas dui id ornare arcu odio. At consectetur lorem donec massa\n sapien. Condimentum vitae sapien pellentesque habitant. Pellentesque habitant morbi\n tristique senectus. Et molestie ac feugiat sed lectus vestibulum. Arcu risus quis\n varius quam quisque. Turpis massa tincidunt dui ut ornare lectus sit amet. Magna eget\n est lorem ipsum dolor sit. Suspendisse faucibus interdum posuere lorem ipsum. Nisi\n vitae suscipit tellus mauris a diam maecenas sed. Ipsum dolor sit amet consectetur\n adipiscing. Ultricies integer quis auctor elit sed. Scelerisque varius morbi enim nunc\n faucibus a. Tortor consequat id porta nibh venenatis cras. Consectetur adipiscing elit\n ut aliquam purus sit.\n </p>\n </Modal.Body>\n <Modal.ButtonGroup>\n <Modal.CloseButton onClick={handleCancel}>Cancel</Modal.CloseButton>\n <Modal.CloseButton as={PrimaryButton} onClick={handleAcknowledge}>\n Acknowledge\n </Modal.CloseButton>\n </Modal.ButtonGroup>\n </Modal.Card>\n </Modal.Overlay>\n </Modal>\n );\n};\n```\n\n> **Accessibility Note**: When body content overflows, ensure users can scroll that region **using\n> only the keyboard**. Mouse users can drag scrollbars, but keyboard users need another path. In\n> this example, **`tabIndex={0}`** is set on **`Modal.Body`** so the scrollable area can receive\n> focus; once focused, **arrow keys** move the viewport within the overflowing content.\n\n### Full overlay scrolling\n\nIf content is large, scrolling the entire overlay container is an option. Use the\n`Modal.OverflowOverlay` component instead of the `Modal.Overlay` component. The `Modal.Card`'s\n`maxHeight` and `height` will need to be reset to `inherit` to prevent any internal overflow.\n\nThis has the effect of scrolling the heading, close button, and any action buttons. If this type of\nscrolling behavior is not desired, try the [Body Content Overflow](#body-content-overflow) method.\n```tsx\nimport {PrimaryButton} from '@workday/canvas-kit-react/button';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {Modal} from '@workday/canvas-kit-react/modal';\nimport {system} from '@workday/canvas-tokens-web';\n\nexport const FullOverflow = () => {\n const handleAcknowledge = () => {\n console.log('License Acknowledged');\n };\n\n const handleCancel = () => {\n console.log('Cancel clicked');\n };\n\n return (\n <Modal>\n <Modal.Target as={PrimaryButton}>Open License</Modal.Target>\n <Modal.OverflowOverlay>\n <Modal.Card cs={{maxHeight: 'inherit', height: 'inherit'}}>\n <Modal.CloseIcon aria-label=\"Close\" />\n <Modal.Heading>MIT License</Modal.Heading>\n <Modal.Body tabIndex={0}>\n <p style={{marginBlockStart: 0}}>\n Permission is hereby granted, free of charge, to any person obtaining a copy of this\n software and associated documentation files (the \"Software\"), to deal in the Software\n without restriction, including without limitation the rights to use, copy, modify,\n merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n permit persons to whom the Software is furnished to do so, subject to the following\n conditions:\n </p>\n <p>\n The above copyright notice and this permission notice shall be included in all copies\n or substantial portions of the Software.\n </p>\n <p>\n THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE\n OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n </p>\n <p>\n Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor\n incididunt ut labore et dolore magna aliqua. Amet massa vitae tortor condimentum\n lacinia quis. Fermentum odio eu feugiat pretium nibh ipsum consequat nisl. Sed lectus\n vestibulum mattis ullamcorper velit sed. Rutrum tellus pellentesque eu tincidunt\n tortor aliquam nulla. Vitae turpis massa sed elementum tempus egestas sed sed risus.\n Cursus vitae congue mauris rhoncus aenean vel elit scelerisque mauris. Id neque\n aliquam vestibulum morbi blandit cursus risus at. Vel eros donec ac odio tempor orci.\n Ac felis donec et odio pellentesque diam volutpat. Laoreet non curabitur gravida arcu\n ac tortor dignissim. Rhoncus urna neque viverra justo nec ultrices dui. Bibendum arcu\n vitae elementum curabitur vitae nunc sed velit dignissim. Sed risus pretium quam\n vulputate dignissim suspendisse in est. Curabitur gravida arcu ac tortor. Nam libero\n justo laoreet sit amet cursus sit amet. Arcu dui vivamus arcu felis bibendum ut\n tristique et egestas. Eros donec ac odio tempor orci dapibus ultrices. At erat\n pellentesque adipiscing commodo elit at. Dignissim cras tincidunt lobortis feugiat\n vivamus at augue.\n </p>\n <p>\n Amet commodo nulla facilisi nullam vehicula ipsum. Blandit libero volutpat sed cras.\n Quam lacus suspendisse faucibus interdum posuere. Aenean euismod elementum nisi quis\n eleifend. Orci nulla pellentesque dignissim enim sit amet venenatis. Diam vel quam\n elementum pulvinar etiam non quam lacus. Sit amet dictum sit amet justo donec enim\n diam vulputate. Tincidunt ornare massa eget egestas purus. Pulvinar neque laoreet\n suspendisse interdum consectetur libero id faucibus. Morbi tincidunt augue interdum\n velit. Nullam non nisi est sit amet.\n </p>\n <p style={{marginBlockEnd: 0}}>\n Aliquet enim tortor at auctor urna nunc id cursus metus. Leo urna molestie at\n elementum eu facilisis. Consectetur purus ut faucibus pulvinar elementum integer.\n Volutpat est velit egestas dui id ornare arcu odio. At consectetur lorem donec massa\n sapien. Condimentum vitae sapien pellentesque habitant. Pellentesque habitant morbi\n tristique senectus. Et molestie ac feugiat sed lectus vestibulum. Arcu risus quis\n varius quam quisque. Turpis massa tincidunt dui ut ornare lectus sit amet. Magna eget\n est lorem ipsum dolor sit. Suspendisse faucibus interdum posuere lorem ipsum. Nisi\n vitae suscipit tellus mauris a diam maecenas sed. Ipsum dolor sit amet consectetur\n adipiscing. Ultricies integer quis auctor elit sed. Scelerisque varius morbi enim nunc\n faucibus a. Tortor consequat id porta nibh venenatis cras. Consectetur adipiscing elit\n ut aliquam purus sit.\n </p>\n </Modal.Body>\n <Modal.ButtonGroup>\n <Modal.CloseButton onClick={handleCancel}>Cancel</Modal.CloseButton>\n <Modal.CloseButton as={PrimaryButton} onClick={handleAcknowledge}>\n Acknowledge\n </Modal.CloseButton>\n </Modal.ButtonGroup>\n </Modal.Card>\n </Modal.OverflowOverlay>\n </Modal>\n );\n};\n```\n\n### Form Modal\n\nThe `Modal.Card` can be turned into a `form` element to make a form modal. The `model` should be\nhoisted to allow for form validation and allow you to control when the modal closes.\n```tsx\nimport React from 'react';\n\nimport {PrimaryButton} from '@workday/canvas-kit-react/button';\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {Modal, useModalModel} from '@workday/canvas-kit-react/modal';\nimport {Select} from '@workday/canvas-kit-react/select';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\nimport {plusIcon} from '@workday/canvas-system-icons-web';\n\nconst FAVORITE_COLOR_OPTIONS = ['Blue', 'Yellow'];\n\nexport const FormModal = () => {\n const model = useModalModel();\n\n const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault(); // prevent a page reload\n\n // do form validation here\n\n console.log('form data', {\n first: (event.currentTarget.elements.namedItem('first') as HTMLInputElement).value,\n last: (event.currentTarget.elements.namedItem('last') as HTMLInputElement).value,\n favoriteColor: (event.currentTarget.elements.namedItem('favoriteColor') as HTMLInputElement)\n .value,\n });\n\n // if it looks good, submit to the server and close the modal\n model.events.hide();\n };\n\n return (\n <Modal model={model}>\n <Modal.Target icon={plusIcon}>Create New User</Modal.Target>\n <Modal.Overlay>\n <Modal.Card as=\"form\" onSubmit={onSubmit}>\n <Modal.CloseIcon aria-label=\"Close\" />\n <Modal.Heading>New User</Modal.Heading>\n <Modal.Body>\n <FormField grow>\n <FormField.Label>First Name</FormField.Label>\n <FormField.Input as={TextInput} name=\"first\" />\n </FormField>\n <FormField grow>\n <FormField.Label>Last Name</FormField.Label>\n <FormField.Input as={TextInput} name=\"last\" />\n </FormField>\n <FormField grow>\n <FormField.Label>Favorite Color</FormField.Label>\n <FormField.Field>\n <Select items={FAVORITE_COLOR_OPTIONS}>\n <FormField.Input as={Select.Input} name=\"favoriteColor\" />\n <Select.Popper>\n <Select.Card>\n <Select.List>{item => <Select.Item>{item}</Select.Item>}</Select.List>\n </Select.Card>\n </Select.Popper>\n </Select>\n </FormField.Field>\n </FormField>\n </Modal.Body>\n <Modal.ButtonGroup>\n <Modal.CloseButton>Cancel</Modal.CloseButton>\n <PrimaryButton type=\"submit\">Submit</PrimaryButton>\n </Modal.ButtonGroup>\n </Modal.Card>\n </Modal.Overlay>\n </Modal>\n );\n};\n```\n\n## Accessibility\n\nEnsure users of assistive technology can discover, name, and operate a **modal** dialog: the rest of\nthe page is blocked by an overlay, background content is hidden from assistive technology via\nsibling **`aria-hidden`**, keyboard focus is trapped inside the modal, the dialog has an accessible\nname that matches its visible heading, and keyboard users can open and dismiss it predictably.\n\nUse **Modal** when the user must complete or acknowledge a task before continuing with the page. For\nnon-blocking tasks, use\n[**Dialog**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-dialog--docs)\ninstead. Prefer **Modal** for the standard blocking dialog; use\n[**Popup**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-popup--docs) with\ncomposed hooks when you need a custom popup stack or to omit behaviors (for example Escape or\noverlay dismiss). For portals, reading order, and related tradeoffs, see\n[Guides > Accessibility > Inline Popups](https://workday.github.io/canvas-kit/?path=/docs/guides-accessibility-inline-popups--docs).\nSee also the\n[Modal Dialog Pattern | APG | WAI | W3C](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/).\n\n### Minimum Accessible Structure\n\nThe following matches the [Basic Example](#basic-example) layout: **`Modal.CloseIcon`** before\n**`Modal.Heading`** so open focus lands on the dismiss control first; primary actions use\n**`Modal.CloseButton`** (which closes the modal on activate).\n\n```tsx\n\n<Modal>\n <Modal.Target as={PrimaryButton}>Open</Modal.Target>\n <Modal.Overlay>\n <Modal.Card>\n <Modal.CloseIcon aria-label=\"Close\" />\n <Modal.Heading>Title</Modal.Heading>\n <Modal.Body>Content</Modal.Body>\n <Modal.ButtonGroup>\n <Modal.CloseButton>Cancel</Modal.CloseButton>\n <Modal.CloseButton as={PrimaryButton}>Acknowledge</Modal.CloseButton>\n </Modal.ButtonGroup>\n </Modal.Card>\n </Modal.Overlay>\n</Modal>;\n```\n\nInclude a dismiss control: **`Modal.CloseButton`** with visible text (for example \"Cancel\" or\n\"Close\"), and/or **`Modal.CloseIcon`** when the design uses an icon-only dismiss (requires\n**`aria-label`** or **`Tooltip`**). Use **`Modal.CloseButton`** for actions that should also close\nthe modal (for example \"Acknowledge\"). Compose with **`Modal.Overlay` \u2192 `Modal.Card`** (or\n**`Modal.OverflowOverlay`** when the entire overlay should scroll).\n\n### Built-in Behaviors\n\nCanvas Kit applies these automatically via `useModalModel` and Modal subcomponents. **Do not\nduplicate them** in consuming code.\n\n**Popup behaviors** (_composed on the default model_):\n\n- `useInitialFocus` \u2014 moves focus into the modal when it opens (default: first focusable element in\n DOM order; optional override via `initialFocusRef` on the model)\n- `useReturnFocus` \u2014 returns focus to `Modal.Target` (or configured return target) when it closes\n- `useCloseOnOverlayClick` \u2014 pointer interaction on the overlay (outside the dialog) closes the\n modal\n- `useCloseOnEscape` \u2014 <kbd>Escape</kbd> closes the modal\n- `useFocusTrap` \u2014 <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> cycle focus **inside** the modal\n (keyboard focus does not leave the dialog)\n- `useAssistiveHideSiblings` \u2014 applies **`aria-hidden`** to siblings of the modal stack while open\n- `useDisableBodyScroll` \u2014 prevents background page scroll while the modal is open\n\n**ARIA and DOM** (_applied by hooks/subcomponents_):\n\n- `Modal.Card`: `role=\"dialog\"`, `aria-labelledby` referencing the heading `id`, and\n **`aria-modal=\"false\"`**\n- `Modal.Heading`: `id` wired to `Modal.Card`'s `aria-labelledby`; when there is no icon-only close\n button before the heading, `useModalHeading` may temporarily set **`tabindex=\"0\"`** on the heading\n so initial focus still lands near the start of the dialog\n- `Modal.CloseIcon` / `Modal.CloseButton`: `onClick` that calls `model.events.hide()`\n- `Modal.Target`: `ref` and `onClick` to open and to receive return focus\n\n**Keyboard** (_trigger is `Modal.Target`, default `SecondaryButton`_):\n\n- <kbd>Enter</kbd> / <kbd>Space</kbd> on the trigger opens the modal (standard button behavior)\n- On open and close, focus is managed by **`useInitialFocus`** and **`useReturnFocus`** (application\n overrides: see **Focus management** in Accessibility Requirements)\n- <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> move focus through interactive elements\n **inside** the modal; focus stays trapped within the dialog\n- <kbd>Escape</kbd> closes the modal and returns focus per `useReturnFocus` (unless Escape dismiss\n is omitted via a custom model\u2014see **Accept-only / no Escape dismiss**)\n\n**Screen reader expectations** (_when built-in behaviors are used as intended_):\n\n- On open, assistive technology should announce the first focused control (often a dismiss control),\n the dialog name (`Modal.Heading`), and `dialog` role\n- Sibling elements of the modal stack receive **`aria-hidden=\"true\"`** while the modal is visible,\n which hides the rest of the page from many assistive technologies\n- Trapping **keyboard** focus does not stop all screen reader virtual-cursor movement outside the\n dialog; treat the trap as the primary keyboard affordance, not a hard boundary\u2014verify behavior in\n your supported browser and screen reader combinations\n\n### Accessibility Requirements\n\nRequired in application code for an accessible Modal. Hoist **`useModalModel`** when you need to\nconfigure focus targets, open without **`Modal.Target`**, or control when the modal closes (for\nexample form validation). Rows marked _(conditional)_ apply only when the situation\nmatches\u2014otherwise omit.\n\n**If no design spec is provided:** use default focus behavior; include a dismiss control and\n**`Modal.Heading`**; omit **`initialFocusRef`**, **`returnFocusRef`**, and **`aria-describedby`**.\nDo not remove Escape or overlay dismiss unless the design requires accept-only confirmation.\n\n**Focus management \u2014 defaults and developer prompts:** Canvas Kit handles open and close focus\nautomatically. **State the default to the developer first.** Only set **`initialFocusRef`** or\n**`returnFocusRef`** after the developer (or an explicit design spec) chooses a non-default target.\n**Do not generate focus refs by default.**\n\n| When | Default behavior | Ask the developer before overriding |\n| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Modal **opens** | **`useInitialFocus`** moves focus to the **first focusable element** in DOM order inside the modal (often **`Modal.CloseIcon`** or **`Modal.CloseButton`**). Omit **`initialFocusRef`**. | _Which element should receive focus when the modal opens?_ (Only when the default first focusable element is wrong for the design.) Attach **`initialFocusRef`** to that element on **`useModalModel`**. |\n| Modal **closes** | **`useReturnFocus`** moves focus to **`Modal.Target`**. Omit **`returnFocusRef`**. | _Which element should receive focus when the modal closes?_ (Only when return focus should land somewhere other than **`Modal.Target`**.) |\n\nIf close **removes the trigger from the DOM**, **`returnFocusRef`** alone is not enough\u2014move focus\nafter the UI updates (for example with **`useLayoutEffect`**). See [Return Focus](#return-focus).\n\n**Custom targets** _(conditional)_: Apply when using a custom **`as`** component on\n**`Modal.Target`**. **`Modal.Target`** adds **`onClick`** and **`ref`**. Custom targets must forward\nboth to a **keyboard-focusable** element (prefer a native **`<button>`** or\n**`as={SecondaryButton}`** / another Canvas Kit button). Wrap the component in\n**`React.forwardRef`** when it does not forward refs by default (required if the modal can open\nprogrammatically before the user clicks the target).\n\n| Requirement | How to satisfy |\n| ------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Accessible dialog name | Use **`Modal.Heading`** so `aria-labelledby` on `Modal.Card` references a visible title. Do not omit the heading: **`Modal.Card` always sets `aria-labelledby`**, and an `aria-label` fallback is unreliable when that ID does not exist. |\n| Dismiss control | Provide a way to close the modal: **`Modal.CloseButton`** with visible text (no extra **`aria-label`** needed), and/or **`Modal.CloseIcon`** for icon-only dismiss (requires **`Tooltip`** or translated **`aria-label`**). |\n| Keyboard-operable trigger | See **Custom targets** above. |\n| Supplementary copy when overriding open focus _(conditional)_ | When **`initialFocusRef`** places open focus **below** **`Modal.Heading`**, assign a unique `id` to supplementary text and pass **`aria-describedby`** on **`Modal.Card`**. See **Open focus below the heading** below, [Custom Focus](#custom-focus), and [Popup > Initial Focus](https://workday.github.io/canvas-kit/?path=/docs/components-popups-popup--docs#initial-focus) (button-focus variant). |\n| Keyboard-scrollable overflowing body _(conditional)_ | When **`Modal.Body`** content overflows, set **`tabIndex={0}`** on **`Modal.Body`** so keyboard users can focus the scroll region and use arrow keys. See [Body Content Overflow](#body-content-overflow). |\n| Accept-only / no Escape dismiss _(conditional)_ | Only when the design requires the user to accept (not dismiss via Escape or overlay click): compose a custom **`usePopupModel`** with the modal behaviors you still need, **omitting** **`useCloseOnEscape`** and **`useCloseOnOverlayClick`**. See [Without Close Icon](#without-close-icon). |\n\n**Open focus below the heading** _(conditional; see supplementary copy row above)_:\n\nWhen open focus moves past the heading (for example into a form field), wire **`aria-describedby`**\nso assistive technology still announces the supplementary copy. For focusing a primary action\ninstead of an input, see\n[Popup > Initial Focus](https://workday.github.io/canvas-kit/?path=/docs/components-popups-popup--docs#initial-focus).\n\n```tsx\n\nconst Example = () => {\n const descriptionId = useUniqueId();\n const inputRef = React.useRef<HTMLInputElement>(null);\n const model = useModalModel({initialFocusRef: inputRef});\n\n return (\n <Modal model={model}>\n <Modal.Target>Open</Modal.Target>\n <Modal.Overlay>\n <Modal.Card aria-describedby={descriptionId}>\n <Modal.CloseIcon aria-label=\"Close\" />\n <Modal.Heading>Title</Modal.Heading>\n <Modal.Body>\n <p id={descriptionId}>Enter your email to continue.</p>\n <FormField>\n <FormField.Label>Email</FormField.Label>\n <FormField.Input as={TextInput} ref={inputRef} />\n </FormField>\n </Modal.Body>\n <Modal.CloseButton>Cancel</Modal.CloseButton>\n </Modal.Card>\n </Modal.Overlay>\n </Modal>\n );\n};\n```\n\n**Summary for code generation:**\n\n- **REQUIRED:** accessible name, dismiss control, keyboard-operable trigger,\n **`Modal.Overlay` \u2192 `Modal.Card`** composition\n- **CONDITIONAL:** **`initialFocusRef`**, **`returnFocusRef`**, **`aria-describedby`**,\n **`forwardRef`** on custom **`Modal.Target`**, **`tabIndex={0}`** on overflowing **`Modal.Body`**,\n custom model omitting Escape/overlay dismiss, **`Modal.OverflowOverlay`**\n\n### Anti-Patterns\n\nDo **not** generate code that does the following (see **Accessibility Requirements** above for what\nto supply instead):\n\n- Manually set `role=\"dialog\"`, `aria-labelledby`, or dialog `id` on **`Modal.Card`** or\n **`Modal.Heading`** \u2014 Canvas Kit hooks wire these\n- Override **`aria-modal`** to **`\"true\"`** on **`Modal.Card`** \u2014 when **`aria-modal`** is `true`,\n some assistive technologies hide everything outside the dialog, including portaled UI owned by the\n modal (such as a Select menu rendered as a sibling). Canvas Kit sets **`aria-modal=\"false\"`** for\n a better VoiceOver experience while **`useAssistiveHideSiblings`** applies **`aria-hidden`** to\n background siblings. Do not change this unless accessibility has approved it. Unlike\n [**Dialog**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-dialog--docs),\n Modal also does **not** use the sibling **`aria-owns`** reading-order pattern\n- Omit **`Modal.Overlay`** (or **`Modal.OverflowOverlay`**), render **`Modal.Card`** outside it, or\n add a custom portal/restructure instead of **`Modal` \u2192 `Modal.Overlay` \u2192 `Modal.Card`**\n- Use **`open`** / **`onClose`** props on **`Modal`** \u2014 Modal has no controlled visibility props;\n use **`useModalModel`** and **`model.events.show()`** / **`model.events.hide()`**\n- Use **Dialog** when the task must block the rest of the page, or add **`useFocusRedirect`** /\n **`aria-owns`** expecting Modal-like blocking behavior \u2014 Modal uses a focus trap and sibling\n hiding instead\n- Set **`initialFocusRef`** or **`returnFocusRef`** by default \u2014 state the default focus behavior\n first and ask the developer before overriding (see **Focus management** in Accessibility\n Requirements)\n- Add **`aria-expanded`** or **`aria-haspopup`** on **`Modal.Target`** \u2014 those attributes apply to\n **non-modal** dialogs (see\n [**Dialog**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-dialog--docs) /\n [**Popup**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-popup--docs)); Modal\n moves focus into the dialog on open and must not use this pattern\n- Use a custom **`Modal.Target`** **`as`** component that does not forward **`ref`** to a focusable\n element \u2014 use **`React.forwardRef`** or a Canvas Kit button component instead\n- Rely on **`returnFocusRef`** alone when close **removes the trigger from the DOM** (see\n [Return Focus](#return-focus))\n- Omit Escape and overlay dismiss without an explicit accept-only design requirement, or remove\n **`Modal.CloseIcon`** without providing another dismiss path (see **Accept-only / no Escape\n dismiss**)\n- Leave overflowing **`Modal.Body`** content without a keyboard path to scroll (see\n **Keyboard-scrollable overflowing body**)\n- Nest multiple **`Modal`** instances without deliberate initial focus and return-focus planning\n- Assume the focus trap alone fully hides outside content from every assistive technology \u2014 verify\n supported browser and screen reader combinations\n\n## Component API\n\n## Specifications\n\n",
|
|
456
|
+
accessibilityProse: '## Accessibility\n\nEnsure users of assistive technology can discover, name, and operate a **modal** dialog: the rest of\nthe page is blocked by an overlay, background content is hidden from assistive technology via\nsibling **`aria-hidden`**, keyboard focus is trapped inside the modal, the dialog has an accessible\nname that matches its visible heading, and keyboard users can open and dismiss it predictably.\n\nUse **Modal** when the user must complete or acknowledge a task before continuing with the page. For\nnon-blocking tasks, use\n[**Dialog**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-dialog--docs)\ninstead. Prefer **Modal** for the standard blocking dialog; use\n[**Popup**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-popup--docs) with\ncomposed hooks when you need a custom popup stack or to omit behaviors (for example Escape or\noverlay dismiss). For portals, reading order, and related tradeoffs, see\n[Guides > Accessibility > Inline Popups](https://workday.github.io/canvas-kit/?path=/docs/guides-accessibility-inline-popups--docs).\nSee also the\n[Modal Dialog Pattern | APG | WAI | W3C](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/).\n\n### Minimum Accessible Structure\n\nThe following matches the [Basic Example](#basic-example) layout: **`Modal.CloseIcon`** before\n**`Modal.Heading`** so open focus lands on the dismiss control first; primary actions use\n**`Modal.CloseButton`** (which closes the modal on activate).\n\n```tsx\n\n<Modal>\n <Modal.Target as={PrimaryButton}>Open</Modal.Target>\n <Modal.Overlay>\n <Modal.Card>\n <Modal.CloseIcon aria-label="Close" />\n <Modal.Heading>Title</Modal.Heading>\n <Modal.Body>Content</Modal.Body>\n <Modal.ButtonGroup>\n <Modal.CloseButton>Cancel</Modal.CloseButton>\n <Modal.CloseButton as={PrimaryButton}>Acknowledge</Modal.CloseButton>\n </Modal.ButtonGroup>\n </Modal.Card>\n </Modal.Overlay>\n</Modal>;\n```\n\nInclude a dismiss control: **`Modal.CloseButton`** with visible text (for example "Cancel" or\n"Close"), and/or **`Modal.CloseIcon`** when the design uses an icon-only dismiss (requires\n**`aria-label`** or **`Tooltip`**). Use **`Modal.CloseButton`** for actions that should also close\nthe modal (for example "Acknowledge"). Compose with **`Modal.Overlay` \u2192 `Modal.Card`** (or\n**`Modal.OverflowOverlay`** when the entire overlay should scroll).\n\n### Built-in Behaviors\n\nCanvas Kit applies these automatically via `useModalModel` and Modal subcomponents. **Do not\nduplicate them** in consuming code.\n\n**Popup behaviors** (_composed on the default model_):\n\n- `useInitialFocus` \u2014 moves focus into the modal when it opens (default: first focusable element in\n DOM order; optional override via `initialFocusRef` on the model)\n- `useReturnFocus` \u2014 returns focus to `Modal.Target` (or configured return target) when it closes\n- `useCloseOnOverlayClick` \u2014 pointer interaction on the overlay (outside the dialog) closes the\n modal\n- `useCloseOnEscape` \u2014 <kbd>Escape</kbd> closes the modal\n- `useFocusTrap` \u2014 <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> cycle focus **inside** the modal\n (keyboard focus does not leave the dialog)\n- `useAssistiveHideSiblings` \u2014 applies **`aria-hidden`** to siblings of the modal stack while open\n- `useDisableBodyScroll` \u2014 prevents background page scroll while the modal is open\n\n**ARIA and DOM** (_applied by hooks/subcomponents_):\n\n- `Modal.Card`: `role="dialog"`, `aria-labelledby` referencing the heading `id`, and\n **`aria-modal="false"`**\n- `Modal.Heading`: `id` wired to `Modal.Card`\'s `aria-labelledby`; when there is no icon-only close\n button before the heading, `useModalHeading` may temporarily set **`tabindex="0"`** on the heading\n so initial focus still lands near the start of the dialog\n- `Modal.CloseIcon` / `Modal.CloseButton`: `onClick` that calls `model.events.hide()`\n- `Modal.Target`: `ref` and `onClick` to open and to receive return focus\n\n**Keyboard** (_trigger is `Modal.Target`, default `SecondaryButton`_):\n\n- <kbd>Enter</kbd> / <kbd>Space</kbd> on the trigger opens the modal (standard button behavior)\n- On open and close, focus is managed by **`useInitialFocus`** and **`useReturnFocus`** (application\n overrides: see **Focus management** in Accessibility Requirements)\n- <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> move focus through interactive elements\n **inside** the modal; focus stays trapped within the dialog\n- <kbd>Escape</kbd> closes the modal and returns focus per `useReturnFocus` (unless Escape dismiss\n is omitted via a custom model\u2014see **Accept-only / no Escape dismiss**)\n\n**Screen reader expectations** (_when built-in behaviors are used as intended_):\n\n- On open, assistive technology should announce the first focused control (often a dismiss control),\n the dialog name (`Modal.Heading`), and `dialog` role\n- Sibling elements of the modal stack receive **`aria-hidden="true"`** while the modal is visible,\n which hides the rest of the page from many assistive technologies\n- Trapping **keyboard** focus does not stop all screen reader virtual-cursor movement outside the\n dialog; treat the trap as the primary keyboard affordance, not a hard boundary\u2014verify behavior in\n your supported browser and screen reader combinations\n\n### Accessibility Requirements\n\nRequired in application code for an accessible Modal. Hoist **`useModalModel`** when you need to\nconfigure focus targets, open without **`Modal.Target`**, or control when the modal closes (for\nexample form validation). Rows marked _(conditional)_ apply only when the situation\nmatches\u2014otherwise omit.\n\n**If no design spec is provided:** use default focus behavior; include a dismiss control and\n**`Modal.Heading`**; omit **`initialFocusRef`**, **`returnFocusRef`**, and **`aria-describedby`**.\nDo not remove Escape or overlay dismiss unless the design requires accept-only confirmation.\n\n**Focus management \u2014 defaults and developer prompts:** Canvas Kit handles open and close focus\nautomatically. **State the default to the developer first.** Only set **`initialFocusRef`** or\n**`returnFocusRef`** after the developer (or an explicit design spec) chooses a non-default target.\n**Do not generate focus refs by default.**\n\n| When | Default behavior | Ask the developer before overriding |\n| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Modal **opens** | **`useInitialFocus`** moves focus to the **first focusable element** in DOM order inside the modal (often **`Modal.CloseIcon`** or **`Modal.CloseButton`**). Omit **`initialFocusRef`**. | _Which element should receive focus when the modal opens?_ (Only when the default first focusable element is wrong for the design.) Attach **`initialFocusRef`** to that element on **`useModalModel`**. |\n| Modal **closes** | **`useReturnFocus`** moves focus to **`Modal.Target`**. Omit **`returnFocusRef`**. | _Which element should receive focus when the modal closes?_ (Only when return focus should land somewhere other than **`Modal.Target`**.) |\n\nIf close **removes the trigger from the DOM**, **`returnFocusRef`** alone is not enough\u2014move focus\nafter the UI updates (for example with **`useLayoutEffect`**). See [Return Focus](#return-focus).\n\n**Custom targets** _(conditional)_: Apply when using a custom **`as`** component on\n**`Modal.Target`**. **`Modal.Target`** adds **`onClick`** and **`ref`**. Custom targets must forward\nboth to a **keyboard-focusable** element (prefer a native **`<button>`** or\n**`as={SecondaryButton}`** / another Canvas Kit button). Wrap the component in\n**`React.forwardRef`** when it does not forward refs by default (required if the modal can open\nprogrammatically before the user clicks the target).\n\n| Requirement | How to satisfy |\n| ------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Accessible dialog name | Use **`Modal.Heading`** so `aria-labelledby` on `Modal.Card` references a visible title. Do not omit the heading: **`Modal.Card` always sets `aria-labelledby`**, and an `aria-label` fallback is unreliable when that ID does not exist. |\n| Dismiss control | Provide a way to close the modal: **`Modal.CloseButton`** with visible text (no extra **`aria-label`** needed), and/or **`Modal.CloseIcon`** for icon-only dismiss (requires **`Tooltip`** or translated **`aria-label`**). |\n| Keyboard-operable trigger | See **Custom targets** above. |\n| Supplementary copy when overriding open focus _(conditional)_ | When **`initialFocusRef`** places open focus **below** **`Modal.Heading`**, assign a unique `id` to supplementary text and pass **`aria-describedby`** on **`Modal.Card`**. See **Open focus below the heading** below, [Custom Focus](#custom-focus), and [Popup > Initial Focus](https://workday.github.io/canvas-kit/?path=/docs/components-popups-popup--docs#initial-focus) (button-focus variant). |\n| Keyboard-scrollable overflowing body _(conditional)_ | When **`Modal.Body`** content overflows, set **`tabIndex={0}`** on **`Modal.Body`** so keyboard users can focus the scroll region and use arrow keys. See [Body Content Overflow](#body-content-overflow). |\n| Accept-only / no Escape dismiss _(conditional)_ | Only when the design requires the user to accept (not dismiss via Escape or overlay click): compose a custom **`usePopupModel`** with the modal behaviors you still need, **omitting** **`useCloseOnEscape`** and **`useCloseOnOverlayClick`**. See [Without Close Icon](#without-close-icon). |\n\n**Open focus below the heading** _(conditional; see supplementary copy row above)_:\n\nWhen open focus moves past the heading (for example into a form field), wire **`aria-describedby`**\nso assistive technology still announces the supplementary copy. For focusing a primary action\ninstead of an input, see\n[Popup > Initial Focus](https://workday.github.io/canvas-kit/?path=/docs/components-popups-popup--docs#initial-focus).\n\n```tsx\n\nconst Example = () => {\n const descriptionId = useUniqueId();\n const inputRef = React.useRef<HTMLInputElement>(null);\n const model = useModalModel({initialFocusRef: inputRef});\n\n return (\n <Modal model={model}>\n <Modal.Target>Open</Modal.Target>\n <Modal.Overlay>\n <Modal.Card aria-describedby={descriptionId}>\n <Modal.CloseIcon aria-label="Close" />\n <Modal.Heading>Title</Modal.Heading>\n <Modal.Body>\n <p id={descriptionId}>Enter your email to continue.</p>\n <FormField>\n <FormField.Label>Email</FormField.Label>\n <FormField.Input as={TextInput} ref={inputRef} />\n </FormField>\n </Modal.Body>\n <Modal.CloseButton>Cancel</Modal.CloseButton>\n </Modal.Card>\n </Modal.Overlay>\n </Modal>\n );\n};\n```\n\n**Summary for code generation:**\n\n- **REQUIRED:** accessible name, dismiss control, keyboard-operable trigger,\n **`Modal.Overlay` \u2192 `Modal.Card`** composition\n- **CONDITIONAL:** **`initialFocusRef`**, **`returnFocusRef`**, **`aria-describedby`**,\n **`forwardRef`** on custom **`Modal.Target`**, **`tabIndex={0}`** on overflowing **`Modal.Body`**,\n custom model omitting Escape/overlay dismiss, **`Modal.OverflowOverlay`**\n\n### Anti-Patterns\n\nDo **not** generate code that does the following (see **Accessibility Requirements** above for what\nto supply instead):\n\n- Manually set `role="dialog"`, `aria-labelledby`, or dialog `id` on **`Modal.Card`** or\n **`Modal.Heading`** \u2014 Canvas Kit hooks wire these\n- Override **`aria-modal`** to **`"true"`** on **`Modal.Card`** \u2014 when **`aria-modal`** is `true`,\n some assistive technologies hide everything outside the dialog, including portaled UI owned by the\n modal (such as a Select menu rendered as a sibling). Canvas Kit sets **`aria-modal="false"`** for\n a better VoiceOver experience while **`useAssistiveHideSiblings`** applies **`aria-hidden`** to\n background siblings. Do not change this unless accessibility has approved it. Unlike\n [**Dialog**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-dialog--docs),\n Modal also does **not** use the sibling **`aria-owns`** reading-order pattern\n- Omit **`Modal.Overlay`** (or **`Modal.OverflowOverlay`**), render **`Modal.Card`** outside it, or\n add a custom portal/restructure instead of **`Modal` \u2192 `Modal.Overlay` \u2192 `Modal.Card`**\n- Use **`open`** / **`onClose`** props on **`Modal`** \u2014 Modal has no controlled visibility props;\n use **`useModalModel`** and **`model.events.show()`** / **`model.events.hide()`**\n- Use **Dialog** when the task must block the rest of the page, or add **`useFocusRedirect`** /\n **`aria-owns`** expecting Modal-like blocking behavior \u2014 Modal uses a focus trap and sibling\n hiding instead\n- Set **`initialFocusRef`** or **`returnFocusRef`** by default \u2014 state the default focus behavior\n first and ask the developer before overriding (see **Focus management** in Accessibility\n Requirements)\n- Add **`aria-expanded`** or **`aria-haspopup`** on **`Modal.Target`** \u2014 those attributes apply to\n **non-modal** dialogs (see\n [**Dialog**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-dialog--docs) /\n [**Popup**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-popup--docs)); Modal\n moves focus into the dialog on open and must not use this pattern\n- Use a custom **`Modal.Target`** **`as`** component that does not forward **`ref`** to a focusable\n element \u2014 use **`React.forwardRef`** or a Canvas Kit button component instead\n- Rely on **`returnFocusRef`** alone when close **removes the trigger from the DOM** (see\n [Return Focus](#return-focus))\n- Omit Escape and overlay dismiss without an explicit accept-only design requirement, or remove\n **`Modal.CloseIcon`** without providing another dismiss path (see **Accept-only / no Escape\n dismiss**)\n- Leave overflowing **`Modal.Body`** content without a keyboard path to scroll (see\n **Keyboard-scrollable overflowing body**)\n- Nest multiple **`Modal`** instances without deliberate initial focus and return-focus planning\n- Assume the focus trap alone fully hides outside content from every assistive technology \u2014 verify\n supported browser and screen reader combinations'
|
|
457
457
|
},
|
|
458
458
|
menu: {
|
|
459
459
|
title: "Components/Popups/Menu",
|
|
460
460
|
storybookUrl: "https://workday.github.io/canvas-kit/?path=/docs/components-popups-menu--docs",
|
|
461
461
|
mdxPath: "modules/react/menu/stories/Menu.mdx",
|
|
462
|
-
mdxProse: "# Canvas Kit Menu\n\n`Menu` displays a list of options when triggered by an action or UI element like an icon or button.\n\n[> Workday Design Reference](https://design.workday.com/components/popups/menus)\n\n## Installation\n\n```sh\nyarn add @workday/canvas-kit-react\n```\n\n## Usage\n\n### Basic Example\n\n`Menu` is typically triggered by an action such as pressing a button. The `Menu` comes with a\n`Target` subcomponent and a Popup.\n```tsx\nimport React from 'react';\n\nimport {Menu} from '@workday/canvas-kit-react/menu';\nimport {BodyText} from '@workday/canvas-kit-react/text';\nimport {system} from '@workday/canvas-tokens-web';\n\nexport const Basic = () => {\n const [selected, setSelected] = React.useState('');\n return (\n <Menu onSelect={data => setSelected(data.id)}>\n <Menu.Target>Open Menu</Menu.Target>\n <Menu.Popper>\n <Menu.Card>\n <Menu.List>\n <Menu.Item>First Item</Menu.Item>\n <Menu.Item>Second Item</Menu.Item>\n <Menu.Divider />\n <Menu.Item>Third Item (with a really, really, really long label)</Menu.Item>\n <Menu.Item aria-disabled>Fourth Item</Menu.Item>\n </Menu.List>\n </Menu.Card>\n </Menu.Popper>\n <BodyText size=\"small\" cs={{marginBlockStart: system.gap.md}}>\n Selected: <span data-testid=\"output\">{selected}</span>\n </BodyText>\n </Menu>\n );\n};\n```\n\n`Menu` will automatically focus on the cursor item (first item by default). The `Menu` uses a menu\nmodel which composes a list model and a popup model and sets up accessibility features for you.\n\n> **Note:** When content exceeds `60vh`, the menu content is clipped and the menu becomes scrollable.\n\n### Alt Example\n\nThe `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.\n```tsx\nimport React from 'react';\n\nimport {SecondaryButton} from '@workday/canvas-kit-react/button';\nimport {Menu} from '@workday/canvas-kit-react/menu';\nimport {createStyles, px2rem} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst altBackgroundStyles = createStyles({\n background: system.color.bg.alt.default,\n padding: system.padding.xl,\n borderRadius: system.shape.md,\n minHeight: px2rem(300),\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n});\n\nexport const Alt = () => {\n return (\n <div className={altBackgroundStyles}>\n <Menu>\n <Menu.Target as={SecondaryButton}>Open Menu</Menu.Target>\n <Menu.Popper>\n <Menu.Card variant=\"alt\">\n <Menu.List>\n <Menu.Item>First Item</Menu.Item>\n <Menu.Item>Second Item</Menu.Item>\n <Menu.Item>Third Item</Menu.Item>\n </Menu.List>\n </Menu.Card>\n </Menu.Popper>\n </Menu>\n </div>\n );\n};\n```\n\n### Context Menu\n```tsx\nimport React from 'react';\n\nimport {Menu} from '@workday/canvas-kit-react/menu';\nimport {BodyText} from '@workday/canvas-kit-react/text';\nimport {system} from '@workday/canvas-tokens-web';\n\nexport const ContextMenu = () => {\n const [selected, setSelected] = React.useState('');\n return (\n <Menu onSelect={data => setSelected(data.id)}>\n <Menu.TargetContext>Right-click to Open Menu</Menu.TargetContext>\n <Menu.Popper>\n <Menu.Card>\n <Menu.List>\n <Menu.Item>First Item</Menu.Item>\n <Menu.Item>Second Item</Menu.Item>\n <Menu.Item>Third Item (with a really, really, really long label)</Menu.Item>\n <Menu.Item>Fourth Item</Menu.Item>\n </Menu.List>\n </Menu.Card>\n </Menu.Popper>\n <BodyText size=\"small\" cs={{marginBlockStart: system.gap.md}}>\n Selected: <span data-testid=\"output\">{selected}</span>\n </BodyText>\n </Menu>\n );\n};\n```\n\n> **Accessibility Note**: This variation relies on the `contextmenu` browser event, which has\n> varying levels of support across different operating systems. On Windows, this feature is better\n> supported and users can typically trigger context menus using the **Shift + F10** keyboard\n> shortcut or the dedicated **Context Menu** key (if available on their keyboard). However, on\n> macOS, context menu support is limited and may require users to enable specific accessibility\n> settings in their system preferences to function properly. Consider providing alternative access\n> methods for critical functionality.\n\n### Icons\n\nMenu supports more complex children, including icons, but the text of the item will no longer be\nknown. In this case, add a `data-text` attribute to inform the collection system what the text of\nthe item is. The text is used for components that filter based on text. For example, a Select\ncomponent will jump to an item based on the keys the user types. If the user types \"C\", the\ncomponent will jump to the first item that starts with a \"C\". This functionality requires knowledge\nabout the text of the item.\n```tsx\nimport React from 'react';\n\nimport {Menu} from '@workday/canvas-kit-react/menu';\nimport {BodyText} from '@workday/canvas-kit-react/text';\nimport {\n bookUserIcon,\n cloudArrowUpIcon,\n configureIcon,\n userIcon,\n} from '@workday/canvas-system-icons-web';\nimport {system} from '@workday/canvas-tokens-web';\n\nexport const Icons = () => {\n const [selected, setSelected] = React.useState('');\n return (\n <Menu onSelect={data => setSelected(data.id)}>\n <Menu.Target>Open Menu</Menu.Target>\n <Menu.Popper>\n <Menu.Card>\n <Menu.List>\n <Menu.Item data-text=\"First Item\">\n <Menu.Item.Icon icon={cloudArrowUpIcon} />\n <Menu.Item.Text>First Item</Menu.Item.Text>\n </Menu.Item>\n <Menu.Item data-text=\"Second Item (with a really really really long label)\">\n <Menu.Item.Icon icon={configureIcon} />\n <Menu.Item.Text>Second Item (with a really really really long label)</Menu.Item.Text>\n </Menu.Item>\n <Menu.Item aria-disabled data-text=\"Third Item\">\n <Menu.Item.Icon icon={cloudArrowUpIcon} />\n <Menu.Item.Text>Third Item</Menu.Item.Text>\n <Menu.Item.Icon icon={bookUserIcon} />\n </Menu.Item>\n <Menu.Item data-text=\"User\">\n <Menu.Item.Icon icon={userIcon} />\n <Menu.Item.Text>User</Menu.Item.Text>\n </Menu.Item>\n <Menu.Divider />\n <Menu.Item data-text=\"Fifth Item (with divider)\">\n <Menu.Item.Icon icon={bookUserIcon} />\n <Menu.Item.Text>Fifth Item (with divider)</Menu.Item.Text>\n </Menu.Item>\n </Menu.List>\n </Menu.Card>\n </Menu.Popper>\n <BodyText size=\"small\" cs={{marginBlockStart: system.gap.md}}>\n Selected: <span data-testid=\"output\">{selected}</span>\n </BodyText>\n </Menu>\n );\n};\n```\n\n> **Accessibility Note**: Icons in menu items do not inherently provide text alternatives to\n> assistive technologies. However, in most cases, icons are used decoratively alongside text labels,\n> and additional text alternatives are not necessary since the menu item text itself provides the\n> accessible name.\n\n### Grouping\n\nGrouping adds hierarchy and categorization to menu items. Group headers do not represent menu items\nand are not selectable with the keyboard or mouse.\n\n> **Note**: Grouping is not supported in virtual rendering. Menus by default have `shouldVirtualize`\n> set to `false`. Setting to `true` results in unspecified behavior. We use `react-virtual` which\n> doesn't support nested virtualization.\n```tsx\nimport React from 'react';\n\nimport {Menu} from '@workday/canvas-kit-react/menu';\nimport {BodyText} from '@workday/canvas-kit-react/text';\nimport {system} from '@workday/canvas-tokens-web';\n\nexport const Grouping = () => {\n const [selected, setSelected] = React.useState('');\n return (\n <>\n <Menu onSelect={data => setSelected(data.id)}>\n <Menu.Target>Open Menu</Menu.Target>\n <Menu.Popper>\n <Menu.Card>\n <Menu.List>\n <Menu.Group title=\"First Group\">\n <Menu.Item>First Item</Menu.Item>\n <Menu.Item>Second Item</Menu.Item>\n </Menu.Group>\n <Menu.Group title=\"Second Group\">\n <Menu.Item>Third Item (with a really, really, really long label)</Menu.Item>\n <Menu.Item aria-disabled>Fourth Item</Menu.Item>\n </Menu.Group>\n </Menu.List>\n </Menu.Card>\n </Menu.Popper>\n <BodyText size=\"small\" cs={{marginBlockStart: system.gap.md}}>\n Selected: <span data-testid=\"output\">{selected}</span>\n </BodyText>\n </Menu>\n </>\n );\n};\n```\n\n> **Accessibility Note**: Menu groups use `role=\"group\"` with appropriate labeling to provide\n> semantic structure for assistive technologies. When navigating through grouped menu items, screen\n> readers will announce the group label when users enter a new group, providing important context\n> about the organization of the menu. Group headers are not part of the keyboard navigation\n> sequence, allowing users to efficiently move between actionable menu items. This semantic grouping\n> helps all users, including those using assistive technologies, understand the hierarchy and\n> categorization of menu options.\n\n### Nested\n\nMenus support nesting. If you only have a few items and not very many nesting levels, the menu can\nbe defined statically using JSX. A submenu is defined using the `<Menu.Submenu>` component. The\n`Submenu` is implemented as a special `Menu` subcomponent. The API of the submenu is the same as the\n`Menu` except the submenu's target is also a menu item. The component is named `TargetItem` to\nindicate this dual role.\n```tsx\nimport React from 'react';\n\nimport {Menu} from '@workday/canvas-kit-react/menu';\nimport {BodyText} from '@workday/canvas-kit-react/text';\nimport {system} from '@workday/canvas-tokens-web';\n\nexport const Nested = () => {\n const [selected, setSelected] = React.useState('');\n return (\n <Menu\n id=\"first-menu\"\n onSelect={data => {\n setSelected(data.id);\n }}\n >\n <Menu.Target>Open Menu</Menu.Target>\n <Menu.Popper>\n <Menu.Card>\n <Menu.List>\n <Menu.Item data-id=\"first-item\">First Item</Menu.Item>\n <Menu.Submenu id=\"second-menu\">\n <Menu.Submenu.TargetItem data-id=\"second-item\">Second Item</Menu.Submenu.TargetItem>\n <Menu.Submenu.Popper>\n <Menu.Submenu.Card>\n <Menu.Submenu.List>\n <Menu.Submenu.Item data-id=\"first-sub-item\">First Sub Item</Menu.Submenu.Item>\n <Menu.Submenu.Item data-id=\"second-sub-item\">First Sub Item</Menu.Submenu.Item>\n <Menu.Submenu.Item data-id=\"third-sub-item\">Third Sub Item</Menu.Submenu.Item>\n <Menu.Submenu.Item data-id=\"fourth-sub-item\">Fourth Sub Item</Menu.Submenu.Item>\n </Menu.Submenu.List>\n </Menu.Submenu.Card>\n </Menu.Submenu.Popper>\n </Menu.Submenu>\n <Menu.Divider />\n <Menu.Item data-id=\"third-item\">\n Third Item (with a really, really, really long label)\n </Menu.Item>\n <Menu.Item aria-disabled data-id=\"fourth-item\">\n Fourth Item\n </Menu.Item>\n </Menu.List>\n </Menu.Card>\n </Menu.Popper>\n <BodyText size=\"small\" cs={{marginBlockStart: system.gap.md}}>\n Selected: <span data-testid=\"output\">{selected}</span>\n </BodyText>\n </Menu>\n );\n};\n```\n\n> **Accessibility Note**: When a menu item has an attached submenu, the `<Menu.Submenu.TargetItem>`\n> includes `aria-haspopup=\"true\"` and `aria-expanded={true | false}` properties. These properties\n> will alert screen reader users to the available submenu systems.\n\n### Nested Dynamic Items\n\nMenu nesting is simpler with the dynamic API. In this example, a `renderItem` function is defined to\nallow recursive nesting of items using a data structure you define. A submenu will inherit the\n`getId` and `getTextValue` functions of the parent menu. While you can pass a specialize `getId` or\n`getTextValue` function to each submenu, it may be simpler to use the same one for the menu and\nsubmenus.\n```tsx\nimport React from 'react';\n\nimport {Menu} from '@workday/canvas-kit-react/menu';\nimport {BodyText} from '@workday/canvas-kit-react/text';\nimport {system} from '@workday/canvas-tokens-web';\n\ntype Item = {\n type?: 'item';\n id: string;\n label: string;\n};\ntype SubmenuItem = {\n id: string;\n label: string;\n type: 'submenu';\n children: (Item | SubmenuItem)[];\n};\n\n// This is a user-defined object. The structure uses `id` for the item identifier which is the\n// default key used by the collection system and therefore doesn't require a `getId` function to be\n// passed to the model. The `label` isn't the standard text value used by the collection system, so\n// a `getTextValue` function is required. The `type` and `children` aren't important at all to the\n// menu and are used in the template by the user-defined `renderItem` function.\nconst items: (SubmenuItem | Item)[] = [\n {id: 'first-item', label: 'First Item'},\n {\n id: 'second-item',\n label: 'Second Item',\n type: 'submenu',\n children: [\n {id: 'first-sub-item', label: 'First Sub Item'},\n {\n id: 'second-sub-item',\n label: 'Second Sub Item',\n type: 'submenu',\n children: [\n {id: 'first-sub-sub-item', label: 'First Sub Sub Item'},\n {\n id: 'second-sub-sub-item',\n type: 'submenu',\n label: 'Second Sub Sub Item',\n children: [\n {id: 'first-sub-sub-sub-item', label: 'First Sub Sub Sub Item'},\n {\n id: 'second-sub-sub-sub-item',\n label: 'Second Sub Sub Sub Item',\n },\n {id: 'third-sub-sub-sub-item', label: 'Third Sub Sub Sub Item'},\n {id: 'fourth-sub-sub-sub-item', label: 'Fourth Sub Sub Sub Item'},\n ],\n },\n {id: 'third-sub-sub-item', label: 'Third Sub Sub Item'},\n {id: 'fourth-sub-sub-item', label: 'Fourth Sub Sub Item'},\n ],\n },\n {id: 'third-sub-item', label: 'Third Sub Item'},\n {id: 'fourth-sub-item', label: 'Fourth Sub Item'},\n ],\n },\n {id: 'third-item', label: 'Third Item'},\n {id: 'fourth-item', label: 'Fourth Item'},\n];\n\nexport const NestedDynamic = () => {\n const [selected, setSelected] = React.useState('');\n\n // defining this inline function allows use to recurse any nesting level defined by the `items`\n // array.\n function renderItem(item: SubmenuItem | Item) {\n if (item.type === 'submenu') {\n return (\n <Menu.Submenu id={item.id} items={item.children}>\n <Menu.Submenu.TargetItem>{item.label}</Menu.Submenu.TargetItem>\n <Menu.Submenu.Popper>\n <Menu.Submenu.Card>\n <Menu.Submenu.List>{renderItem}</Menu.Submenu.List>\n </Menu.Submenu.Card>\n </Menu.Submenu.Popper>\n </Menu.Submenu>\n );\n }\n return <Menu.Item>{item.label}</Menu.Item>;\n }\n\n return (\n <Menu\n items={items}\n id=\"first-menu\"\n getTextValue={item => item.label}\n onSelect={data => {\n setSelected(data.id);\n }}\n >\n <Menu.Target>Open Menu</Menu.Target>\n <Menu.Popper>\n <Menu.Card>\n <Menu.List>{renderItem}</Menu.List>\n </Menu.Card>\n </Menu.Popper>\n <BodyText size=\"small\" cs={{marginBlockStart: system.gap.md}}>\n Selected: <span data-testid=\"output\">{selected}</span>\n </BodyText>\n </Menu>\n );\n};\n```\n\n## Accessibility\n\nOur Menu component is based on the Menu Button pattern on the ARIA Authoring Practices Guide from\nthe W3C and relies on the roving tabindex technique for managing focus within the opened menu. This\nmeans that the minimum requirements for screen reader support and keyboard navigation are included\nin the component.\n\n[Menu Button Pattern | APG | WAI | W3C](https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/)\n\n- The `<Menu.Target>` sub-component uses `aria-haspopup=\"true\"` and `aria-expanded={true | false}`\n properties. This benefits screen reader users by indicating when a button element has an attached\n menu.\n- The `<Menu.List>` sub-component uses `role=\"menu\"` and `<Menu.Item>` uses `role=\"menuitem\"` ARIA\n roles. These roles allow screen readers to pass through arrow key events to the web application.\n- The `<Menu.List>` sub-component includes an `aria-labelledby` ID reference to the `<Menu.Target>`\n sub-component. This assigns a label to the menu for context.\n\n### Navigation\n\n- **Enter or Space**: When focused on the menu button, opens the menu and moves focus to the first\n menu item. When focused on a menu item, activates the item and closes the menu\n- **Escape**: Closes the menu and returns focus to the menu button\n- **Up & Down Arrow**: Moves focus up and down the menu items\n- **Home & End**: Moves focus to the first or last menu item\n- **Right & Left Arrow**: When focused on a menu item with a submenu, opens the submenu and moves\n focus to the first item in the submenu or closes the submenu and returns focus to the parent menu\n item\n\n### Screen Reader Experience\n\n- The menu button will be announced with its label text followed by the button role, a notification\n that it has a popup menu, and the current state of the menu (For example: \"Actions, button, menu\n popup, collapsed\")\n- **Opening the Menu:** When the menu button is activated, screen readers will announce the menu\n opening, the number of menu items available, and the currently focused item (For example:\n \"Actions, menu, First Action, menu item, 1 of 4.\")\n- **Navigating Menu Items:** As focus moves between menu items, screen readers will announce the\n item name and its position in the list (For example: \"Second Action, menu item, 2 of 4.\")\n- **Menu Items with Submenus:** When focused on a menu item that has a submenu, screen readers will\n announce that it has a submenu and provide the expanded/collapsed state (For example: \"More\n Actions, menu item, has submenu, collapsed, 3 of 4.\")\n\n## Component API\n\n## Specifications\n\n",
|
|
463
|
-
accessibilityProse: '## Accessibility\n\nOur Menu component is based on the Menu Button pattern on the ARIA Authoring Practices Guide from\nthe W3C and relies on the roving tabindex technique for managing focus within the opened menu. This\nmeans that the minimum requirements for screen reader support and keyboard navigation are included\nin the component.\n\n[Menu Button Pattern | APG | WAI | W3C](https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/)\n\n- The `<Menu.Target>` sub-component uses `aria-haspopup="true"` and `aria-expanded={true | false}`\n properties. This benefits screen reader users by indicating when a button element has an attached\n menu.\n- The `<Menu.List>` sub-component uses `role="menu"` and `<Menu.Item>` uses `role="menuitem"` ARIA\n roles. These roles allow screen readers to pass through arrow key events to the web application.\n- The `<Menu.List>` sub-component includes an `aria-labelledby` ID reference to the `<Menu.Target>`\n sub-component. This assigns a label to the menu for context.\n\n### Navigation\n\n- **Enter or Space**: When focused on the menu button, opens the menu and moves focus to the first\n menu item. When focused on a menu item, activates the item and closes the menu\n- **Escape**: Closes the menu and returns focus to the menu button\n- **Up & Down Arrow**: Moves focus up and down the menu items\n- **Home & End**: Moves focus to the first or last menu item\n- **Right & Left Arrow**: When focused on a menu item with a submenu, opens the submenu and moves\n focus to the first item in the submenu or closes the submenu and returns focus to the parent menu\n item\n\n### Screen Reader Experience\n\n- The menu button will be announced with its label text followed by the button role, a notification\n that it has a popup menu, and the current state of the menu (For example: "Actions, button, menu\n popup, collapsed")\n- **Opening the Menu:** When the menu button is activated, screen readers will announce the menu\n opening, the number of menu items available, and the currently focused item (For example:\n "Actions, menu, First Action, menu item, 1 of 4.")\n- **Navigating Menu Items:** As focus moves between menu items, screen readers will announce the\n item name and its position in the list (For example: "Second Action, menu item, 2 of 4.")\n- **Menu Items with Submenus:** When focused on a menu item that has a submenu, screen readers will\n announce that it has a submenu and provide the expanded/collapsed state (For example: "More\n Actions, menu item, has submenu, collapsed, 3 of 4.")'
|
|
462
|
+
mdxProse: "# Canvas Kit Menu\n\n`Menu` displays a list of options when triggered by an action or UI element like an icon or button.\n\n[> Workday Design Reference](https://design.workday.com/components/popups/menus)\n\n## Installation\n\n```sh\nyarn add @workday/canvas-kit-react\n```\n\n## Usage\n\n### Basic Example\n\n`Menu` is typically triggered by an action such as pressing a button. The `Menu` comes with a\n`Target` subcomponent and a Popup.\n```tsx\nimport React from 'react';\n\nimport {Menu} from '@workday/canvas-kit-react/menu';\nimport {BodyText} from '@workday/canvas-kit-react/text';\nimport {system} from '@workday/canvas-tokens-web';\n\nexport const Basic = () => {\n const [selected, setSelected] = React.useState('');\n return (\n <Menu onSelect={data => setSelected(data.id)}>\n <Menu.Target>Open Menu</Menu.Target>\n <Menu.Popper>\n <Menu.Card>\n <Menu.List>\n <Menu.Item>First Item</Menu.Item>\n <Menu.Item>Second Item</Menu.Item>\n <Menu.Divider />\n <Menu.Item>Third Item (with a really, really, really long label)</Menu.Item>\n <Menu.Item aria-disabled>Fourth Item</Menu.Item>\n </Menu.List>\n </Menu.Card>\n </Menu.Popper>\n <BodyText size=\"small\" cs={{marginBlockStart: system.gap.md}}>\n Selected: <span data-testid=\"output\">{selected}</span>\n </BodyText>\n </Menu>\n );\n};\n```\n\n`Menu` will automatically move focus to the first menu item when it opens. The `Menu` uses a menu\nmodel which composes a list model and a popup model and sets up accessibility features for you.\n\n> **Note:** When content exceeds `60vh`, the menu content is clipped and the menu becomes scrollable.\n\n### Alt Example\n\nThe `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.\n```tsx\nimport React from 'react';\n\nimport {SecondaryButton} from '@workday/canvas-kit-react/button';\nimport {Menu} from '@workday/canvas-kit-react/menu';\nimport {createStyles, px2rem} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst altBackgroundStyles = createStyles({\n background: system.color.bg.alt.default,\n padding: system.padding.xl,\n borderRadius: system.shape.md,\n minHeight: px2rem(300),\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n});\n\nexport const Alt = () => {\n return (\n <div className={altBackgroundStyles}>\n <Menu>\n <Menu.Target as={SecondaryButton}>Open Menu</Menu.Target>\n <Menu.Popper>\n <Menu.Card variant=\"alt\">\n <Menu.List>\n <Menu.Item>First Item</Menu.Item>\n <Menu.Item>Second Item</Menu.Item>\n <Menu.Item>Third Item</Menu.Item>\n </Menu.List>\n </Menu.Card>\n </Menu.Popper>\n </Menu>\n </div>\n );\n};\n```\n\n### Context Menu\n```tsx\nimport React from 'react';\n\nimport {Menu} from '@workday/canvas-kit-react/menu';\nimport {BodyText} from '@workday/canvas-kit-react/text';\nimport {system} from '@workday/canvas-tokens-web';\n\nexport const ContextMenu = () => {\n const [selected, setSelected] = React.useState('');\n return (\n <Menu onSelect={data => setSelected(data.id)}>\n <Menu.TargetContext>Right-click to Open Menu</Menu.TargetContext>\n <Menu.Popper>\n <Menu.Card>\n <Menu.List>\n <Menu.Item>First Item</Menu.Item>\n <Menu.Item>Second Item</Menu.Item>\n <Menu.Item>Third Item (with a really, really, really long label)</Menu.Item>\n <Menu.Item>Fourth Item</Menu.Item>\n </Menu.List>\n </Menu.Card>\n </Menu.Popper>\n <BodyText size=\"small\" cs={{marginBlockStart: system.gap.md}}>\n Selected: <span data-testid=\"output\">{selected}</span>\n </BodyText>\n </Menu>\n );\n};\n```\n\n> **Accessibility Note**: This variation relies on the `contextmenu` browser event, which has\n> varying levels of support across different operating systems. On Windows, this feature is better\n> supported and users can typically trigger context menus using the **Shift + F10** keyboard\n> shortcut or the dedicated **Context Menu** key (if available on their keyboard). However, on\n> macOS, context menu support is limited and may require users to enable specific accessibility\n> settings in their system preferences to function properly. Consider providing alternative access\n> methods for critical functionality.\n\n### Icons\n\nMenu supports more complex children, including icons, but the text of the item will no longer be\nknown. In this case, add a `data-text` attribute to inform the collection system what the text of\nthe item is. The text is used for components that filter based on text. For example, a Select\ncomponent will jump to an item based on the keys the user types. If the user types \"C\", the\ncomponent will jump to the first item that starts with a \"C\". This functionality requires knowledge\nabout the text of the item.\n```tsx\nimport React from 'react';\n\nimport {Menu} from '@workday/canvas-kit-react/menu';\nimport {BodyText} from '@workday/canvas-kit-react/text';\nimport {\n bookUserIcon,\n cloudArrowUpIcon,\n configureIcon,\n userIcon,\n} from '@workday/canvas-system-icons-web';\nimport {system} from '@workday/canvas-tokens-web';\n\nexport const Icons = () => {\n const [selected, setSelected] = React.useState('');\n return (\n <Menu onSelect={data => setSelected(data.id)}>\n <Menu.Target>Open Menu</Menu.Target>\n <Menu.Popper>\n <Menu.Card>\n <Menu.List>\n <Menu.Item data-text=\"First Item\">\n <Menu.Item.Icon icon={cloudArrowUpIcon} />\n <Menu.Item.Text>First Item</Menu.Item.Text>\n </Menu.Item>\n <Menu.Item data-text=\"Second Item (with a really really really long label)\">\n <Menu.Item.Icon icon={configureIcon} />\n <Menu.Item.Text>Second Item (with a really really really long label)</Menu.Item.Text>\n </Menu.Item>\n <Menu.Item aria-disabled data-text=\"Third Item\">\n <Menu.Item.Icon icon={cloudArrowUpIcon} />\n <Menu.Item.Text>Third Item</Menu.Item.Text>\n <Menu.Item.Icon icon={bookUserIcon} />\n </Menu.Item>\n <Menu.Item data-text=\"User\">\n <Menu.Item.Icon icon={userIcon} />\n <Menu.Item.Text>User</Menu.Item.Text>\n </Menu.Item>\n <Menu.Divider />\n <Menu.Item data-text=\"Fifth Item (with divider)\">\n <Menu.Item.Icon icon={bookUserIcon} />\n <Menu.Item.Text>Fifth Item (with divider)</Menu.Item.Text>\n </Menu.Item>\n </Menu.List>\n </Menu.Card>\n </Menu.Popper>\n <BodyText size=\"small\" cs={{marginBlockStart: system.gap.md}}>\n Selected: <span data-testid=\"output\">{selected}</span>\n </BodyText>\n </Menu>\n );\n};\n```\n\n> **Accessibility Note**: Icons in menu items do not inherently provide text alternatives to\n> assistive technologies. However, in most cases, icons are used decoratively alongside text labels,\n> and additional text alternatives are not necessary since the menu item text itself provides the\n> accessible name.\n\n### Grouping\n\nGrouping adds hierarchy and categorization to menu items. Group headers do not represent menu items\nand are not selectable with the keyboard or mouse.\n\n> **Note**: Grouping is not supported in virtual rendering. Menus by default have `shouldVirtualize`\n> set to `false`. Setting to `true` results in unspecified behavior. We use `react-virtual` which\n> doesn't support nested virtualization.\n```tsx\nimport React from 'react';\n\nimport {Menu} from '@workday/canvas-kit-react/menu';\nimport {BodyText} from '@workday/canvas-kit-react/text';\nimport {system} from '@workday/canvas-tokens-web';\n\nexport const Grouping = () => {\n const [selected, setSelected] = React.useState('');\n return (\n <>\n <Menu onSelect={data => setSelected(data.id)}>\n <Menu.Target>Open Menu</Menu.Target>\n <Menu.Popper>\n <Menu.Card>\n <Menu.List>\n <Menu.Group title=\"First Group\">\n <Menu.Item>First Item</Menu.Item>\n <Menu.Item>Second Item</Menu.Item>\n </Menu.Group>\n <Menu.Group title=\"Second Group\">\n <Menu.Item>Third Item (with a really, really, really long label)</Menu.Item>\n <Menu.Item aria-disabled>Fourth Item</Menu.Item>\n </Menu.Group>\n </Menu.List>\n </Menu.Card>\n </Menu.Popper>\n <BodyText size=\"small\" cs={{marginBlockStart: system.gap.md}}>\n Selected: <span data-testid=\"output\">{selected}</span>\n </BodyText>\n </Menu>\n </>\n );\n};\n```\n\n> **Accessibility Note**: Menu groups use `role=\"group\"` with appropriate labeling to provide\n> semantic structure for assistive technologies. When navigating through grouped menu items, screen\n> readers will announce the group label when users enter a new group, providing important context\n> about the organization of the menu. Group headers are not part of the keyboard navigation\n> sequence, allowing users to efficiently move between actionable menu items. This semantic grouping\n> helps all users, including those using assistive technologies, understand the hierarchy and\n> categorization of menu options.\n\n### Nested\n\nMenus support nesting. If you only have a few items and not very many nesting levels, the menu can\nbe defined statically using JSX. A submenu is defined using the `<Menu.Submenu>` component. The\n`Submenu` is implemented as a special `Menu` subcomponent. The API of the submenu is the same as the\n`Menu` except the submenu's target is also a menu item. The component is named `TargetItem` to\nindicate this dual role.\n```tsx\nimport React from 'react';\n\nimport {Menu} from '@workday/canvas-kit-react/menu';\nimport {BodyText} from '@workday/canvas-kit-react/text';\nimport {system} from '@workday/canvas-tokens-web';\n\nexport const Nested = () => {\n const [selected, setSelected] = React.useState('');\n return (\n <Menu\n id=\"first-menu\"\n onSelect={data => {\n setSelected(data.id);\n }}\n >\n <Menu.Target>Open Menu</Menu.Target>\n <Menu.Popper>\n <Menu.Card>\n <Menu.List>\n <Menu.Item data-id=\"first-item\">First Item</Menu.Item>\n <Menu.Submenu id=\"second-menu\">\n <Menu.Submenu.TargetItem data-id=\"second-item\">Second Item</Menu.Submenu.TargetItem>\n <Menu.Submenu.Popper>\n <Menu.Submenu.Card>\n <Menu.Submenu.List>\n <Menu.Submenu.Item data-id=\"first-sub-item\">First Sub Item</Menu.Submenu.Item>\n <Menu.Submenu.Item data-id=\"second-sub-item\">First Sub Item</Menu.Submenu.Item>\n <Menu.Submenu.Item data-id=\"third-sub-item\">Third Sub Item</Menu.Submenu.Item>\n <Menu.Submenu.Item data-id=\"fourth-sub-item\">Fourth Sub Item</Menu.Submenu.Item>\n </Menu.Submenu.List>\n </Menu.Submenu.Card>\n </Menu.Submenu.Popper>\n </Menu.Submenu>\n <Menu.Divider />\n <Menu.Item data-id=\"third-item\">\n Third Item (with a really, really, really long label)\n </Menu.Item>\n <Menu.Item aria-disabled data-id=\"fourth-item\">\n Fourth Item\n </Menu.Item>\n </Menu.List>\n </Menu.Card>\n </Menu.Popper>\n <BodyText size=\"small\" cs={{marginBlockStart: system.gap.md}}>\n Selected: <span data-testid=\"output\">{selected}</span>\n </BodyText>\n </Menu>\n );\n};\n```\n\n> **Accessibility Note**: Canvas Kit applies `aria-haspopup` and `aria-expanded` on\n> **`Menu.Submenu.TargetItem`** automatically. Do not set these manually \u2014 see\n> [Accessibility](#accessibility).\n\n### Nested Dynamic Items\n\nMenu nesting is simpler with the dynamic API. In this example, a `renderItem` function is defined to\nallow recursive nesting of items using a data structure you define. A submenu will inherit the\n`getId` and `getTextValue` functions of the parent menu. While you can pass a specialize `getId` or\n`getTextValue` function to each submenu, it may be simpler to use the same one for the menu and\nsubmenus.\n```tsx\nimport React from 'react';\n\nimport {Menu} from '@workday/canvas-kit-react/menu';\nimport {BodyText} from '@workday/canvas-kit-react/text';\nimport {system} from '@workday/canvas-tokens-web';\n\ntype Item = {\n type?: 'item';\n id: string;\n label: string;\n};\ntype SubmenuItem = {\n id: string;\n label: string;\n type: 'submenu';\n children: (Item | SubmenuItem)[];\n};\n\n// This is a user-defined object. The structure uses `id` for the item identifier which is the\n// default key used by the collection system and therefore doesn't require a `getId` function to be\n// passed to the model. The `label` isn't the standard text value used by the collection system, so\n// a `getTextValue` function is required. The `type` and `children` aren't important at all to the\n// menu and are used in the template by the user-defined `renderItem` function.\nconst items: (SubmenuItem | Item)[] = [\n {id: 'first-item', label: 'First Item'},\n {\n id: 'second-item',\n label: 'Second Item',\n type: 'submenu',\n children: [\n {id: 'first-sub-item', label: 'First Sub Item'},\n {\n id: 'second-sub-item',\n label: 'Second Sub Item',\n type: 'submenu',\n children: [\n {id: 'first-sub-sub-item', label: 'First Sub Sub Item'},\n {\n id: 'second-sub-sub-item',\n type: 'submenu',\n label: 'Second Sub Sub Item',\n children: [\n {id: 'first-sub-sub-sub-item', label: 'First Sub Sub Sub Item'},\n {\n id: 'second-sub-sub-sub-item',\n label: 'Second Sub Sub Sub Item',\n },\n {id: 'third-sub-sub-sub-item', label: 'Third Sub Sub Sub Item'},\n {id: 'fourth-sub-sub-sub-item', label: 'Fourth Sub Sub Sub Item'},\n ],\n },\n {id: 'third-sub-sub-item', label: 'Third Sub Sub Item'},\n {id: 'fourth-sub-sub-item', label: 'Fourth Sub Sub Item'},\n ],\n },\n {id: 'third-sub-item', label: 'Third Sub Item'},\n {id: 'fourth-sub-item', label: 'Fourth Sub Item'},\n ],\n },\n {id: 'third-item', label: 'Third Item'},\n {id: 'fourth-item', label: 'Fourth Item'},\n];\n\nexport const NestedDynamic = () => {\n const [selected, setSelected] = React.useState('');\n\n // defining this inline function allows use to recurse any nesting level defined by the `items`\n // array.\n function renderItem(item: SubmenuItem | Item) {\n if (item.type === 'submenu') {\n return (\n <Menu.Submenu id={item.id} items={item.children}>\n <Menu.Submenu.TargetItem>{item.label}</Menu.Submenu.TargetItem>\n <Menu.Submenu.Popper>\n <Menu.Submenu.Card>\n <Menu.Submenu.List>{renderItem}</Menu.Submenu.List>\n </Menu.Submenu.Card>\n </Menu.Submenu.Popper>\n </Menu.Submenu>\n );\n }\n return <Menu.Item>{item.label}</Menu.Item>;\n }\n\n return (\n <Menu\n items={items}\n id=\"first-menu\"\n getTextValue={item => item.label}\n onSelect={data => {\n setSelected(data.id);\n }}\n >\n <Menu.Target>Open Menu</Menu.Target>\n <Menu.Popper>\n <Menu.Card>\n <Menu.List>{renderItem}</Menu.List>\n </Menu.Card>\n </Menu.Popper>\n <BodyText size=\"small\" cs={{marginBlockStart: system.gap.md}}>\n Selected: <span data-testid=\"output\">{selected}</span>\n </BodyText>\n </Menu>\n );\n};\n```\n\n## Accessibility\n\n`Menu` follows the\n[Menu Button Pattern | APG | WAI | W3C](https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/), which\nhas two parts with different accessibility jobs.\n\n**Menu button** (`Menu.Target`): a focusable control that opens and closes the menu. It exposes\npopup presence and expanded/collapsed state (`aria-haspopup`, `aria-expanded`), and receives focus\nagain when the menu is dismissed.\n\n**Menu popup** (`Menu.List` and its items): the floating action list. It uses `role=\"menu\"` /\n`role=\"menuitem\"`, is labeled by the button, and manages focus inside the list with **roving\ntabindex** so users can move between items and activate one.\n\nUse **Menu** for action lists opened from a control. Prefer\n[**Select**](https://workday.github.io/canvas-kit/?path=/docs/components-inputs-select--docs) or\n[**Combobox**](https://workday.github.io/canvas-kit/?path=/docs/features-combobox--docs)\nwhen choosing a value from options (`Menu.Option` / `listbox` patterns are composed there\u2014do not use\n`Menu.Option` alone for a standard menu button). Prefer\n[**Modal**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs) or\n[**Dialog**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-dialog--docs) for\ntask dialogs, not menus.\n\n### Minimum Accessible Structure\n\nThe following matches the [Basic Example](#basic-example): a keyboard-operable **`Menu.Target`**,\nportaled **`Menu.Popper` \u2192 `Menu.Card` \u2192 `Menu.List`**, and **`Menu.Item`** children. On open, focus\nmoves to the first menu item by default.\n\n```tsx\n\n<Menu>\n <Menu.Target>Open Menu</Menu.Target>\n <Menu.Popper>\n <Menu.Card>\n <Menu.List>\n <Menu.Item>First Item</Menu.Item>\n <Menu.Item>Second Item</Menu.Item>\n </Menu.List>\n </Menu.Card>\n </Menu.Popper>\n</Menu>;\n```\n\nProvide a clearly named **`Menu.Target`** (visible text, or an icon-only control with\n**`Tooltip`**, or a translated **`aria-label`** if you are not using **`Tooltip`**). \nUse **`aria-disabled`** on items that should stay in the keyboard sequence but\nnot activate \u2014 do not use the native `disabled` attribute for disabled menu items.\n\n### Built-in Behaviors\n\nCanvas Kit applies these automatically via `useMenuModel` (list + popup) and Menu subcomponents.\n**Do not duplicate them** in consuming code.\n\n**Popup behaviors** (_composed on the default model_):\n\n- `useAlwaysCloseOnOutsideClick` \u2014 pointer interaction outside closes the menu\n- `useCloseOnEscape` \u2014 <kbd>Escape</kbd> closes the menu\n- `useReturnFocus` (_on `Menu.List`_) \u2014 returns focus to **`Menu.Target`** (or configured return\n target) when the menu closes\n- `useFocusRedirect` (_on `Menu.List`_) \u2014 <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> from\n inside the menu closes it and moves focus to the next or previous focusable element on the page\n (not a focus trap)\n\n**ARIA and DOM** (_applied by hooks/subcomponents_):\n\n- **`Menu.Target`**: shared model `id`, `aria-haspopup=\"true\"`,\n `aria-expanded={visibility === 'visible'}`; <kbd>ArrowDown</kbd> / <kbd>ArrowUp</kbd> also open\n the menu\n- **`Menu.List`**: `role=\"menu\"`, `aria-labelledby` referencing the target `id`,\n `aria-orientation` from the model\n- **`Menu.Item`**: `role=\"menuitem\"`, roving `tabIndex` (`0` on the focused item, `-1` on others);\n in default `mode=\"single\"`, activating an item selects it and closes the menu (and any open parent\n menus)\n- **`Menu.Group`**: `role=\"group\"` with `aria-labelledby` referencing **`Menu.Group.Heading`** (or\n the heading created from the `title` prop)\n- **`Menu.Submenu.TargetItem`**: `role=\"menuitem\"`, `aria-haspopup=\"true\"`, `aria-expanded` for the\n submenu\n\n**Implementation note on open focus:** Menu does **not** compose `useInitialFocus`. In default\n`mode=\"single\"`, **`useMenuItemFocus`** moves focus to the first menu item when the menu opens. Do\nnot generate **`initialFocusRef`** \u2014 it is not wired on Menu.\n\n**Implementation note on `mode=\"multiple\"`:** `useMenuModel` supports `mode=\"multiple\"`, which keeps\nthe menu open and toggles selection in model state. **`Menu.Item`** uses `role=\"menuitem\"`, which\ndoes not support **`aria-selected`**, so selected state is not exposed to assistive technology. Do\nnot generate **`mode=\"multiple\"`** with **`Menu.Item`** for an accessible multi-select UI\u2014use\n[**Select**](https://workday.github.io/canvas-kit/?path=/docs/components-inputs-select--docs),\n**MultiSelect**, or\n[**Combobox**](https://workday.github.io/canvas-kit/?path=/docs/features-combobox--docs) instead.\n\n**Keyboard** (_trigger is `Menu.Target`, default `SecondaryButton`; list uses vertical orientation by\ndefault_):\n\n- <kbd>Enter</kbd> / <kbd>Space</kbd> on the trigger opens the menu (button activation)\n- <kbd>ArrowDown</kbd> / <kbd>ArrowUp</kbd> on the trigger also opens the menu\n- On open, focus moves to the first menu item by default\n- <kbd>ArrowDown</kbd> / <kbd>ArrowUp</kbd> moves the roving tabindex between items\n- <kbd>Home</kbd> / <kbd>End</kbd> moves to the first or last item\n- <kbd>Enter</kbd> / <kbd>Space</kbd> on an item activates it and closes the menu (default\n `mode=\"single\"`)\n- <kbd>Escape</kbd> closes the menu and returns focus per `useReturnFocus`\n- <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> closes the menu via `useFocusRedirect`\n- <kbd>ArrowRight</kbd> / <kbd>Enter</kbd> / <kbd>Space</kbd> on **`Menu.Submenu.TargetItem`** opens\n the submenu\n- <kbd>ArrowLeft</kbd> on a submenu item closes it (for LTR languages)\n\n**Screen reader expectations** (_when built-in behaviors are used as intended_):\n\n- On the trigger: name, button role, menu popup is available, and expanded/collapsed state\n (for example: \"Open Menu, button, menu popup, collapsed\")\n- On open: menu role (labeled by the trigger), focused item name, menuitem role, and often position\n in set (for example: \"Open Menu, menu, First Item, menu item, 1 of 4\")\n- While navigating: each focused item\u2019s name and role; group labels when entering a\n **`Menu.Group`**; submenu items announce has-popup / expanded state (for example: \"More Actions, menu item, has submenu, collapsed, 3 of 4.\")\n- Disabled items with **`aria-disabled`** remain discoverable but not selectable\n\n### Accessibility Requirements\n\nRequired in application code for an accessible Menu. Hoist **`useMenuModel`** when you need return\nfocus overrides or dynamic `items`. Rows marked _(conditional)_ apply only when the situation\nmatches\u2014otherwise omit.\n\n**If no design spec is provided:** use **`Menu.Target`** + **`Menu.Item`** (not **`Menu.Option`**),\ndefault `mode=\"single\"`, default open focus on the first menu item, and omit **`returnFocusRef`**,\n**`initialFocusRef`**, and manual ARIA on Target/List/Item.\n\n**Focus management \u2014 defaults and developer prompts:** Canvas Kit handles open and close focus for\nthe default menu button pattern. **State the default to the developer first.** Only set\n**`returnFocusRef`** after the developer (or an explicit design spec) chooses a non-default return\ntarget. **Do not generate `returnFocusRef` or `initialFocusRef` by default.**\n\n| When | Default behavior | Ask the developer before overriding |\n| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Menu **opens** | Focus moves to the **first menu item** by default via item focus hooks\u2014not `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. |\n| 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`**.) |\n\n**Custom targets** _(conditional)_: Apply when using a custom **`as`** component on\n**`Menu.Target`**. **`Menu.Target`** adds **`onClick`**, keyboard openers, and **`ref`**. Custom\ntargets must forward **`ref`** and props to a **keyboard-focusable** element (prefer a native\n**`<button>`** or **`as={SecondaryButton}`**). Wrap the component in **`React.forwardRef`** when it\ndoes not forward refs by default.\n\n| Requirement | How to satisfy |\n| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| 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. |\n| Menu list composition | **`Menu.Popper` \u2192 `Menu.Card` \u2192 `Menu.List`** with **`Menu.Item`** children (or dynamic `items` + render prop on **`Menu.List`**). |\n| Disabled items _(conditional)_ | **`aria-disabled`** on **`Menu.Item`** so the item stays in the roving tabindex / screen reader sequence. |\n| Stable item ids _(conditional)_ | **`data-id`** on items when using **`onSelect`**, dynamic lists, or nested menus that need stable selection ids. |\n| 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. |\n| Groups _(conditional)_ | **`Menu.Group`** with **`title`** or **`Menu.Group.Heading`** so `role=\"group\"` is labeled. Group headers are not keyboard-selectable. |\n| Nested menus _(conditional)_ | **`Menu.Submenu`** with **`Menu.Submenu.TargetItem`** plus **`Popper` / `Card` / `List` / `Item`**. Do not manually set submenu `aria-haspopup` / `aria-expanded`. |\n| Context menu trigger _(conditional)_ | **`Menu.TargetContext`** instead of **`Menu.Target`**. OS/browser support for `contextmenu` / Shift+F10 varies\u2014provide an alternate open path for critical actions when required. |\n| 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). |\n\n**Summary for code generation:**\n\n- **REQUIRED:** keyboard-operable named **`Menu.Target`**, **`Menu.Popper` \u2192 `Menu.Card` \u2192\n `Menu.List`**, **`Menu.Item`** (or dynamic list items)\n- **CONDITIONAL:** **`aria-disabled`**, **`data-id`**, **`data-text`**, groups, submenus,\n **`Menu.TargetContext`**, **`returnFocusRef`**, **`forwardRef`** on custom targets\n\n### Anti-Patterns\n\nDo **not** generate code that does the following (see **Accessibility Requirements** above for what\nto supply instead):\n\n- Manually set `role=\"menu\"`, `role=\"menuitem\"`, `aria-labelledby`, `aria-orientation`,\n `aria-haspopup`, `aria-expanded`, shared `id`, or roving `tabIndex` on **`Menu.Target`**,\n **`Menu.List`**, **`Menu.Item`**, or **`Menu.Submenu.TargetItem`** \u2014 Canvas Kit hooks wire these\n- Omit **`Menu.Popper`**, or render **`Menu.Card` / `Menu.List`** outside the Menu composition\n- Use **`Menu.Option`**, `role=\"listbox\"`, or **`mode=\"multiple\"`** with **`Menu.Item`** for\n selectable or multi-select UIs \u2014 use **Select**, **MultiSelect**, or **Combobox** instead (see\n **Implementation note on `mode=\"multiple\"`** in Built-in Behaviors)\n- Set **`initialFocusRef`** \u2014 Menu does not compose **`useInitialFocus`**, so this prop has no\n effect on open focus (see **Built-in Behaviors**)\n- Set **`returnFocusRef`** by default \u2014 state the default return-to-target behavior first and ask\n before overriding\n- Use native **`disabled`** (or deprecated **`isDisabled`**) instead of **`aria-disabled`** when\n the item should remain discoverable\n- Skip **`data-text`** on static items whose accessible/filter text is not plain string children\n- Use a custom **`Menu.Target`** **`as`** component that does not forward **`ref`** to a focusable\n element \u2014 use **`React.forwardRef`** or a Canvas Kit button component instead\n- Treat Menu like a **Modal** / **Dialog** (focus trap, `role=\"dialog\"`, inert page) \u2014 Menu is a\n menu button popup with roving tabindex inside **`role=\"menu\"`**\n\n## Component API\n\n## Specifications\n\n",
|
|
463
|
+
accessibilityProse: '## Accessibility\n\n`Menu` follows the\n[Menu Button Pattern | APG | WAI | W3C](https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/), which\nhas two parts with different accessibility jobs.\n\n**Menu button** (`Menu.Target`): a focusable control that opens and closes the menu. It exposes\npopup presence and expanded/collapsed state (`aria-haspopup`, `aria-expanded`), and receives focus\nagain when the menu is dismissed.\n\n**Menu popup** (`Menu.List` and its items): the floating action list. It uses `role="menu"` /\n`role="menuitem"`, is labeled by the button, and manages focus inside the list with **roving\ntabindex** so users can move between items and activate one.\n\nUse **Menu** for action lists opened from a control. Prefer\n[**Select**](https://workday.github.io/canvas-kit/?path=/docs/components-inputs-select--docs) or\n[**Combobox**](https://workday.github.io/canvas-kit/?path=/docs/features-combobox--docs)\nwhen choosing a value from options (`Menu.Option` / `listbox` patterns are composed there\u2014do not use\n`Menu.Option` alone for a standard menu button). Prefer\n[**Modal**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs) or\n[**Dialog**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-dialog--docs) for\ntask dialogs, not menus.\n\n### Minimum Accessible Structure\n\nThe following matches the [Basic Example](#basic-example): a keyboard-operable **`Menu.Target`**,\nportaled **`Menu.Popper` \u2192 `Menu.Card` \u2192 `Menu.List`**, and **`Menu.Item`** children. On open, focus\nmoves to the first menu item by default.\n\n```tsx\n\n<Menu>\n <Menu.Target>Open Menu</Menu.Target>\n <Menu.Popper>\n <Menu.Card>\n <Menu.List>\n <Menu.Item>First Item</Menu.Item>\n <Menu.Item>Second Item</Menu.Item>\n </Menu.List>\n </Menu.Card>\n </Menu.Popper>\n</Menu>;\n```\n\nProvide a clearly named **`Menu.Target`** (visible text, or an icon-only control with\n**`Tooltip`**, or a translated **`aria-label`** if you are not using **`Tooltip`**). \nUse **`aria-disabled`** on items that should stay in the keyboard sequence but\nnot activate \u2014 do not use the native `disabled` attribute for disabled menu items.\n\n### Built-in Behaviors\n\nCanvas Kit applies these automatically via `useMenuModel` (list + popup) and Menu subcomponents.\n**Do not duplicate them** in consuming code.\n\n**Popup behaviors** (_composed on the default model_):\n\n- `useAlwaysCloseOnOutsideClick` \u2014 pointer interaction outside closes the menu\n- `useCloseOnEscape` \u2014 <kbd>Escape</kbd> closes the menu\n- `useReturnFocus` (_on `Menu.List`_) \u2014 returns focus to **`Menu.Target`** (or configured return\n target) when the menu closes\n- `useFocusRedirect` (_on `Menu.List`_) \u2014 <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> from\n inside the menu closes it and moves focus to the next or previous focusable element on the page\n (not a focus trap)\n\n**ARIA and DOM** (_applied by hooks/subcomponents_):\n\n- **`Menu.Target`**: shared model `id`, `aria-haspopup="true"`,\n `aria-expanded={visibility === \'visible\'}`; <kbd>ArrowDown</kbd> / <kbd>ArrowUp</kbd> also open\n the menu\n- **`Menu.List`**: `role="menu"`, `aria-labelledby` referencing the target `id`,\n `aria-orientation` from the model\n- **`Menu.Item`**: `role="menuitem"`, roving `tabIndex` (`0` on the focused item, `-1` on others);\n in default `mode="single"`, activating an item selects it and closes the menu (and any open parent\n menus)\n- **`Menu.Group`**: `role="group"` with `aria-labelledby` referencing **`Menu.Group.Heading`** (or\n the heading created from the `title` prop)\n- **`Menu.Submenu.TargetItem`**: `role="menuitem"`, `aria-haspopup="true"`, `aria-expanded` for the\n submenu\n\n**Implementation note on open focus:** Menu does **not** compose `useInitialFocus`. In default\n`mode="single"`, **`useMenuItemFocus`** moves focus to the first menu item when the menu opens. Do\nnot generate **`initialFocusRef`** \u2014 it is not wired on Menu.\n\n**Implementation note on `mode="multiple"`:** `useMenuModel` supports `mode="multiple"`, which keeps\nthe menu open and toggles selection in model state. **`Menu.Item`** uses `role="menuitem"`, which\ndoes not support **`aria-selected`**, so selected state is not exposed to assistive technology. Do\nnot generate **`mode="multiple"`** with **`Menu.Item`** for an accessible multi-select UI\u2014use\n[**Select**](https://workday.github.io/canvas-kit/?path=/docs/components-inputs-select--docs),\n**MultiSelect**, or\n[**Combobox**](https://workday.github.io/canvas-kit/?path=/docs/features-combobox--docs) instead.\n\n**Keyboard** (_trigger is `Menu.Target`, default `SecondaryButton`; list uses vertical orientation by\ndefault_):\n\n- <kbd>Enter</kbd> / <kbd>Space</kbd> on the trigger opens the menu (button activation)\n- <kbd>ArrowDown</kbd> / <kbd>ArrowUp</kbd> on the trigger also opens the menu\n- On open, focus moves to the first menu item by default\n- <kbd>ArrowDown</kbd> / <kbd>ArrowUp</kbd> moves the roving tabindex between items\n- <kbd>Home</kbd> / <kbd>End</kbd> moves to the first or last item\n- <kbd>Enter</kbd> / <kbd>Space</kbd> on an item activates it and closes the menu (default\n `mode="single"`)\n- <kbd>Escape</kbd> closes the menu and returns focus per `useReturnFocus`\n- <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> closes the menu via `useFocusRedirect`\n- <kbd>ArrowRight</kbd> / <kbd>Enter</kbd> / <kbd>Space</kbd> on **`Menu.Submenu.TargetItem`** opens\n the submenu\n- <kbd>ArrowLeft</kbd> on a submenu item closes it (for LTR languages)\n\n**Screen reader expectations** (_when built-in behaviors are used as intended_):\n\n- On the trigger: name, button role, menu popup is available, and expanded/collapsed state\n (for example: "Open Menu, button, menu popup, collapsed")\n- On open: menu role (labeled by the trigger), focused item name, menuitem role, and often position\n in set (for example: "Open Menu, menu, First Item, menu item, 1 of 4")\n- While navigating: each focused item\u2019s name and role; group labels when entering a\n **`Menu.Group`**; submenu items announce has-popup / expanded state (for example: "More Actions, menu item, has submenu, collapsed, 3 of 4.")\n- Disabled items with **`aria-disabled`** remain discoverable but not selectable\n\n### Accessibility Requirements\n\nRequired in application code for an accessible Menu. Hoist **`useMenuModel`** when you need return\nfocus overrides or dynamic `items`. Rows marked _(conditional)_ apply only when the situation\nmatches\u2014otherwise omit.\n\n**If no design spec is provided:** use **`Menu.Target`** + **`Menu.Item`** (not **`Menu.Option`**),\ndefault `mode="single"`, default open focus on the first menu item, and omit **`returnFocusRef`**,\n**`initialFocusRef`**, and manual ARIA on Target/List/Item.\n\n**Focus management \u2014 defaults and developer prompts:** Canvas Kit handles open and close focus for\nthe default menu button pattern. **State the default to the developer first.** Only set\n**`returnFocusRef`** after the developer (or an explicit design spec) chooses a non-default return\ntarget. **Do not generate `returnFocusRef` or `initialFocusRef` by default.**\n\n| When | Default behavior | Ask the developer before overriding |\n| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Menu **opens** | Focus moves to the **first menu item** by default via item focus hooks\u2014not `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. |\n| 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`**.) |\n\n**Custom targets** _(conditional)_: Apply when using a custom **`as`** component on\n**`Menu.Target`**. **`Menu.Target`** adds **`onClick`**, keyboard openers, and **`ref`**. Custom\ntargets must forward **`ref`** and props to a **keyboard-focusable** element (prefer a native\n**`<button>`** or **`as={SecondaryButton}`**). Wrap the component in **`React.forwardRef`** when it\ndoes not forward refs by default.\n\n| Requirement | How to satisfy |\n| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| 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. |\n| Menu list composition | **`Menu.Popper` \u2192 `Menu.Card` \u2192 `Menu.List`** with **`Menu.Item`** children (or dynamic `items` + render prop on **`Menu.List`**). |\n| Disabled items _(conditional)_ | **`aria-disabled`** on **`Menu.Item`** so the item stays in the roving tabindex / screen reader sequence. |\n| Stable item ids _(conditional)_ | **`data-id`** on items when using **`onSelect`**, dynamic lists, or nested menus that need stable selection ids. |\n| 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. |\n| Groups _(conditional)_ | **`Menu.Group`** with **`title`** or **`Menu.Group.Heading`** so `role="group"` is labeled. Group headers are not keyboard-selectable. |\n| Nested menus _(conditional)_ | **`Menu.Submenu`** with **`Menu.Submenu.TargetItem`** plus **`Popper` / `Card` / `List` / `Item`**. Do not manually set submenu `aria-haspopup` / `aria-expanded`. |\n| Context menu trigger _(conditional)_ | **`Menu.TargetContext`** instead of **`Menu.Target`**. OS/browser support for `contextmenu` / Shift+F10 varies\u2014provide an alternate open path for critical actions when required. |\n| 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). |\n\n**Summary for code generation:**\n\n- **REQUIRED:** keyboard-operable named **`Menu.Target`**, **`Menu.Popper` \u2192 `Menu.Card` \u2192\n `Menu.List`**, **`Menu.Item`** (or dynamic list items)\n- **CONDITIONAL:** **`aria-disabled`**, **`data-id`**, **`data-text`**, groups, submenus,\n **`Menu.TargetContext`**, **`returnFocusRef`**, **`forwardRef`** on custom targets\n\n### Anti-Patterns\n\nDo **not** generate code that does the following (see **Accessibility Requirements** above for what\nto supply instead):\n\n- Manually set `role="menu"`, `role="menuitem"`, `aria-labelledby`, `aria-orientation`,\n `aria-haspopup`, `aria-expanded`, shared `id`, or roving `tabIndex` on **`Menu.Target`**,\n **`Menu.List`**, **`Menu.Item`**, or **`Menu.Submenu.TargetItem`** \u2014 Canvas Kit hooks wire these\n- Omit **`Menu.Popper`**, or render **`Menu.Card` / `Menu.List`** outside the Menu composition\n- Use **`Menu.Option`**, `role="listbox"`, or **`mode="multiple"`** with **`Menu.Item`** for\n selectable or multi-select UIs \u2014 use **Select**, **MultiSelect**, or **Combobox** instead (see\n **Implementation note on `mode="multiple"`** in Built-in Behaviors)\n- Set **`initialFocusRef`** \u2014 Menu does not compose **`useInitialFocus`**, so this prop has no\n effect on open focus (see **Built-in Behaviors**)\n- Set **`returnFocusRef`** by default \u2014 state the default return-to-target behavior first and ask\n before overriding\n- Use native **`disabled`** (or deprecated **`isDisabled`**) instead of **`aria-disabled`** when\n the item should remain discoverable\n- Skip **`data-text`** on static items whose accessible/filter text is not plain string children\n- Use a custom **`Menu.Target`** **`as`** component that does not forward **`ref`** to a focusable\n element \u2014 use **`React.forwardRef`** or a Canvas Kit button component instead\n- Treat Menu like a **Modal** / **Dialog** (focus trap, `role="dialog"`, inert page) \u2014 Menu is a\n menu button popup with roving tabindex inside **`role="menu"`**'
|
|
464
464
|
},
|
|
465
465
|
"loading-dots": {
|
|
466
466
|
title: "Components/Indicators/Loading Dots",
|
|
@@ -501,8 +501,8 @@ var stories_config_default = {
|
|
|
501
501
|
title: "Components/Inputs/Form Field",
|
|
502
502
|
storybookUrl: "https://workday.github.io/canvas-kit/?path=/docs/components-inputs-form-field--docs",
|
|
503
503
|
mdxPath: "modules/react/form-field/stories/FormField.mdx",
|
|
504
|
-
mdxProse: "# Canvas Kit Form Field\n\nFormField allows users to wrap input components to make them accessible. You can customize the field\nby passing in `TextInput`, `Select`, `RadioGroup` and other form elements to `FormField.Input`.\n\n## Installation\n\n```sh\nyarn add @workday/canvas-kit-react\n```\n\n## Usage\n\n### Basic\n\nForm Field should be used in tandem with most Canvas Kit input components to ensure they meet\naccessibility standards. The orientation of the label by default is `vertical`.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const Basic = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <Flex>\n <FormField>\n <FormField.Label>First Name</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextInput} value={value} onChange={handleChange} />\n </FormField.Field>\n </FormField>\n </Flex>\n );\n};\n```\n\n### Error States\n\nSet the `error` prop of the Form Field or define it in the model to indicate it has an error.\n`error` accepts the following values:\n\n`\"error\" | \"caution\" | undefined`\n\n### Caution\n\nUse the caution state when a value is valid but there is additional information.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const Caution = () => {\n const [value, setValue] = React.useState('hi');\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <Flex>\n <FormField error=\"caution\">\n <FormField.Label>Create Password</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextInput} type=\"password\" value={value} onChange={handleChange} />\n <FormField.Hint>\n Alert: Password strength is weak, using more characters is recommended.\n </FormField.Hint>\n </FormField.Field>\n </FormField>\n </Flex>\n );\n};\n```\n\n> **Accessibility Note**: Caution state **will not** include the `aria-invalid` attribute on the\n> input for screen readers. Use error states when values are not valid.\n\n### Error\n\nUse the error state when the value is no longer valid.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const Error = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <Flex>\n <FormField error=\"error\">\n <FormField.Label>Password</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextInput} type=\"password\" value={value} onChange={handleChange} />\n <FormField.Hint>Error: Must Contain a number and a capital letter</FormField.Hint>\n </FormField.Field>\n </FormField>\n </Flex>\n );\n};\n```\n\n> **Accessibility Note**: Error states include visual color changes to the input border and\n> **require** supplemental \"error\" text for colorblind users to distinguish between fields in an\n> error state from fields with standard hint text. Read more about\n> [Failure of Success Criterion 1.4.1 due to identifying required or error fields using color differences only](https://www.w3.org/WAI/WCAG22/Techniques/failures/F81)\n\n### Hint\n\nUse `FormField.Hint` to display a short message below the input component and `FormField.Field` to\nensure proper alignment.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const Hint = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <Flex>\n <FormField orientation=\"horizontalStart\">\n <FormField.Label>First Name</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextInput} value={value} onChange={handleChange} />\n <FormField.Hint>Cannot contain numbers</FormField.Hint>\n </FormField.Field>\n </FormField>\n </Flex>\n );\n};\n```\n\n> **Accessibility Note**: Hints are automatically associated to the input field with the\n> `aria-describedby` attribute. This ensures that screen readers can automatically announce the hint\n> text to users when the input field is focused.\n\n### Disabled\n\nSet the `disabled` prop of `FormField.Input` to prevent users from interacting with it.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const Disabled = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <Flex>\n <FormField>\n <FormField.Label>Email</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextInput} value={value} disabled onChange={handleChange} />\n </FormField.Field>\n </FormField>\n </Flex>\n );\n};\n```\n\n> **Accessibility Note**: Disabled form elements are exempt from\n> [WCAG minimum contrast guidelines](https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html).\n> Despite this exemption, disabled fields are more difficult for low vision and colorblind users to\n> perceive and may harm the usability of the form. Consider using text elements instead, or\n> read-only fields if users cannot modify data.\n\n### Label Position\n\nSet the `orientation` prop of the Form Field to designate the position of the label relative to the\ninput component. By default, the orientation will be set to `vertical`. If you want your label to be\nhorizontal, you have two options: `horizontalStart` and `horizontalEnd`.\n\nIf you want the position of the label at the start of the container, set orientation prop to\n`horizontalStart`.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\nimport {createStyles} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst formStyles = createStyles({\n display: 'flex',\n gap: system.gap.sm,\n flexDirection: 'column',\n});\n\nexport const LabelPositionHorizontalStart = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <form className={formStyles}>\n <FormField orientation=\"horizontalStart\">\n <FormField.Label>Email</FormField.Label>\n <FormField.Input as={TextInput} value={value} onChange={handleChange} />\n </FormField>\n <FormField orientation=\"horizontalStart\">\n <FormField.Label>Password</FormField.Label>\n <FormField.Input as={TextInput} type=\"password\" />\n </FormField>\n </form>\n );\n};\n```\n\nIf you want the position of the label at the end of the container, set orientation prop to\n`horizontalEnd`.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\nimport {createStyles} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst formStyles = createStyles({\n display: 'flex',\n gap: system.gap.sm,\n flexDirection: 'column',\n});\n\nexport const LabelPositionHorizontalEnd = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <form className={formStyles}>\n <FormField orientation=\"horizontalEnd\">\n <FormField.Label>Email</FormField.Label>\n <FormField.Input as={TextInput} value={value} onChange={handleChange} />\n </FormField>\n <FormField orientation=\"horizontalEnd\">\n <FormField.Label>Password</FormField.Label>\n <FormField.Input as={TextInput} type=\"password\" />\n </FormField>\n </form>\n );\n};\n```\n\n### Grow\n\nSet the `grow` prop of the Form Field to `true` to configure it (including the wrapped input\ncomponent) to expand to the width of its container.\n\n**Note: This Prop is deprecated and will be removed in a future major version.**\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const Grow = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <Flex>\n <FormField grow>\n <FormField.Label>First Name</FormField.Label>\n <FormField.Input as={TextInput} value={value} onChange={handleChange} />\n </FormField>\n </Flex>\n );\n};\n```\n\n### Ref Forwarding\n\nIf you need full customization you can use the `FormField` behavior hooks to build your own\nsolution. It is also easy it work with custom components or third party libraries and get the CKR\naccessibility guarantees by using the `as` prop.\n```tsx\nimport React from 'react';\n\nimport {SecondaryButton} from '@workday/canvas-kit-react/button';\nimport {changeFocus} from '@workday/canvas-kit-react/common';\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\nimport {createStyles} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst parentContainerStyles = createStyles({\n gap: system.gap.xs,\n alignItems: 'flex-start',\n flexDirection: 'column',\n});\n\nexport const RefForwarding = () => {\n const [value, setValue] = React.useState('');\n const ref = React.useRef(null);\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n const handleClick = () => {\n changeFocus(ref.current);\n };\n\n return (\n <Flex cs={parentContainerStyles}>\n <FormField>\n <FormField.Label>Email</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextInput} onChange={handleChange} value={value} ref={ref} />\n </FormField.Field>\n </FormField>\n <SecondaryButton onClick={handleClick}>Focus Text Input</SecondaryButton>\n </Flex>\n );\n};\n```\n\n### Required\n\nSet the `isRequired` prop of the Form Field to `true` to indicate that the field is required. Labels\nfor required fields are suffixed by a red asterisk.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const Required = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <FormField isRequired={true}>\n <FormField.Label>Email</FormField.Label>\n <FormField.Field>\n <FormField.Input\n as={TextInput}\n placeholder=\"your@gmail.com\"\n onChange={handleChange}\n value={value}\n />\n </FormField.Field>\n </FormField>\n );\n};\n```\n\n> **Accessibility Note**: The HTML `required` attribute will be added to the input field and\n> announced by screen readers. Consider adding a note at the top of your form indicating that fields\n> marked with an asterisk (\\*) are required. This provides context for all users.\n\n### Grouped Inputs\n\nUse `FormFieldGroup` when you have a group of inputs that need to be associated to one another, like\n`RadioGroup` or a group of `Checkbox`'s. `FormFieldGroup` renders a `fieldset` element and\n`FormFieldGroup.Label` renders a `legend` element. These elements will allow screen readers to\nautomatically announce the legend's context when focusing on the inputs in the group.\n\n`FormFieldGroup` supports the same props of `FormField`:\n\n- `error`: `\"caution\" | \"error\"` Defines the error around the whole group of inputs.\n- `orientation`: `\"horizontal\" | \"vertical\"` Defines the legend placement.\n- `isRequired`: `true` Defines if a group like RadioGroup is required.\n```tsx\nimport React from 'react';\n\nimport {RadioGroup} from '@workday/canvas-kit-preview-react/radio';\nimport {Banner} from '@workday/canvas-kit-react/banner';\nimport {PrimaryButton, SecondaryButton} from '@workday/canvas-kit-react/button';\nimport {Checkbox} from '@workday/canvas-kit-react/checkbox';\nimport {AriaLiveRegion} from '@workday/canvas-kit-react/common';\nimport {FormFieldGroup} from '@workday/canvas-kit-react/form-field';\nimport {createStyles, px2rem} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst formStyles = createStyles({\n margin: `0 ${px2rem(12)}`,\n});\n\nconst formButtonStyles = createStyles({\n display: 'inline-flex',\n gap: system.gap.sm,\n});\n\nconst toppings = [\n {\n id: 1,\n label: 'Pepperoni',\n checked: false,\n },\n {\n id: 2,\n label: 'Cheese',\n checked: false,\n },\n {\n id: 3,\n label: 'Pineapple',\n checked: false,\n },\n {\n id: 4,\n label: 'Mushrooms',\n checked: false,\n },\n];\n\nconst bannerStyles = createStyles({\n position: 'absolute',\n right: 0,\n});\n\nexport const GroupedInputs = () => {\n const [toppingsState, setToppingsState] = React.useState(toppings);\n const [error, setError] = React.useState(undefined);\n const [radioError, setRadioError] = React.useState(undefined);\n const [showSuccess, setShowSuccess] = React.useState(false);\n\n const [value, setValue] = React.useState<string>('');\n const [formData, setFormData] = React.useState({\n toppings: [],\n crust: '',\n });\n const handleCheckboxCheck = id => {\n if (error) {\n setError(undefined);\n }\n setToppingsState(\n toppingsState.map(item => (item.id === id ? {...item, checked: !item.checked} : item))\n );\n };\n\n const handleRadioChange = (e: React.ChangeEvent) => {\n if (radioError) {\n setRadioError(undefined);\n }\n const target = e.currentTarget;\n if (target instanceof HTMLInputElement) {\n setValue(target.value);\n }\n };\n\n const handleSubmit = e => {\n e.preventDefault();\n const radioError = !value && toppingsState.some(item => !item.checked) ? 'error' : undefined;\n const error = toppingsState.every(item => !item.checked) ? 'error' : undefined;\n\n setRadioError(radioError);\n setError(error);\n if (!error && !radioError && toppingsState.some(item => item.checked) && value) {\n setShowSuccess(true);\n }\n setFormData({\n toppings: toppingsState,\n crust: value,\n });\n };\n\n React.useEffect(() => {\n const timeout = setTimeout(() => {\n if (showSuccess) {\n setShowSuccess(false);\n }\n }, 3000);\n\n return () => clearTimeout(timeout);\n }, [showSuccess]);\n\n const handleReset = () => {\n setFormData({toppings: [], crust: ''});\n setError(undefined);\n setValue('');\n setRadioError('');\n setShowSuccess(false);\n setToppingsState(\n toppingsState.map(item => {\n return {...item, checked: false};\n })\n );\n };\n\n return (\n <div>\n <h3>Choose your pizza options</h3>\n <AriaLiveRegion role=\"alert\">\n <div style={{display: 'flex', gap: '40px'}}>\n {error || radioError ? (\n <Banner isSticky hasError className={bannerStyles}>\n <Banner.Label>\n {error && radioError\n ? 'At least one topping and crust selection is required'\n : error\n ? 'You must choose at least one topping'\n : radioError\n ? 'You must choose a crust'\n : ''}\n </Banner.Label>\n </Banner>\n ) : null}\n {showSuccess && (\n <Banner isSticky className={bannerStyles}>\n <Banner.Label>You've successfully submitted your pizza options.</Banner.Label>\n </Banner>\n )}\n </div>\n </AriaLiveRegion>\n\n <form className={formStyles} onSubmit={handleSubmit}>\n <FormFieldGroup error={error} isRequired>\n <FormFieldGroup.Label>Choose Your Toppings</FormFieldGroup.Label>\n <FormFieldGroup.List>\n {toppingsState.map(item => {\n return (\n <FormFieldGroup.Input\n key={item.id}\n onChange={() => handleCheckboxCheck(item.id)}\n checked={item.checked}\n value={item.label}\n as={Checkbox}\n disabled={item.label === 'Pineapple' ? true : undefined}\n label={item.label}\n />\n );\n })}\n </FormFieldGroup.List>\n <FormFieldGroup.Hint>\n {error === 'error' && 'Error: You must choose one topping'}\n </FormFieldGroup.Hint>\n </FormFieldGroup>\n <FormFieldGroup error={radioError} isRequired>\n <FormFieldGroup.Label>Choose Your Crust</FormFieldGroup.Label>\n <FormFieldGroup.Field>\n <FormFieldGroup.List\n as={RadioGroup}\n onChange={handleRadioChange}\n value={value}\n name=\"crust\"\n >\n <FormFieldGroup.Input as={RadioGroup.RadioButton} value=\"thin-crust\">\n Thin Crust\n </FormFieldGroup.Input>\n <FormFieldGroup.Input as={RadioGroup.RadioButton} value=\"hand-tossed\">\n Hand Tossed\n </FormFieldGroup.Input>\n <FormFieldGroup.Input as={RadioGroup.RadioButton} value=\"deep-dish\">\n Deep Dish\n </FormFieldGroup.Input>\n <FormFieldGroup.Input as={RadioGroup.RadioButton} value=\"cauliflower\">\n Cauliflower\n </FormFieldGroup.Input>\n </FormFieldGroup.List>\n <FormFieldGroup.Hint>\n {radioError === 'error' ? 'Error: You must choose a crust' : null}\n </FormFieldGroup.Hint>\n </FormFieldGroup.Field>\n </FormFieldGroup>\n <div className={formButtonStyles}>\n <PrimaryButton type=\"submit\">Submit Your Choices</PrimaryButton>\n <SecondaryButton onClick={() => handleReset()}>Reset Form</SecondaryButton>\n </div>\n </form>\n <div>\n <div>\n Selected Toppings:{' '}\n {!error && formData.toppings.map(item => (item.checked ? `${item.label} ` : null))}\n </div>\n <div>Selected Crust: {formData.crust}</div>\n </div>\n </div>\n );\n};\n```\n\n> **Accessibility Note**: In addition to radio button and checkbox groups, `FormFieldGroup` can be\n> useful in any situation where the form needs to have multiple sets of identical input fields. For\n> example, a form with identical fields for a Shipping address and a Billing address. The legend\n> provides critical context for screen reader users in these situations.\n\n### Custom\n\nIf you need full customization you can use the `FormField` behavior hooks to build your own\nsolution. It is also easy it work with custom components or third party libraries and get the CKR\naccessibility guarantees by using the `as` prop.\n```tsx\nimport React from 'react';\n\nimport {useModelContext} from '@workday/canvas-kit-react/common';\nimport {\n formFieldStencil,\n useFormFieldHint,\n useFormFieldInput,\n useFormFieldLabel,\n useFormFieldModel,\n} from '@workday/canvas-kit-react/form-field';\nimport {Flex} from '@workday/canvas-kit-react/layout';\n\nconst Label = ({model, children}) => {\n const localModel = useModelContext(useFormFieldModel.Context, model);\n const props = useFormFieldLabel(localModel);\n\n return (\n <label {...props}>\n {children}\n {model.state.isRequired ? '*' : ''}\n </label>\n );\n};\n\nconst Hint = ({model, children}) => {\n const localModel = useModelContext(useFormFieldModel.Context, model);\n const props = useFormFieldHint(localModel);\n\n return <span {...props}>{children}</span>;\n};\n\nconst Input = ({model, ...elementProps}) => {\n const localModel = useModelContext(useFormFieldModel.Context, model);\n const props = useFormFieldInput(localModel, elementProps);\n\n return <input type=\"text\" required={model.state.isRequired ? true : false} {...props} />;\n};\n\nexport const Custom = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n const model = useFormFieldModel({isRequired: true});\n\n return (\n <Flex cs={formFieldStencil({orientation: 'horizontalStart'})}>\n <Label model={model}>My Custom Field</Label>\n <Input model={model} value={value} onChange={handleChange} />\n <Hint model={model}>You can be anything</Hint>\n </Flex>\n );\n};\n```\n\n### Custom id\n\nForm Field will automatically generate an HTML `id` for its input element to link it to the\ncorreponding label. Alternatively, you may set the `id` prop of the Form Field to specify a custom\n`id` for the input element. The `id` will be appended by `input-${your-unique-id}`.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const CustomId = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <Flex>\n <FormField id=\"first-name\">\n <FormField.Label>First Name</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextInput} value={value} onChange={handleChange} />\n </FormField.Field>\n </FormField>\n </Flex>\n );\n};\n```\n\n### All Fields\n\nForm Field should allow you to use it with all `inputs` including `Select`, `TextInput`, `Checkbox`,\n`TextArea`, `Switch`, and `RadioGroup`.\n```tsx\nimport {RadioGroup} from '@workday/canvas-kit-preview-react/radio';\nimport {Checkbox} from '@workday/canvas-kit-react/checkbox';\nimport {FormField, FormFieldGroup} from '@workday/canvas-kit-react/form-field';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {Select} from '@workday/canvas-kit-react/select';\nimport {Switch} from '@workday/canvas-kit-react/switch';\nimport {TextArea} from '@workday/canvas-kit-react/text-area';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\nimport {calc, createStyles} from '@workday/canvas-kit-styling';\nimport {base, system} from '@workday/canvas-tokens-web';\n\nconst parentContainerStyles = createStyles({\n flexDirection: 'column',\n gap: calc.subtract(system.gap.lg, system.gap.xs),\n padding: calc.subtract(base.size500, system.padding.xxs),\n borderRadius: system.shape.sm,\n});\n\nexport const AllFields = () => {\n return (\n <Flex cs={parentContainerStyles}>\n <FormField grow>\n <FormField.Label>First Name</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextInput} />\n </FormField.Field>\n </FormField>\n\n <FormField isRequired={true} error=\"caution\" grow>\n <FormField.Label>Email</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextInput} />\n <FormField.Hint>Hint text for your input</FormField.Hint>\n </FormField.Field>\n </FormField>\n <FormField grow>\n <FormField.Label>Text Area Label</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextArea} />\n </FormField.Field>\n </FormField>\n <FormField error=\"error\" grow>\n <FormField.Label>Choose a Crust</FormField.Label>\n <Select items={['Pizza', 'Cheeseburger', 'Fries']}>\n <FormField.Input as={Select.Input} />\n <Select.Popper>\n <Select.Card>\n <Select.List>{item => <Select.Item>{item}</Select.Item>}</Select.List>\n </Select.Card>\n </Select.Popper>\n </Select>\n </FormField>\n <FormField as=\"fieldset\" isRequired={true} error={'error'} orientation=\"horizontalStart\" grow>\n <FormField.Label as=\"legend\">Radio Group Legend</FormField.Label>\n <FormField.Field>\n <FormField.Input as={RadioGroup}>\n <RadioGroup.RadioButton value=\"deep-dish\">Deep dish</RadioGroup.RadioButton>\n <RadioGroup.RadioButton value=\"thin\">Thin</RadioGroup.RadioButton>\n <RadioGroup.RadioButton value=\"gluten-free\">Gluten free</RadioGroup.RadioButton>\n <RadioGroup.RadioButton value=\"cauliflower\">Cauliflower</RadioGroup.RadioButton>\n <RadioGroup.RadioButton value=\"butter\">\n Butter - the best thing to put on bread\n </RadioGroup.RadioButton>\n </FormField.Input>\n <FormField.Hint>Error Message</FormField.Hint>\n </FormField.Field>\n </FormField>\n <FormField as=\"fieldset\" grow>\n <FormField.Label as=\"legend\">Checkbox Legend</FormField.Label>\n <FormField.Input checked={true} as={Checkbox} label=\"Checkbox Label\" />\n <FormField.Input checked={false} as={Checkbox} label=\"Thin Crust\" />\n <FormField.Input checked={false} as={Checkbox} label=\"Extra Cheese\" />\n </FormField>\n <FormFieldGroup error=\"error\" orientation=\"horizontalStart\" grow>\n <FormFieldGroup.Label>Choose Your Crust</FormFieldGroup.Label>\n <FormFieldGroup.Field>\n <FormFieldGroup.List as={RadioGroup}>\n <FormFieldGroup.Input as={RadioGroup.RadioButton} value=\"thin-crust\">\n Thin Crust\n </FormFieldGroup.Input>\n <FormFieldGroup.Input as={RadioGroup.RadioButton} value=\"hand-tossed\">\n Hand Tossed\n </FormFieldGroup.Input>\n <FormFieldGroup.Input as={RadioGroup.RadioButton} value=\"deep-dish\">\n Deep Dish\n </FormFieldGroup.Input>\n <FormFieldGroup.Input as={RadioGroup.RadioButton} value=\"cauliflower\">\n Cauliflower\n </FormFieldGroup.Input>\n </FormFieldGroup.List>\n </FormFieldGroup.Field>\n </FormFieldGroup>\n <FormFieldGroup grow>\n <FormFieldGroup.Label>Checkbox Legend</FormFieldGroup.Label>\n <FormField.Field>\n <FormFieldGroup.List>\n <FormFieldGroup.Input checked={true} as={Checkbox} label=\"Checkbox Label\" />\n <FormFieldGroup.Input checked={false} as={Checkbox} label=\"Thin Crust\" />\n <FormFieldGroup.Input checked={false} as={Checkbox} label=\"Extra Cheese\" />\n </FormFieldGroup.List>\n </FormField.Field>\n </FormFieldGroup>\n\n <FormField orientation=\"horizontalStart\" grow>\n <FormField.Label>Switch Label</FormField.Label>\n <FormField.Field>\n <FormField.Input as={Switch} />\n </FormField.Field>\n </FormField>\n </Flex>\n );\n};\n```\n\n### Hidden Label\n\nIn cases where you want to hide the label while still meeting accessibility standards, you can add\n`isHidden` on the `<FormField.Label/>`. This prop will visually hide the label.\n```tsx\nimport React from 'react';\n\nimport {\n FormField,\n useFormFieldInput,\n useFormFieldModel,\n} from '@workday/canvas-kit-react/form-field';\nimport {SystemIcon} from '@workday/canvas-kit-react/icon';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {InputGroup, TextInput} from '@workday/canvas-kit-react/text-input';\nimport {searchIcon} from '@workday/canvas-system-icons-web';\n\n/**\n * Using `as={InputGroup}` on `FormField.Input` will break the label associations necessary for accessibility.\n * In this example, we've rendered `FormField.Field` as `InputGroup` and then hoisted the `id` of the input from the FormField model.\n * This allows us to set the `id` of the `InputGroup.Input` correctly for proper label association.\n */\n\nexport const HiddenLabel = () => {\n const [value, setValue] = React.useState('');\n const model = useFormFieldModel();\n const {id: formFieldInputId} = useFormFieldInput(model);\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <Flex>\n <FormField model={model}>\n <FormField.Label isHidden>Search</FormField.Label>\n <FormField.Field as={InputGroup}>\n <InputGroup.InnerStart>\n <SystemIcon icon={searchIcon} size=\"small\" />\n </InputGroup.InnerStart>\n <InputGroup.Input\n as={TextInput}\n id={formFieldInputId}\n onChange={handleChange}\n value={value}\n />\n </FormField.Field>\n </FormField>\n </Flex>\n );\n};\n```\n\n> **Accessibility Note**: Hidden labels are typically not recommended. In this example, a\n> universally recognizable icon like a magnifying glass signaling \"search\" may be a suitable\n> alternative to visible text labels.\n\n### Themed Errors\n\nYou can theme your error rings by wrapping an input in a `CanvasProvider` and defining\n`focusOutline` and `error` properties on the `theme`.\n\n### Custom Styles\n\nForm Field and its subcomponents support custom styling via the `cs` prop. For more information,\ncheck our\n[\"How To Customize Styles\"](https://workday.github.io/canvas-kit/?path=/docs/styling-guides-customizing-styles--docs).\n\n## Accessibility\n\n`FormField` provides essential accessibility features to ensure form inputs are properly labeled and\ndescribed for all users, including those using assistive technologies. This section covers both the\ntechnical implementation and best practices for creating accessible forms.\n\n### Label Association\n\nThe `FormField` adds a `for` attribute to the `FormField.Label` (`<label>` element) element that\nmatches the `id` attribute of the `FormField.Input` which is usually a `input` element. This both\nlabels the input for screen readers and other assistive technology as well as will focus on the\ninput when the user clicks on the label. If your form field input component is more complicated, the\n`FormField` will also add an `id` to the `FormField.Label` and an `aria-labelledby` to the\n`FormField.Input` component. You can then forward the `aria-labelledby` to whatever elements you\nneed for the proper accessibility.\n\nFor example, the DOM will look something like this:\n\n```html\n<div>\n <label id=\"label-abc\" for=\"input-abc\">First Name</label>\n <input id=\"input-abc\" aria-labelledby=\"label-abc\" />\n</div>\n```\n\nSome components, like `MultiSelect`, have an additional `role=listbox` element that also needs to\nlink to the `label` element. The resulting DOM will look something like:\n\n```html\n<div>\n <label id=\"label-abc\" for=\"input-abc\">States you've lived in</label>\n <input id=\"input-abc\" aria-labelledby=\"label-abc\" role=\"combobox\" ... />\n <ul role=\"listbox\" aria-labelledby=\"label-abc\">\n <li>Texas</li>\n <li>California</li>\n </ul>\n</div>\n```\n\nThe `MultiSelect` component gets the `aria-labelledby` from the `FormField.Input` and forwards it to\nboth the `input[role=combobox]` element and the `ul[role=listbox]` element so the screen reader\nknows the label for both is \"States you've lived in\".\n\n### Label Text Best Practices\n\n- **Be Clear and Concise**: Labels should clearly describe the purpose of the input field.\n- **Use Visible Labels Instead of Only Placeholders**: Always provide a persistent and accessible\n label with `FormField.Label`. Do not rely solely on placeholder text, as it can disappear while\n typing and may not be accessible to assistive technologies. Use the `isHidden` prop on\n `FormField.Label` if a hidden label is required for visual design.\n\n### Screen Reader Experience\n\n- The label is announced when the input receives focus.\n- Required, disabled, and invalid statuses are announced automatically.\n- Help text and error messages are announced automatically when focused.\n- For grouped inputs, the group label (`legend`) is announced automatically when focused.\n\n## Component API\n\n## Specifications\n\n",
|
|
505
|
-
accessibilityProse: '## Accessibility\n\n`FormField` provides essential accessibility features to ensure form inputs
|
|
504
|
+
mdxProse: "# Canvas Kit Form Field\n\nFormField allows users to wrap input components to make them accessible. You can customize the field\nby passing in `TextInput`, `Select`, `RadioGroup` and other form elements to `FormField.Input`.\n\n## Installation\n\n```sh\nyarn add @workday/canvas-kit-react\n```\n\n## Usage\n\n### Basic\n\nForm Field should be used in tandem with most Canvas Kit input components to ensure they meet\naccessibility standards. The orientation of the label by default is `vertical`.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const Basic = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <Flex>\n <FormField>\n <FormField.Label>First Name</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextInput} value={value} onChange={handleChange} />\n </FormField.Field>\n </FormField>\n </Flex>\n );\n};\n```\n\n### Error States\n\nSet the `error` prop of the Form Field or define it in the model to indicate it has an error.\n`error` accepts the following values:\n\n`\"error\" | \"caution\" | undefined`\n\n### Caution\n\nUse the caution state when a value is valid but there is additional information.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const Caution = () => {\n const [value, setValue] = React.useState('hi');\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <Flex>\n <FormField error=\"caution\">\n <FormField.Label>Create Password</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextInput} type=\"password\" value={value} onChange={handleChange} />\n <FormField.Hint>\n Alert: Password strength is weak, using more characters is recommended.\n </FormField.Hint>\n </FormField.Field>\n </FormField>\n </Flex>\n );\n};\n```\n\n> **Accessibility Note**: Caution state **will not** include the `aria-invalid` attribute on the\n> input for screen readers. Use error states when values are not valid.\n\n### Error\n\nUse the error state when the value is no longer valid.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const Error = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <Flex>\n <FormField error=\"error\">\n <FormField.Label>Password</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextInput} type=\"password\" value={value} onChange={handleChange} />\n <FormField.Hint>Error: Must Contain a number and a capital letter</FormField.Hint>\n </FormField.Field>\n </FormField>\n </Flex>\n );\n};\n```\n\n> **Accessibility Note**: Error states include visual color changes to the input border and\n> **require** supplemental \"error\" text for colorblind users to distinguish between fields in an\n> error state from fields with standard hint text. Read more about\n> [Failure of Success Criterion 1.4.1 due to identifying required or error fields using color differences only](https://www.w3.org/WAI/WCAG22/Techniques/failures/F81)\n\n### Hint\n\nUse `FormField.Hint` to display a short message below the input component and `FormField.Field` to\nensure proper alignment.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const Hint = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <Flex>\n <FormField orientation=\"horizontalStart\">\n <FormField.Label>First Name</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextInput} value={value} onChange={handleChange} />\n <FormField.Hint>Cannot contain numbers</FormField.Hint>\n </FormField.Field>\n </FormField>\n </Flex>\n );\n};\n```\n\n> **Accessibility Note**: Hints are automatically associated to the input field with the\n> `aria-describedby` attribute. This ensures that screen readers can automatically announce the hint\n> text to users when the input field is focused.\n\n### Disabled\n\nSet the `disabled` prop of `FormField.Input` to prevent users from interacting with it.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const Disabled = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <Flex>\n <FormField>\n <FormField.Label>Email</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextInput} value={value} disabled onChange={handleChange} />\n </FormField.Field>\n </FormField>\n </Flex>\n );\n};\n```\n\n> **Accessibility Note**: Disabled form elements are exempt from\n> [WCAG minimum contrast guidelines](https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html).\n> Despite this exemption, disabled fields are more difficult for low vision and colorblind users to\n> perceive and may harm the usability of the form. Consider using text elements instead, or\n> read-only fields if users cannot modify data.\n\n### Label Position\n\nSet the `orientation` prop of the Form Field to designate the position of the label relative to the\ninput component. By default, the orientation will be set to `vertical`. If you want your label to be\nhorizontal, you have two options: `horizontalStart` and `horizontalEnd`.\n\nIf you want the position of the label at the start of the container, set orientation prop to\n`horizontalStart`.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\nimport {createStyles} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst formStyles = createStyles({\n display: 'flex',\n gap: system.gap.sm,\n flexDirection: 'column',\n});\n\nexport const LabelPositionHorizontalStart = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <form className={formStyles}>\n <FormField orientation=\"horizontalStart\">\n <FormField.Label>Email</FormField.Label>\n <FormField.Input as={TextInput} value={value} onChange={handleChange} />\n </FormField>\n <FormField orientation=\"horizontalStart\">\n <FormField.Label>Password</FormField.Label>\n <FormField.Input as={TextInput} type=\"password\" />\n </FormField>\n </form>\n );\n};\n```\n\nIf you want the position of the label at the end of the container, set orientation prop to\n`horizontalEnd`.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\nimport {createStyles} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst formStyles = createStyles({\n display: 'flex',\n gap: system.gap.sm,\n flexDirection: 'column',\n});\n\nexport const LabelPositionHorizontalEnd = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <form className={formStyles}>\n <FormField orientation=\"horizontalEnd\">\n <FormField.Label>Email</FormField.Label>\n <FormField.Input as={TextInput} value={value} onChange={handleChange} />\n </FormField>\n <FormField orientation=\"horizontalEnd\">\n <FormField.Label>Password</FormField.Label>\n <FormField.Input as={TextInput} type=\"password\" />\n </FormField>\n </form>\n );\n};\n```\n\n### Grow\n\nSet the `grow` prop of the Form Field to `true` to configure it (including the wrapped input\ncomponent) to expand to the width of its container.\n\n**Note: This Prop is deprecated and will be removed in a future major version.**\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const Grow = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <Flex>\n <FormField grow>\n <FormField.Label>First Name</FormField.Label>\n <FormField.Input as={TextInput} value={value} onChange={handleChange} />\n </FormField>\n </Flex>\n );\n};\n```\n\n### Ref Forwarding\n\nIf you need full customization you can use the `FormField` behavior hooks to build your own\nsolution. It is also easy it work with custom components or third party libraries and get the CKR\naccessibility guarantees by using the `as` prop.\n```tsx\nimport React from 'react';\n\nimport {SecondaryButton} from '@workday/canvas-kit-react/button';\nimport {changeFocus} from '@workday/canvas-kit-react/common';\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\nimport {createStyles} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst parentContainerStyles = createStyles({\n gap: system.gap.xs,\n alignItems: 'flex-start',\n flexDirection: 'column',\n});\n\nexport const RefForwarding = () => {\n const [value, setValue] = React.useState('');\n const ref = React.useRef(null);\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n const handleClick = () => {\n changeFocus(ref.current);\n };\n\n return (\n <Flex cs={parentContainerStyles}>\n <FormField>\n <FormField.Label>Email</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextInput} onChange={handleChange} value={value} ref={ref} />\n </FormField.Field>\n </FormField>\n <SecondaryButton onClick={handleClick}>Focus Text Input</SecondaryButton>\n </Flex>\n );\n};\n```\n\n### Required\n\nSet the `isRequired` prop of the Form Field to `true` to indicate that the field is required. Labels\nfor required fields are suffixed by a red asterisk.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const Required = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <FormField isRequired={true}>\n <FormField.Label>Email</FormField.Label>\n <FormField.Field>\n <FormField.Input\n as={TextInput}\n placeholder=\"your@gmail.com\"\n onChange={handleChange}\n value={value}\n />\n </FormField.Field>\n </FormField>\n );\n};\n```\n\n> **Accessibility Note**: The HTML `required` attribute will be added to the input field and\n> announced by screen readers. Consider adding a note at the top of your form indicating that fields\n> marked with an asterisk (\\*) are required. This provides context for all users.\n\n### Grouped Inputs\n\nUse `FormFieldGroup` when you have a group of inputs that need to be associated to one another, like\n`RadioGroup` or a group of `Checkbox`'s. `FormFieldGroup` renders a `div` with `role=\"group\"` and an\n`aria-labelledby` reference. `FormFieldGroup.Label` renders a `div` with an `id` referenced by the\n`aria-labelledby` of the `FormFieldGroup`. Screen readers announce the group label when focusing\ncontrols in the group.\n\n`FormFieldGroup` supports the same props of `FormField`:\n\n- `error`: `\"caution\" | \"error\"` Defines the error around the whole group of inputs.\n- `orientation`: `\"vertical\" | \"horizontalStart\" | \"horizontalEnd\"` Defines the group label\n placement.\n- `isRequired`: `true` Defines if a group like RadioGroup is required.\n```tsx\nimport React from 'react';\n\nimport {RadioGroup} from '@workday/canvas-kit-preview-react/radio';\nimport {Banner} from '@workday/canvas-kit-react/banner';\nimport {PrimaryButton, SecondaryButton} from '@workday/canvas-kit-react/button';\nimport {Checkbox} from '@workday/canvas-kit-react/checkbox';\nimport {AriaLiveRegion} from '@workday/canvas-kit-react/common';\nimport {FormFieldGroup} from '@workday/canvas-kit-react/form-field';\nimport {createStyles, px2rem} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst formStyles = createStyles({\n margin: `0 ${px2rem(12)}`,\n});\n\nconst formButtonStyles = createStyles({\n display: 'inline-flex',\n gap: system.gap.sm,\n});\n\nconst toppings = [\n {\n id: 1,\n label: 'Pepperoni',\n checked: false,\n },\n {\n id: 2,\n label: 'Cheese',\n checked: false,\n },\n {\n id: 3,\n label: 'Pineapple',\n checked: false,\n },\n {\n id: 4,\n label: 'Mushrooms',\n checked: false,\n },\n];\n\nconst bannerStyles = createStyles({\n position: 'absolute',\n right: 0,\n});\n\nexport const GroupedInputs = () => {\n const [toppingsState, setToppingsState] = React.useState(toppings);\n const [error, setError] = React.useState(undefined);\n const [radioError, setRadioError] = React.useState(undefined);\n const [showSuccess, setShowSuccess] = React.useState(false);\n\n const [value, setValue] = React.useState<string>('');\n const [formData, setFormData] = React.useState({\n toppings: [],\n crust: '',\n });\n const handleCheckboxCheck = id => {\n if (error) {\n setError(undefined);\n }\n setToppingsState(\n toppingsState.map(item => (item.id === id ? {...item, checked: !item.checked} : item))\n );\n };\n\n const handleRadioChange = (e: React.ChangeEvent) => {\n if (radioError) {\n setRadioError(undefined);\n }\n const target = e.currentTarget;\n if (target instanceof HTMLInputElement) {\n setValue(target.value);\n }\n };\n\n const handleSubmit = e => {\n e.preventDefault();\n const radioError = !value && toppingsState.some(item => !item.checked) ? 'error' : undefined;\n const error = toppingsState.every(item => !item.checked) ? 'error' : undefined;\n\n setRadioError(radioError);\n setError(error);\n if (!error && !radioError && toppingsState.some(item => item.checked) && value) {\n setShowSuccess(true);\n }\n setFormData({\n toppings: toppingsState,\n crust: value,\n });\n };\n\n React.useEffect(() => {\n const timeout = setTimeout(() => {\n if (showSuccess) {\n setShowSuccess(false);\n }\n }, 3000);\n\n return () => clearTimeout(timeout);\n }, [showSuccess]);\n\n const handleReset = () => {\n setFormData({toppings: [], crust: ''});\n setError(undefined);\n setValue('');\n setRadioError('');\n setShowSuccess(false);\n setToppingsState(\n toppingsState.map(item => {\n return {...item, checked: false};\n })\n );\n };\n\n return (\n <div>\n <h3>Choose your pizza options</h3>\n <AriaLiveRegion role=\"alert\">\n <div style={{display: 'flex', gap: '40px'}}>\n {error || radioError ? (\n <Banner isSticky hasError className={bannerStyles}>\n <Banner.Label>\n {error && radioError\n ? 'At least one topping and crust selection is required'\n : error\n ? 'You must choose at least one topping'\n : radioError\n ? 'You must choose a crust'\n : ''}\n </Banner.Label>\n </Banner>\n ) : null}\n {showSuccess && (\n <Banner isSticky className={bannerStyles}>\n <Banner.Label>You've successfully submitted your pizza options.</Banner.Label>\n </Banner>\n )}\n </div>\n </AriaLiveRegion>\n\n <form className={formStyles} onSubmit={handleSubmit}>\n <FormFieldGroup error={error} isRequired>\n <FormFieldGroup.Label>Choose Your Toppings</FormFieldGroup.Label>\n <FormFieldGroup.List>\n {toppingsState.map(item => {\n return (\n <FormFieldGroup.Input\n key={item.id}\n onChange={() => handleCheckboxCheck(item.id)}\n checked={item.checked}\n value={item.label}\n as={Checkbox}\n disabled={item.label === 'Pineapple' ? true : undefined}\n label={item.label}\n />\n );\n })}\n </FormFieldGroup.List>\n <FormFieldGroup.Hint>\n {error === 'error' && 'Error: You must choose one topping'}\n </FormFieldGroup.Hint>\n </FormFieldGroup>\n <FormFieldGroup error={radioError} isRequired>\n <FormFieldGroup.Label>Choose Your Crust</FormFieldGroup.Label>\n <FormFieldGroup.Field>\n <FormFieldGroup.List\n as={RadioGroup}\n onChange={handleRadioChange}\n value={value}\n name=\"crust\"\n >\n <FormFieldGroup.Input as={RadioGroup.RadioButton} value=\"thin-crust\">\n Thin Crust\n </FormFieldGroup.Input>\n <FormFieldGroup.Input as={RadioGroup.RadioButton} value=\"hand-tossed\">\n Hand Tossed\n </FormFieldGroup.Input>\n <FormFieldGroup.Input as={RadioGroup.RadioButton} value=\"deep-dish\">\n Deep Dish\n </FormFieldGroup.Input>\n <FormFieldGroup.Input as={RadioGroup.RadioButton} value=\"cauliflower\">\n Cauliflower\n </FormFieldGroup.Input>\n </FormFieldGroup.List>\n <FormFieldGroup.Hint>\n {radioError === 'error' ? 'Error: You must choose a crust' : null}\n </FormFieldGroup.Hint>\n </FormFieldGroup.Field>\n </FormFieldGroup>\n <div className={formButtonStyles}>\n <PrimaryButton type=\"submit\">Submit Your Choices</PrimaryButton>\n <SecondaryButton onClick={() => handleReset()}>Reset Form</SecondaryButton>\n </div>\n </form>\n <div>\n <div>\n Selected Toppings:{' '}\n {!error && formData.toppings.map(item => (item.checked ? `${item.label} ` : null))}\n </div>\n <div>Selected Crust: {formData.crust}</div>\n </div>\n </div>\n );\n};\n```\n\n> **Accessibility Note**: In addition to radio button and checkbox groups, `FormFieldGroup` can be\n> useful in any situation where the form needs to have multiple sets of identical input fields. For\n> example, a form with identical fields for a Shipping address and a Billing address. The legend\n> (group label) provides critical context for screen reader users in these situations.\n\n### Custom\n\nIf you need full customization you can use the `FormField` behavior hooks to build your own\nsolution. It is also easy it work with custom components or third party libraries and get the CKR\naccessibility guarantees by using the `as` prop.\n```tsx\nimport React from 'react';\n\nimport {useModelContext} from '@workday/canvas-kit-react/common';\nimport {\n formFieldStencil,\n useFormFieldHint,\n useFormFieldInput,\n useFormFieldLabel,\n useFormFieldModel,\n} from '@workday/canvas-kit-react/form-field';\nimport {Flex} from '@workday/canvas-kit-react/layout';\n\nconst Label = ({model, children}) => {\n const localModel = useModelContext(useFormFieldModel.Context, model);\n const props = useFormFieldLabel(localModel);\n\n return (\n <label {...props}>\n {children}\n {model.state.isRequired ? '*' : ''}\n </label>\n );\n};\n\nconst Hint = ({model, children}) => {\n const localModel = useModelContext(useFormFieldModel.Context, model);\n const props = useFormFieldHint(localModel);\n\n return <span {...props}>{children}</span>;\n};\n\nconst Input = ({model, ...elementProps}) => {\n const localModel = useModelContext(useFormFieldModel.Context, model);\n const props = useFormFieldInput(localModel, elementProps);\n\n return <input type=\"text\" required={model.state.isRequired ? true : false} {...props} />;\n};\n\nexport const Custom = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n const model = useFormFieldModel({isRequired: true});\n\n return (\n <Flex cs={formFieldStencil({orientation: 'horizontalStart'})}>\n <Label model={model}>My Custom Field</Label>\n <Input model={model} value={value} onChange={handleChange} />\n <Hint model={model}>You can be anything</Hint>\n </Flex>\n );\n};\n```\n\n### Custom id\n\nForm Field will automatically generate an HTML `id` for its input element to link it to the\ncorreponding label. Alternatively, you may set the `id` prop of the Form Field to specify a custom\n`id` for the input element. The `id` will be appended by `input-${your-unique-id}`.\n```tsx\nimport React from 'react';\n\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const CustomId = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <Flex>\n <FormField id=\"first-name\">\n <FormField.Label>First Name</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextInput} value={value} onChange={handleChange} />\n </FormField.Field>\n </FormField>\n </Flex>\n );\n};\n```\n\n### All Fields\n\nForm Field should allow you to use it with all `inputs` including `Select`, `TextInput`, `Checkbox`,\n`TextArea`, `Switch`, and `RadioGroup`.\n```tsx\nimport {RadioGroup} from '@workday/canvas-kit-preview-react/radio';\nimport {Checkbox} from '@workday/canvas-kit-react/checkbox';\nimport {FormField, FormFieldGroup} from '@workday/canvas-kit-react/form-field';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {Select} from '@workday/canvas-kit-react/select';\nimport {Switch} from '@workday/canvas-kit-react/switch';\nimport {TextArea} from '@workday/canvas-kit-react/text-area';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\nimport {calc, createStyles} from '@workday/canvas-kit-styling';\nimport {base, system} from '@workday/canvas-tokens-web';\n\nconst parentContainerStyles = createStyles({\n flexDirection: 'column',\n gap: calc.subtract(system.gap.lg, system.gap.xs),\n padding: calc.subtract(base.size500, system.padding.xxs),\n borderRadius: system.shape.sm,\n});\n\nexport const AllFields = () => {\n return (\n <Flex cs={parentContainerStyles}>\n <FormField grow>\n <FormField.Label>First Name</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextInput} />\n </FormField.Field>\n </FormField>\n\n <FormField isRequired={true} error=\"caution\" grow>\n <FormField.Label>Email</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextInput} />\n <FormField.Hint>Hint text for your input</FormField.Hint>\n </FormField.Field>\n </FormField>\n <FormField grow>\n <FormField.Label>Text Area Label</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextArea} />\n </FormField.Field>\n </FormField>\n <FormField error=\"error\" grow>\n <FormField.Label>Choose a Crust</FormField.Label>\n <Select items={['Pizza', 'Cheeseburger', 'Fries']}>\n <FormField.Input as={Select.Input} />\n <Select.Popper>\n <Select.Card>\n <Select.List>{item => <Select.Item>{item}</Select.Item>}</Select.List>\n </Select.Card>\n </Select.Popper>\n </Select>\n </FormField>\n <FormField as=\"fieldset\" isRequired={true} error={'error'} orientation=\"horizontalStart\" grow>\n <FormField.Label as=\"legend\">Radio Group Legend</FormField.Label>\n <FormField.Field>\n <FormField.Input as={RadioGroup}>\n <RadioGroup.RadioButton value=\"deep-dish\">Deep dish</RadioGroup.RadioButton>\n <RadioGroup.RadioButton value=\"thin\">Thin</RadioGroup.RadioButton>\n <RadioGroup.RadioButton value=\"gluten-free\">Gluten free</RadioGroup.RadioButton>\n <RadioGroup.RadioButton value=\"cauliflower\">Cauliflower</RadioGroup.RadioButton>\n <RadioGroup.RadioButton value=\"butter\">\n Butter - the best thing to put on bread\n </RadioGroup.RadioButton>\n </FormField.Input>\n <FormField.Hint>Error Message</FormField.Hint>\n </FormField.Field>\n </FormField>\n <FormField as=\"fieldset\" grow>\n <FormField.Label as=\"legend\">Checkbox Legend</FormField.Label>\n <FormField.Input checked={true} as={Checkbox} label=\"Checkbox Label\" />\n <FormField.Input checked={false} as={Checkbox} label=\"Thin Crust\" />\n <FormField.Input checked={false} as={Checkbox} label=\"Extra Cheese\" />\n </FormField>\n <FormFieldGroup error=\"error\" orientation=\"horizontalStart\" grow>\n <FormFieldGroup.Label>Choose Your Crust</FormFieldGroup.Label>\n <FormFieldGroup.Field>\n <FormFieldGroup.List as={RadioGroup}>\n <FormFieldGroup.Input as={RadioGroup.RadioButton} value=\"thin-crust\">\n Thin Crust\n </FormFieldGroup.Input>\n <FormFieldGroup.Input as={RadioGroup.RadioButton} value=\"hand-tossed\">\n Hand Tossed\n </FormFieldGroup.Input>\n <FormFieldGroup.Input as={RadioGroup.RadioButton} value=\"deep-dish\">\n Deep Dish\n </FormFieldGroup.Input>\n <FormFieldGroup.Input as={RadioGroup.RadioButton} value=\"cauliflower\">\n Cauliflower\n </FormFieldGroup.Input>\n </FormFieldGroup.List>\n </FormFieldGroup.Field>\n </FormFieldGroup>\n <FormFieldGroup grow>\n <FormFieldGroup.Label>Checkbox Legend</FormFieldGroup.Label>\n <FormField.Field>\n <FormFieldGroup.List>\n <FormFieldGroup.Input checked={true} as={Checkbox} label=\"Checkbox Label\" />\n <FormFieldGroup.Input checked={false} as={Checkbox} label=\"Thin Crust\" />\n <FormFieldGroup.Input checked={false} as={Checkbox} label=\"Extra Cheese\" />\n </FormFieldGroup.List>\n </FormField.Field>\n </FormFieldGroup>\n\n <FormField orientation=\"horizontalStart\" grow>\n <FormField.Label>Switch Label</FormField.Label>\n <FormField.Field>\n <FormField.Input as={Switch} />\n </FormField.Field>\n </FormField>\n </Flex>\n );\n};\n```\n\n### Hidden Label\n\nIn cases where you want to hide the label while still meeting accessibility standards, you can add\n`isHidden` on the `<FormField.Label/>`. This prop will visually hide the label.\n```tsx\nimport React from 'react';\n\nimport {\n FormField,\n useFormFieldInput,\n useFormFieldModel,\n} from '@workday/canvas-kit-react/form-field';\nimport {SystemIcon} from '@workday/canvas-kit-react/icon';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {InputGroup, TextInput} from '@workday/canvas-kit-react/text-input';\nimport {searchIcon} from '@workday/canvas-system-icons-web';\n\n/**\n * Using `as={InputGroup}` on `FormField.Input` will break the label associations necessary for accessibility.\n * In this example, we've rendered `FormField.Field` as `InputGroup` and then hoisted the `id` of the input from the FormField model.\n * This allows us to set the `id` of the `InputGroup.Input` correctly for proper label association.\n */\n\nexport const HiddenLabel = () => {\n const [value, setValue] = React.useState('');\n const model = useFormFieldModel();\n const {id: formFieldInputId} = useFormFieldInput(model);\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n return (\n <Flex>\n <FormField model={model}>\n <FormField.Label isHidden>Search</FormField.Label>\n <FormField.Field as={InputGroup}>\n <InputGroup.InnerStart>\n <SystemIcon icon={searchIcon} size=\"small\" />\n </InputGroup.InnerStart>\n <InputGroup.Input\n as={TextInput}\n id={formFieldInputId}\n onChange={handleChange}\n value={value}\n />\n </FormField.Field>\n </FormField>\n </Flex>\n );\n};\n```\n\n> **Accessibility Note**: Hidden labels are typically not recommended. In this example, a\n> universally recognizable icon like a magnifying glass signaling \"search\" may be a suitable\n> alternative to visible text labels.\n\n### Themed Errors\n\nYou can theme your error rings by wrapping an input in a `CanvasProvider` and defining\n`focusOutline` and `error` properties on the `theme`.\n\n### Custom Styles\n\nForm Field and its subcomponents support custom styling via the `cs` prop. For more information,\ncheck our\n[\"How To Customize Styles\"](https://workday.github.io/canvas-kit/?path=/docs/styling-guides-customizing-styles--docs).\n\n## Accessibility\n\n`FormField` provides essential accessibility features to ensure form inputs have a programmatically\ndeterminable name, relationships, and instructions. The primary accessibility goal is to ensure\nassistive technology users can identify every field, understand how to complete it, and hear hints,\nerrors, and required state when the form input receives focus.\n\n### Minimum Accessible Structure\n\n```tsx\n\n<FormField>\n <FormField.Label>Email</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextInput} />\n <FormField.Hint>We'll never share your email.</FormField.Hint>\n </FormField.Field>\n</FormField>;\n```\n\nInclude **`FormField.Hint`** whenever there is hint or error text\u2014the wiring slot for\n`aria-describedby` exists by default (see **Built-in Behaviors**).\n\n### Built-in Behaviors\n\nCanvas Kit applies these automatically when you compose `FormField` with its subcomponents. **Do not\nduplicate them** in consuming code.\n\n**ARIA and DOM** (_applied by subcomponents_):\n\n- `FormField.Label`: `<label>` with `htmlFor` matching the `id` on `FormField.Input` (`input-{id}`).\n Clicking the label moves focus to the input.\n- `FormField.Input`: `id` (`input-{id}`), `aria-labelledby` (`label-{id}`), `aria-describedby`\n (`hint-{id}` when an `id` exists), `required` when `isRequired`, and `aria-invalid=\"true\"` when\n `error=\"error\"`. Caution (`error=\"caution\"`) does **not** set `aria-invalid`.\n- `FormField.Hint`: `id=\"hint-{id}\"` for description association.\n- Generated IDs: `label-{id}`, `input-{id}`, and `hint-{id}` from the `FormField` `id`\n (auto-generated or custom via the `id` prop).\n- `FormFieldGroup`: `role=\"group\"` with `aria-labelledby` referencing `FormFieldGroup.Label`.\n- Composite controls (for example, `MultiSelect`) forward `aria-labelledby` to related sub-elements\n (combobox, listbox) so every part shares the same accessible name.\n\n**Implementation note on `aria-describedby`:** `FormField.Input` always sets\n`aria-describedby=\"hint-{id}\"` when an `id` exists, even if `FormField.Hint` is not rendered. Always\nrender `FormField.Hint` when there is hint or error text. Omitting `FormField.Hint` leaves a\ndangling `aria-describedby` reference.\n\n**Keyboard** (_standard form control behavior_):\n\n- <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> move focus to and from the input (native tab\n order)\n- Clicking `FormField.Label` moves focus to the associated input (native `<label>` behavior)\n- Checkbox, radio, and composite controls follow the wrapped input component's own keyboard patterns\n\n**Screen reader expectations** (_when built-in behaviors are used as intended_):\n\n- On focus, assistive technology announces the field label and, when applicable: required state,\n invalid state (`error=\"error\"`), and hint or error text via `aria-describedby`\n- The Caution state is visual only \u2014`aria-invalid` is **not** set for `error=\"caution\"`\n- In a `FormFieldGroup`, the group label is associated via `aria-labelledby`; focusing a control in\n the group includes the group name in context\n- Disabled inputs are skipped in the tab order and may be announced as unavailable\n\nFor a simple field, the DOM looks like:\n\n```html\n<div>\n <label id=\"label-abc\" for=\"input-abc\">First Name</label>\n <input id=\"input-abc\" aria-labelledby=\"label-abc\" />\n</div>\n```\n\nSome composite controls such as `MultiSelect`, have additional sub-elements that also need to be\nlinked to the `FormField.Label`. In the example below, the FormField.Label is applied to both the\n`input[role=combobox]` element and the `ul[role=listbox]` element so that screen reader knows that\nthe label for both is the \"States you've lived in\".\n\n```html\n<div>\n <label id=\"label-abc\" for=\"input-abc\">States you've lived in</label>\n <input\n id=\"input-abc\"\n aria-labelledby=\"label-abc\"\n role=\"combobox\"\n aria-expanded=\"false\"\n aria-autocomplete=\"list\"\n aria-controls=\"listbox-abc\"\n type=\"text\"\n />\n <ul id=\"listbox-abc\" role=\"listbox\" aria-labelledby=\"label-abc\">\n <li role=\"option\">Texas</li>\n <li role=\"option\">California</li>\n </ul>\n</div>\n```\n\nFor a field in an error state, the error text is referenced using `aria-describedby`. The rendered\nmarkup looks like:\n\n```html\n<div>\n <label id=\"label-abc\" for=\"input-abc\">Password</label>\n <input\n id=\"input-abc\"\n aria-labelledby=\"label-abc\"\n aria-describedby=\"hint-abc\"\n required\n aria-invalid=\"true\"\n />\n <p id=\"hint-abc\">Error: Must contain a number and a capital letter</p>\n</div>\n```\n\nFor grouped checkbox controls, compose a `FormFieldGroup` with `FormFieldGroup.Label` and\n`FormFieldGroup.Input`. The rendered markup looks like:\n\n```html\n<div role=\"group\" aria-labelledby=\"label-abc\">\n <div id=\"label-abc\">Pizza toppings:</div>\n <div>\n <input type=\"checkbox\" id=\"checkbox-1\" />\n <label for=\"checkbox-1\">Pepperoni</label>\n </div>\n <div>\n <input type=\"checkbox\" id=\"checkbox-2\" />\n <label for=\"checkbox-2\">Mushrooms</label>\n </div>\n <div>\n <input type=\"checkbox\" id=\"checkbox-3\" />\n <label for=\"checkbox-3\">Pineapple</label>\n </div>\n</div>\n```\n\n### Accessibility Requirements\n\nRequired in application code for accessible form fields. Rows marked _(conditional)_ apply only when\nthe situation matches\u2014otherwise omit.\n\n**If no design spec is provided:** use a visible `FormField.Label`, wrap the input with\n`FormField.Input`, omit `isHidden`, and omit a custom `id` unless testing or composition requires\nit.\n\n| Requirement | How to satisfy |\n| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Visible label | **`FormField.Label`** with concise, meaningful text |\n| Input wiring | **`FormField.Input as={...}`** wrapping every Canvas Kit input (`TextInput`, `Select`, `TextArea`, etc.) |\n| Hint or error text _(conditional)_ | **`FormField.Hint`** when there is help or validation copy\u2014required because `aria-describedby` is wired by default (see **Built-in Behaviors**) |\n| Error state _(conditional)_ | `error=\"error\"` on **`FormField`** and **`FormField.Hint`** prefixed with \"Error:\" so users who cannot perceive color can distinguish errors from hints |\n| Caution state _(conditional)_ | `error=\"caution\"` on **`FormField`** with descriptive **`FormField.Hint`** (no `aria-invalid`) |\n| Required field _(conditional)_ | `isRequired` on **`FormField`**; consider a form-level note that asterisk (\\*) marks required fields |\n| Group label _(conditional)_ | **`FormFieldGroup`** + **`FormFieldGroup.Label`** for radio buttons, checkbox groups, or related inputs. Alternatively, **`FormField as=\"fieldset\"`** with **`FormField.Label as=\"legend\"`** |\n| Hidden label _(conditional)_ | **`FormField.Label isHidden`** only when an equally clear alternative exists (for example, a universally recognizable search icon) |\n| 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 |\n\n**Summary for code generation:**\n\n- **REQUIRED:** visible label, input wiring\n- **CONDITIONAL:** hint/error text, required state, group label, hidden label, stable `id`\n\n### Anti-Patterns\n\nDo **not** generate code that does the following (see **Accessibility Requirements** above for what\nto supply instead):\n\n- Manually set `id`, `htmlFor`, `aria-labelledby`, `aria-describedby`, `aria-invalid`, or `required`\n on **`FormField.Input`** or **`FormField.Label`** \u2014 Canvas Kit subcomponents wire these\n automatically\n- **Unlabeled inputs**: Do not use Canvas Kit inputs without `FormField` and `FormField.Label`.\n- **Placeholder-only labels**: Do not rely on `placeholder` instead of `FormField.Label`;\n placeholders disappear while typing and are poor substitutes for labels.\n- **Color-only errors**: Do not set `error=\"error\"` without descriptive text in `FormField.Hint`.\n- **Caution for invalid values**: Do not use `error=\"caution\"` when a value is invalid; use\n `error=\"error\"` so `aria-invalid` is exposed.\n- **Broken ID references**: Do not point `aria-labelledby` or `aria-describedby` at missing or empty\n elements; ensure `FormField.Label` and `FormField.Hint` render meaningful content.\n- **Skipping group labels**: Do not render `RadioGroup` or multiple related checkboxes without\n `FormFieldGroup` and `FormFieldGroup.Label` (or `FormField` with `as=\"fieldset\"` and\n `FormField.Label as=\"legend\"`).\n- **Disabled when read-only fits**: Avoid `disabled` on `FormField.Input` when users only need to\n view data; disabled fields are harder to perceive and are removed from the tab order.\n\n## Component API\n\n## Specifications\n\n",
|
|
505
|
+
accessibilityProse: '## Accessibility\n\n`FormField` provides essential accessibility features to ensure form inputs have a programmatically\ndeterminable name, relationships, and instructions. The primary accessibility goal is to ensure\nassistive technology users can identify every field, understand how to complete it, and hear hints,\nerrors, and required state when the form input receives focus.\n\n### Minimum Accessible Structure\n\n```tsx\n\n<FormField>\n <FormField.Label>Email</FormField.Label>\n <FormField.Field>\n <FormField.Input as={TextInput} />\n <FormField.Hint>We\'ll never share your email.</FormField.Hint>\n </FormField.Field>\n</FormField>;\n```\n\nInclude **`FormField.Hint`** whenever there is hint or error text\u2014the wiring slot for\n`aria-describedby` exists by default (see **Built-in Behaviors**).\n\n### Built-in Behaviors\n\nCanvas Kit applies these automatically when you compose `FormField` with its subcomponents. **Do not\nduplicate them** in consuming code.\n\n**ARIA and DOM** (_applied by subcomponents_):\n\n- `FormField.Label`: `<label>` with `htmlFor` matching the `id` on `FormField.Input` (`input-{id}`).\n Clicking the label moves focus to the input.\n- `FormField.Input`: `id` (`input-{id}`), `aria-labelledby` (`label-{id}`), `aria-describedby`\n (`hint-{id}` when an `id` exists), `required` when `isRequired`, and `aria-invalid="true"` when\n `error="error"`. Caution (`error="caution"`) does **not** set `aria-invalid`.\n- `FormField.Hint`: `id="hint-{id}"` for description association.\n- Generated IDs: `label-{id}`, `input-{id}`, and `hint-{id}` from the `FormField` `id`\n (auto-generated or custom via the `id` prop).\n- `FormFieldGroup`: `role="group"` with `aria-labelledby` referencing `FormFieldGroup.Label`.\n- Composite controls (for example, `MultiSelect`) forward `aria-labelledby` to related sub-elements\n (combobox, listbox) so every part shares the same accessible name.\n\n**Implementation note on `aria-describedby`:** `FormField.Input` always sets\n`aria-describedby="hint-{id}"` when an `id` exists, even if `FormField.Hint` is not rendered. Always\nrender `FormField.Hint` when there is hint or error text. Omitting `FormField.Hint` leaves a\ndangling `aria-describedby` reference.\n\n**Keyboard** (_standard form control behavior_):\n\n- <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> move focus to and from the input (native tab\n order)\n- Clicking `FormField.Label` moves focus to the associated input (native `<label>` behavior)\n- Checkbox, radio, and composite controls follow the wrapped input component\'s own keyboard patterns\n\n**Screen reader expectations** (_when built-in behaviors are used as intended_):\n\n- On focus, assistive technology announces the field label and, when applicable: required state,\n invalid state (`error="error"`), and hint or error text via `aria-describedby`\n- The Caution state is visual only \u2014`aria-invalid` is **not** set for `error="caution"`\n- In a `FormFieldGroup`, the group label is associated via `aria-labelledby`; focusing a control in\n the group includes the group name in context\n- Disabled inputs are skipped in the tab order and may be announced as unavailable\n\nFor a simple field, the DOM looks like:\n\n```html\n<div>\n <label id="label-abc" for="input-abc">First Name</label>\n <input id="input-abc" aria-labelledby="label-abc" />\n</div>\n```\n\nSome composite controls such as `MultiSelect`, have additional sub-elements that also need to be\nlinked to the `FormField.Label`. In the example below, the FormField.Label is applied to both the\n`input[role=combobox]` element and the `ul[role=listbox]` element so that screen reader knows that\nthe label for both is the "States you\'ve lived in".\n\n```html\n<div>\n <label id="label-abc" for="input-abc">States you\'ve lived in</label>\n <input\n id="input-abc"\n aria-labelledby="label-abc"\n role="combobox"\n aria-expanded="false"\n aria-autocomplete="list"\n aria-controls="listbox-abc"\n type="text"\n />\n <ul id="listbox-abc" role="listbox" aria-labelledby="label-abc">\n <li role="option">Texas</li>\n <li role="option">California</li>\n </ul>\n</div>\n```\n\nFor a field in an error state, the error text is referenced using `aria-describedby`. The rendered\nmarkup looks like:\n\n```html\n<div>\n <label id="label-abc" for="input-abc">Password</label>\n <input\n id="input-abc"\n aria-labelledby="label-abc"\n aria-describedby="hint-abc"\n required\n aria-invalid="true"\n />\n <p id="hint-abc">Error: Must contain a number and a capital letter</p>\n</div>\n```\n\nFor grouped checkbox controls, compose a `FormFieldGroup` with `FormFieldGroup.Label` and\n`FormFieldGroup.Input`. The rendered markup looks like:\n\n```html\n<div role="group" aria-labelledby="label-abc">\n <div id="label-abc">Pizza toppings:</div>\n <div>\n <input type="checkbox" id="checkbox-1" />\n <label for="checkbox-1">Pepperoni</label>\n </div>\n <div>\n <input type="checkbox" id="checkbox-2" />\n <label for="checkbox-2">Mushrooms</label>\n </div>\n <div>\n <input type="checkbox" id="checkbox-3" />\n <label for="checkbox-3">Pineapple</label>\n </div>\n</div>\n```\n\n### Accessibility Requirements\n\nRequired in application code for accessible form fields. Rows marked _(conditional)_ apply only when\nthe situation matches\u2014otherwise omit.\n\n**If no design spec is provided:** use a visible `FormField.Label`, wrap the input with\n`FormField.Input`, omit `isHidden`, and omit a custom `id` unless testing or composition requires\nit.\n\n| Requirement | How to satisfy |\n| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Visible label | **`FormField.Label`** with concise, meaningful text |\n| Input wiring | **`FormField.Input as={...}`** wrapping every Canvas Kit input (`TextInput`, `Select`, `TextArea`, etc.) |\n| Hint or error text _(conditional)_ | **`FormField.Hint`** when there is help or validation copy\u2014required because `aria-describedby` is wired by default (see **Built-in Behaviors**) |\n| Error state _(conditional)_ | `error="error"` on **`FormField`** and **`FormField.Hint`** prefixed with "Error:" so users who cannot perceive color can distinguish errors from hints |\n| Caution state _(conditional)_ | `error="caution"` on **`FormField`** with descriptive **`FormField.Hint`** (no `aria-invalid`) |\n| Required field _(conditional)_ | `isRequired` on **`FormField`**; consider a form-level note that asterisk (\\*) marks required fields |\n| Group label _(conditional)_ | **`FormFieldGroup`** + **`FormFieldGroup.Label`** for radio buttons, checkbox groups, or related inputs. Alternatively, **`FormField as="fieldset"`** with **`FormField.Label as="legend"`** |\n| Hidden label _(conditional)_ | **`FormField.Label isHidden`** only when an equally clear alternative exists (for example, a universally recognizable search icon) |\n| 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 |\n\n**Summary for code generation:**\n\n- **REQUIRED:** visible label, input wiring\n- **CONDITIONAL:** hint/error text, required state, group label, hidden label, stable `id`\n\n### Anti-Patterns\n\nDo **not** generate code that does the following (see **Accessibility Requirements** above for what\nto supply instead):\n\n- Manually set `id`, `htmlFor`, `aria-labelledby`, `aria-describedby`, `aria-invalid`, or `required`\n on **`FormField.Input`** or **`FormField.Label`** \u2014 Canvas Kit subcomponents wire these\n automatically\n- **Unlabeled inputs**: Do not use Canvas Kit inputs without `FormField` and `FormField.Label`.\n- **Placeholder-only labels**: Do not rely on `placeholder` instead of `FormField.Label`;\n placeholders disappear while typing and are poor substitutes for labels.\n- **Color-only errors**: Do not set `error="error"` without descriptive text in `FormField.Hint`.\n- **Caution for invalid values**: Do not use `error="caution"` when a value is invalid; use\n `error="error"` so `aria-invalid` is exposed.\n- **Broken ID references**: Do not point `aria-labelledby` or `aria-describedby` at missing or empty\n elements; ensure `FormField.Label` and `FormField.Hint` render meaningful content.\n- **Skipping group labels**: Do not render `RadioGroup` or multiple related checkboxes without\n `FormFieldGroup` and `FormFieldGroup.Label` (or `FormField` with `as="fieldset"` and\n `FormField.Label as="legend"`).\n- **Disabled when read-only fits**: Avoid `disabled` on `FormField.Input` when users only need to\n view data; disabled fields are harder to perceive and are removed from the tab order.'
|
|
506
506
|
},
|
|
507
507
|
expandable: {
|
|
508
508
|
title: "Components/Containers/Expandable",
|
|
@@ -515,8 +515,8 @@ var stories_config_default = {
|
|
|
515
515
|
title: "Components/Popups/Dialog",
|
|
516
516
|
storybookUrl: "https://workday.github.io/canvas-kit/?path=/docs/components-popups-dialog--docs",
|
|
517
517
|
mdxPath: "modules/react/dialog/stories/Dialog.mdx",
|
|
518
|
-
mdxProse: "# Canvas Kit Dialog\n\nA Dialog component is a non-modal type of dialog that will not render the rest of the page inert\nwhile it is active. A Dialog should be used in situations where the task is not critical.\n\n## Installation\n\n```sh\nyarn add @workday/canvas-kit-react\n```\n\n## Usage\n\n### Basic Example\n\nUnlike Modal, Dialog **does not** render the rest of the page inert while it is active. Dialog\nshould be used in situations where the task does not require immediate attention.\n```tsx\nimport React from 'react';\n\nimport {PrimaryButton} from '@workday/canvas-kit-react/button';\nimport {Dialog} from '@workday/canvas-kit-react/dialog';\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\nimport {system} from '@workday/canvas-tokens-web';\n\nexport const Basic = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n const handleEmail = () => {\n console.log('Email Submitted');\n };\n\n return (\n <Dialog>\n <Dialog.Target as={PrimaryButton}>Open for Offer</Dialog.Target>\n <Dialog.Popper>\n <Dialog.Card>\n <Dialog.CloseIcon aria-label=\"Close\" />\n <Dialog.Heading cs={{paddingBlockStart: system.padding.md}}>\n Sign Up for 15% Off Your Next Order\n </Dialog.Heading>\n <Dialog.Body>\n <FormField>\n <FormField.Label>Email</FormField.Label>\n <FormField.Input as={TextInput} grow onChange={handleChange} value={value} />\n </FormField>\n </Dialog.Body>\n <Dialog.ButtonGroup>\n <Dialog.CloseButton>Cancel</Dialog.CloseButton>\n <Dialog.CloseButton as={PrimaryButton} onClick={handleEmail}>\n Submit\n </Dialog.CloseButton>\n </Dialog.ButtonGroup>\n </Dialog.Card>\n </Dialog.Popper>\n </Dialog>\n );\n};\n```\n\n### Focus Redirect\n\nDialog **does not** trap keyboard focus like the Modal component does. Instead, it allows focus to\nmove freely in and out of the dialog, supporting more flexible navigation. The following example\nshows how Dialog manages focus in and out of the component.\n```tsx\nimport React from 'react';\n\nimport {PrimaryButton} from '@workday/canvas-kit-react/button';\nimport {Dialog} from '@workday/canvas-kit-react/dialog';\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\nimport {system} from '@workday/canvas-tokens-web';\n\nexport const Focus = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n const handleEmail = () => {\n console.log('Email Submitted');\n };\n\n return (\n <Flex cs={{gap: system.gap.lg}}>\n <Dialog>\n <Dialog.Target as={PrimaryButton}>Open for Offer</Dialog.Target>\n <Dialog.Popper>\n <Dialog.Card>\n <Dialog.CloseIcon aria-label=\"Close\" />\n <Dialog.Heading cs={{paddingBlockStart: system.padding.md}}>\n Sign Up for 15% Off Your Next Order\n </Dialog.Heading>\n <Dialog.Body>\n <FormField>\n <FormField.Label>Email</FormField.Label>\n <FormField.Input as={TextInput} grow onChange={handleChange} value={value} />\n </FormField>\n </Dialog.Body>\n <Dialog.ButtonGroup>\n <Dialog.CloseButton>Cancel</Dialog.CloseButton>\n <Dialog.CloseButton as={PrimaryButton} onClick={handleEmail}>\n Submit\n </Dialog.CloseButton>\n </Dialog.ButtonGroup>\n </Dialog.Card>\n </Dialog.Popper>\n </Dialog>\n <PrimaryButton>Focus #1</PrimaryButton>\n <PrimaryButton>Focus #2</PrimaryButton>\n </Flex>\n );\n};\n```\n\n> **Accessibility Note**: Focus redirect **will not** have any effect on the reading order of a\n> screen reader.\n\n### Alt Example\n\nThe `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.\n```tsx\nimport React from 'react';\n\nimport {SecondaryButton} from '@workday/canvas-kit-react/button';\nimport {Dialog} from '@workday/canvas-kit-react/dialog';\nimport {createStyles, px2rem} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst altBackgroundStyles = createStyles({\n background: system.color.bg.alt.default,\n padding: system.padding.xl,\n borderRadius: system.shape.md,\n minHeight: px2rem(400),\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n});\n\nexport const Alt = () => {\n return (\n <div className={altBackgroundStyles}>\n <Dialog>\n <Dialog.Target as={SecondaryButton}>Open Dialog</Dialog.Target>\n <Dialog.Popper>\n <Dialog.Card variant=\"alt\">\n <Dialog.CloseIcon aria-label=\"Close\" />\n <Dialog.Heading>Dialog with Alt Variant</Dialog.Heading>\n <Dialog.Body>\n This dialog uses the alt variant for proper contrast on colored backgrounds.\n </Dialog.Body>\n <Dialog.ButtonGroup>\n <Dialog.CloseButton as={SecondaryButton}>Cancel</Dialog.CloseButton>\n <Dialog.CloseButton>OK</Dialog.CloseButton>\n </Dialog.ButtonGroup>\n </Dialog.Card>\n </Dialog.Popper>\n </Dialog>\n </div>\n );\n};\n```\n\n## Accessibility\n\n`Dialog` composes the popup stack with `useInitialFocus`, `useReturnFocus`, `useCloseOnEscape`,\n`useCloseOnOutsideClick`, and `useFocusRedirect`. The card container includes an ARIA\n**`role=\"dialog\"`** that is **non-modal**: the rest of the page stays available. The card also\nincludes an **`aria-labelledby`** attribute referencing the `id` on `Dialog.Heading`, so the dialog\nhas an accessible name that matches the visible heading.\n\nThe Dialog component includes a `<div>` element (sibling to the `Dialog.Target`) with `aria-owns`\npointing to the `Dialog.Card`. This remaps the hierarchy of the accessibility tree to improve\nsequential reading order in supported browsers. For more information, see\n[Guides > Accessibility > Inline Popups](https://workday.github.io/canvas-kit/?path=/docs/guides-accessibility-inline-popups--docs).\n\n[Dialog Pattern | APG | WAI | W3C](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/)\n\n- Prefer **`Dialog.Heading`** so the dialog is properly labelled; avoid leaving a dialog without an\n accessible name.\n- Ensure icon-only controls such as **`Dialog.CloseIcon`** include an accessible name. Prefer the\n `Tooltip` component to provide a visible label, or a translated `aria-label` string is acceptable.\n\n### Navigation\n\n- **Enter** / **Space**: Open the dialog (standard button behavior on the trigger). When it opens,\n focus moves to the **first focusable element** inside the dialog in DOM order\u2014often the close\n control\u2014or to the element referenced by **`initialFocusRef`** on the dialog model when set.\n- **Tab** / **Shift + Tab**: Move through focusable elements inside the dialog; leaving the first or\n last focusable element **closes** the dialog and moves focus to the next or previous focusable\n element on the page (non-modal focus redirect behavior).\n- **Escape**: Closes the dialog and returns focus to the `Dialog.Target` (or configured return\n target).\n\n### Screen Reader Experience\n\n- **When the dialog opens:** Screen readers should announce the name and role of the first focused\n control (often the close button), the dialog's name (`Dialog.Heading`) and role.\n- **Reading order:** The dialog contents should be read in the same order as it appears on screen\n for browsers and screen readers that support `aria-owns`. Results vary, so always test with your\n supported browsers and screen reader combinations.\n- **Expanded or collapsed state:** The `Dialog.Target` does not include an expanded or collapsed\n state by default, but it can be added if the interaction design isn't using an initial focus for\n the Dialog. See\n [Guides > Accessibility > Inline Popups](https://workday.github.io/canvas-kit/?path=/docs/guides-accessibility-inline-popups--docs)\n for more information.\n\n## Component API\n\n",
|
|
519
|
-
accessibilityProse: "## Accessibility\n\n`Dialog` composes the popup stack with `useInitialFocus`, `useReturnFocus`, `useCloseOnEscape`,\n`useCloseOnOutsideClick`, and `useFocusRedirect`. The card container includes an ARIA\n**`role=\"dialog\"`** that is **non-modal**: the rest of the page stays available. The card also\nincludes an **`aria-labelledby`** attribute referencing the `id` on `Dialog.Heading`, so the dialog\nhas an accessible name that matches the visible heading.\n\nThe Dialog component includes a `<div>` element (sibling to the `Dialog.Target`) with `aria-owns`\npointing to the `Dialog.Card`. This remaps the hierarchy of the accessibility tree to improve\nsequential reading order in supported browsers. For more information, see\n[Guides > Accessibility > Inline Popups](https://workday.github.io/canvas-kit/?path=/docs/guides-accessibility-inline-popups--docs).\n\n[Dialog Pattern | APG | WAI | W3C](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/)\n\n- Prefer **`Dialog.Heading`** so the dialog is properly labelled; avoid leaving a dialog without an\n accessible name.\n- Ensure icon-only controls such as **`Dialog.CloseIcon`** include an accessible name. Prefer the\n `Tooltip` component to provide a visible label, or a translated `aria-label` string is acceptable.\n\n### Navigation\n\n- **Enter** / **Space**: Open the dialog (standard button behavior on the trigger). When it opens,\n focus moves to the **first focusable element** inside the dialog in DOM order\u2014often the close\n control\u2014or to the element referenced by **`initialFocusRef`** on the dialog model when set.\n- **Tab** / **Shift + Tab**: Move through focusable elements inside the dialog; leaving the first or\n last focusable element **closes** the dialog and moves focus to the next or previous focusable\n element on the page (non-modal focus redirect behavior).\n- **Escape**: Closes the dialog and returns focus to the `Dialog.Target` (or configured return\n target).\n\n### Screen Reader Experience\n\n- **When the dialog opens:** Screen readers should announce the name and role of the first focused\n control (often the close button), the dialog's name (`Dialog.Heading`) and role.\n- **Reading order:** The dialog contents should be read in the same order as it appears on screen\n for browsers and screen readers that support `aria-owns`. Results vary, so always test with your\n supported browsers and screen reader combinations.\n- **Expanded or collapsed state:** The `Dialog.Target` does not include an expanded or collapsed\n state by default, but it can be added if the interaction design isn't using an initial focus for\n the Dialog. See\n [Guides > Accessibility > Inline Popups](https://workday.github.io/canvas-kit/?path=/docs/guides-accessibility-inline-popups--docs)\n for more information."
|
|
518
|
+
mdxProse: "# Canvas Kit Dialog\n\nA Dialog component is a non-modal type of dialog that will not render the rest of the page inert\nwhile it is active. A Dialog should be used in situations where the task is not critical.\n\n## Installation\n\n```sh\nyarn add @workday/canvas-kit-react\n```\n\n## Usage\n\n### Basic Example\n\nThe following example shows a typical Dialog with heading, close control, and form content.\n```tsx\nimport React from 'react';\n\nimport {PrimaryButton} from '@workday/canvas-kit-react/button';\nimport {Dialog} from '@workday/canvas-kit-react/dialog';\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\nimport {system} from '@workday/canvas-tokens-web';\n\nexport const Basic = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n const handleEmail = () => {\n console.log('Email Submitted');\n };\n\n return (\n <Dialog>\n <Dialog.Target as={PrimaryButton}>Open for Offer</Dialog.Target>\n <Dialog.Popper>\n <Dialog.Card>\n <Dialog.CloseIcon aria-label=\"Close\" />\n <Dialog.Heading cs={{paddingBlockStart: system.padding.md}}>\n Sign Up for 15% Off Your Next Order\n </Dialog.Heading>\n <Dialog.Body>\n <FormField>\n <FormField.Label>Email</FormField.Label>\n <FormField.Input as={TextInput} grow onChange={handleChange} value={value} />\n </FormField>\n </Dialog.Body>\n <Dialog.ButtonGroup>\n <Dialog.CloseButton>Cancel</Dialog.CloseButton>\n <Dialog.CloseButton as={PrimaryButton} onClick={handleEmail}>\n Submit\n </Dialog.CloseButton>\n </Dialog.ButtonGroup>\n </Dialog.Card>\n </Dialog.Popper>\n </Dialog>\n );\n};\n```\n\n### Focus Redirect\n\nDialog **does not** trap keyboard focus like the Modal component does. The default `useDialogModel`\ncomposes `useFocusRedirect`: <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> at the last or first\nfocusable element inside the dialog closes it and moves focus to the next or previous focusable\nelement on the page. Dialog is non-modal and is **not** a focus trap; it does **not** change screen\nreader reading order. The following example shows how Dialog manages focus at those edges.\n```tsx\nimport React from 'react';\n\nimport {PrimaryButton} from '@workday/canvas-kit-react/button';\nimport {Dialog} from '@workday/canvas-kit-react/dialog';\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\nimport {system} from '@workday/canvas-tokens-web';\n\nexport const Focus = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setValue(event.target.value);\n };\n\n const handleEmail = () => {\n console.log('Email Submitted');\n };\n\n return (\n <Flex cs={{gap: system.gap.lg}}>\n <Dialog>\n <Dialog.Target as={PrimaryButton}>Open for Offer</Dialog.Target>\n <Dialog.Popper>\n <Dialog.Card>\n <Dialog.CloseIcon aria-label=\"Close\" />\n <Dialog.Heading cs={{paddingBlockStart: system.padding.md}}>\n Sign Up for 15% Off Your Next Order\n </Dialog.Heading>\n <Dialog.Body>\n <FormField>\n <FormField.Label>Email</FormField.Label>\n <FormField.Input as={TextInput} grow onChange={handleChange} value={value} />\n </FormField>\n </Dialog.Body>\n <Dialog.ButtonGroup>\n <Dialog.CloseButton>Cancel</Dialog.CloseButton>\n <Dialog.CloseButton as={PrimaryButton} onClick={handleEmail}>\n Submit\n </Dialog.CloseButton>\n </Dialog.ButtonGroup>\n </Dialog.Card>\n </Dialog.Popper>\n </Dialog>\n <PrimaryButton>Focus #1</PrimaryButton>\n <PrimaryButton>Focus #2</PrimaryButton>\n </Flex>\n );\n};\n```\n\n> **Accessibility Note**: Focus redirect **will not** have any effect on the reading order of a\n> screen reader.\n\n### Alt Example\n\nThe `alt` variant is designed for use on alternative page backgrounds\n(`system.color.bg.alt.default`). Use this variant to maintain proper visual hierarchy when placing\ncomponents on colored backgrounds. While the default variant should be used on\n`system.color.bg.default` backgrounds, the `alt` variant ensures the component remains visually\nelevated on `system.color.bg.alt.default` backgrounds.\n```tsx\nimport React from 'react';\n\nimport {SecondaryButton} from '@workday/canvas-kit-react/button';\nimport {Dialog} from '@workday/canvas-kit-react/dialog';\nimport {createStyles, px2rem} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst altBackgroundStyles = createStyles({\n background: system.color.bg.alt.default,\n padding: system.padding.xl,\n borderRadius: system.shape.md,\n minHeight: px2rem(400),\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n});\n\nexport const Alt = () => {\n return (\n <div className={altBackgroundStyles}>\n <Dialog>\n <Dialog.Target as={SecondaryButton}>Open Dialog</Dialog.Target>\n <Dialog.Popper>\n <Dialog.Card variant=\"alt\">\n <Dialog.CloseIcon aria-label=\"Close\" />\n <Dialog.Heading>Dialog with Alt Variant</Dialog.Heading>\n <Dialog.Body>\n This dialog uses the alt variant for proper contrast on colored backgrounds.\n </Dialog.Body>\n <Dialog.ButtonGroup>\n <Dialog.CloseButton as={SecondaryButton}>Cancel</Dialog.CloseButton>\n <Dialog.CloseButton>OK</Dialog.CloseButton>\n </Dialog.ButtonGroup>\n </Dialog.Card>\n </Dialog.Popper>\n </Dialog>\n </div>\n );\n};\n```\n\n## Accessibility\n\nEnsure users of assistive technology can discover, name, and operate a **non-modal** dialog: the\nrest of the page stays available (no inert background), the dialog has an accessible name that\nmatches its visible heading, keyboard users can open and dismiss it predictably, and screen reader\nreading order is improved where `aria-owns` is supported (see\n[Guides > Accessibility > Inline Popups](https://workday.github.io/canvas-kit/?path=/docs/guides-accessibility-inline-popups--docs)).\nFor blocking tasks, use\n[**Modal**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs) instead.\nPrefer **Dialog** for the standard non-modal dialog; use\n[**Popup**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-popup--docs) with\ncomposed hooks when you need a custom popup stack or behavior (for example omitting\n**`useInitialFocus`**). The W3C\n[Dialog (Modal) Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/) applies to\n[**Modal**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs); Dialog\nis intentionally non-modal.\n\n### Minimum Accessible Structure\n\nThe following matches the [Basic Example](#basic-example) layout: **`Dialog.CloseIcon`** before\n**`Dialog.Heading`** so open focus lands on the dismiss control first; primary actions use\n**`Dialog.CloseButton`** (which closes the dialog on activate).\n\n```tsx\n\n<Dialog>\n <Dialog.Target as={PrimaryButton}>Open</Dialog.Target>\n <Dialog.Popper>\n <Dialog.Card>\n <Dialog.CloseIcon aria-label=\"Close\" />\n <Dialog.Heading>Title</Dialog.Heading>\n <Dialog.Body>\n <FormField>\n <FormField.Label>Email</FormField.Label>\n <FormField.Input as={TextInput} />\n </FormField>\n </Dialog.Body>\n <Dialog.ButtonGroup>\n <Dialog.CloseButton>Cancel</Dialog.CloseButton>\n <Dialog.CloseButton as={PrimaryButton}>Submit</Dialog.CloseButton>\n </Dialog.ButtonGroup>\n </Dialog.Card>\n </Dialog.Popper>\n</Dialog>;\n```\n\nInclude a dismiss control: **`Dialog.CloseButton`** with visible text (for example \"Cancel\" or\n\"Close\"), and/or **`Dialog.CloseIcon`** when the design uses an icon-only dismiss (requires\n**`aria-label`** or **`Tooltip`**). Use **`Dialog.CloseButton`** for actions that should also close\nthe dialog (for example \"Submit\").\n\n### Built-in Behaviors\n\nCanvas Kit applies these automatically via `useDialogModel` and Dialog subcomponents. **Do not\nduplicate them** in consuming code.\n\n**Popup behaviors** (_composed on the default model_):\n\n- `useInitialFocus` \u2014 moves focus into the dialog when it opens (default: first focusable element in\n DOM order; optional override via `initialFocusRef` on the model)\n- `useReturnFocus` \u2014 returns focus to `Dialog.Target` (or configured return target) when it closes\n- `useCloseOnEscape` \u2014 <kbd>Escape</kbd> closes the dialog\n- `useCloseOnOutsideClick` \u2014 pointer interaction outside closes the dialog\n- `useFocusRedirect` \u2014 <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> at the first or last\n focusable element inside the dialog closes it and moves focus to the next or previous focusable\n element on the page (non-modal; **not** a focus trap; does **not** change screen reader reading\n order)\n\n**ARIA and DOM** (_applied by hooks/subcomponents_):\n\n- `Dialog.Card`: `role=\"dialog\"`, `aria-labelledby` referencing the heading `id` (non-modal; page\n content is not hidden with `aria-hidden`)\n- `Dialog.Popper`: sibling wrapper rendered when open with `aria-owns` pointing at `Dialog.Card` to\n remap the accessibility tree for sequential reading order in supported browsers\n- `Dialog.Heading`: `id` wired to `Dialog.Card`'s `aria-labelledby`\n- `Dialog.CloseIcon` / `Dialog.CloseButton`: `onClick` that calls `model.events.hide()`\n- `Dialog.Target`: `ref` and `onClick` to open and to receive return focus\n\n**Keyboard** (_trigger is `Dialog.Target`, default `SecondaryButton`_):\n\n- <kbd>Enter</kbd> / <kbd>Space</kbd> on the trigger opens the dialog (standard button behavior)\n- On open and close, focus is managed by **`useInitialFocus`** and **`useReturnFocus`** (application\n overrides: see **Focus management** in Accessibility Requirements)\n- <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> move focus forward and backward through\n interactive elements inside the dialog (standard sequential focus behavior)\n- <kbd>Escape</kbd> closes the dialog and returns focus per `useReturnFocus`\n\n**Screen reader expectations** (_when built-in behaviors are used as intended_):\n\n- On open, assistive technology should announce the first focused control (often a dismiss control),\n the dialog name (`Dialog.Heading`), and `dialog` role\n- Background page content remains available to assistive technology\u2014Dialog does **not** apply\n **`aria-hidden`** to siblings or render the rest of the page inert (unlike\n [**Modal**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs))\n- Reading order may follow on-screen order where `aria-owns` is honored; support varies by browser\n and screen reader\n\n### Accessibility Requirements\n\nRequired in application code for an accessible Dialog. Hoist **`useDialogModel`** when you need to\nconfigure focus targets. Rows marked _(conditional)_ apply only when the situation matches\u2014otherwise\nomit.\n\n**If no design spec is provided:** use default focus behavior; omit **`initialFocusRef`**,\n**`returnFocusRef`**, **`aria-describedby`**, **`aria-expanded`**, and **`aria-haspopup`**.\n\n**Focus management \u2014 defaults and developer prompts:** Canvas Kit handles open and close focus\nautomatically. **State the default to the developer first.** Only set **`initialFocusRef`** or\n**`returnFocusRef`** after the developer (or an explicit design spec) chooses a non-default target.\n**Do not generate focus refs by default.**\n\n| When | Default behavior | Ask the developer before overriding |\n| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| 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`**. |\n| 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`**.) |\n\nIf close **removes the trigger from the DOM**, **`returnFocusRef`** alone is not enough\u2014move focus\nafter the UI updates (for example with **`useLayoutEffect`**). See\n[Modal > Return Focus](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs#return-focus).\n\n**Custom targets** _(conditional)_: Apply when using a custom **`as`** component on\n**`Dialog.Target`**. **`Dialog.Target`** adds **`onClick`** and **`ref`**. Custom targets must\nforward both to a **keyboard-focusable** element (prefer a native **`<button>`** or\n**`as={SecondaryButton}`** / another Canvas Kit button). Wrap the component in\n**`React.forwardRef`** when it does not forward refs by default (required if the dialog can open\nprogrammatically before the user clicks the target).\n\n| Requirement | How to satisfy |\n| ------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| 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. |\n| 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`**). |\n| Keyboard-operable trigger | See **Custom targets** above. |\n| 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). |\n| Open/closed state on the trigger _(conditional)_ | See **Wiring aria-expanded** below. **Default:** omit **`aria-expanded`** and **`aria-haspopup`**. |\n\n**Open focus below the heading** _(conditional; see supplementary copy row above)_:\n\nWhen open focus moves past the heading (for example into a form field), wire **`aria-describedby`**\nso assistive technology still announces the supplementary copy. For focusing a primary action\ninstead of an input, see\n[Popup > Initial Focus](https://workday.github.io/canvas-kit/?path=/docs/components-popups-popup--docs#initial-focus).\n\n```tsx\n\nconst Example = () => {\n const descriptionId = useUniqueId();\n const inputRef = React.useRef<HTMLInputElement>(null);\n const model = useDialogModel({initialFocusRef: inputRef});\n\n return (\n <Dialog model={model}>\n <Dialog.Target>Open</Dialog.Target>\n <Dialog.Popper>\n <Dialog.Card aria-describedby={descriptionId}>\n <Dialog.CloseIcon aria-label=\"Close\" />\n <Dialog.Heading>Title</Dialog.Heading>\n <Dialog.Body>\n <p id={descriptionId}>Enter your email to continue.</p>\n <FormField>\n <FormField.Label>Email</FormField.Label>\n <FormField.Input as={TextInput} ref={inputRef} />\n </FormField>\n </Dialog.Body>\n <Dialog.CloseButton>Cancel</Dialog.CloseButton>\n </Dialog.Card>\n </Dialog.Popper>\n </Dialog>\n );\n};\n```\n\n**Summary for code generation:**\n\n- **REQUIRED:** accessible name, dismiss control, keyboard-operable trigger\n- **CONDITIONAL:** **`initialFocusRef`**, **`returnFocusRef`**, **`aria-describedby`**,\n **`aria-expanded`** / **`aria-haspopup`**, **`forwardRef`** on custom **`Dialog.Target`**\n\n**Wiring aria-expanded** _(conditional)_:\n\nThe **`aria-expanded`** pattern is **uncommon** for Dialog\u2014omit **`aria-expanded`** and\n**`aria-haspopup`** unless a review deliberately keeps open focus on the trigger (for example\n**`initialFocusRef`** on the trigger per design spec). When required, on **`Dialog.Target`** set\n**`aria-expanded={model.state.visibility !== 'hidden'}`** and **`aria-haspopup=\"dialog\"`**. See\n**Focus management** and the open/closed-state row above. If the design should not move focus into\nthe dialog on open, use\n[**Popup**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-popup--docs) with\ncomposed hooks instead of overriding Dialog defaults.\n\n### Anti-Patterns\n\nDo **not** generate code that does the following (see **Accessibility Requirements** above for what\nto supply instead):\n\n- Manually set `role=\"dialog\"`, `aria-labelledby`, `aria-owns`, or dialog `id` on **`Dialog.Card`**,\n **`Dialog.Popper`**, or **`Dialog.Heading`** \u2014 Canvas Kit hooks wire these\n- Omit **`Dialog.Popper`**, render **`Dialog.Card`** outside it, or add a custom portal/restructure\n instead of **`Dialog` \u2192 `Dialog.Popper` \u2192 `Dialog.Card`**\n- Use **`open`** / **`onClose`** props on **`Dialog`** \u2014 Dialog has no controlled visibility props;\n use **`useDialogModel`** and **`model.events.show()`** / **`model.events.hide()`**\n- Add **`useFocusTrap`**, **`aria-modal=\"true\"`**, or **`aria-hidden`** on page siblings expecting\n modal behavior \u2014 Dialog is intentionally non-modal\n- Use **Modal** when the task is non-critical or the rest of the page must stay operable\n- Set **`initialFocusRef`** or **`returnFocusRef`** by default \u2014 state the default focus behavior\n first and ask the developer before overriding (see **Focus management** in Accessibility\n Requirements)\n- Add **`aria-expanded`** / **`aria-haspopup`** on the default Dialog path, or bind\n **`aria-expanded`** to a static value (see **Wiring aria-expanded** in Accessibility Requirements)\n- Use a custom **`Dialog.Target`** **`as`** component that does not forward **`ref`** to a focusable\n element \u2014 use **`React.forwardRef`** or a Canvas Kit button component instead\n- Rely on **`returnFocusRef`** alone when close **removes the trigger from the DOM** (see\n [Modal > Return Focus](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs#return-focus))\n- Nest multiple **`Dialog`** instances without deliberate initial focus and return-focus planning\n- Assume **`useFocusRedirect`** fixes screen reader reading order, or that **`aria-owns`** remapping\n works in all browser and screen reader combinations \u2014 test your supported combinations\n\n## Component API\n\n",
|
|
519
|
+
accessibilityProse: '## Accessibility\n\nEnsure users of assistive technology can discover, name, and operate a **non-modal** dialog: the\nrest of the page stays available (no inert background), the dialog has an accessible name that\nmatches its visible heading, keyboard users can open and dismiss it predictably, and screen reader\nreading order is improved where `aria-owns` is supported (see\n[Guides > Accessibility > Inline Popups](https://workday.github.io/canvas-kit/?path=/docs/guides-accessibility-inline-popups--docs)).\nFor blocking tasks, use\n[**Modal**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs) instead.\nPrefer **Dialog** for the standard non-modal dialog; use\n[**Popup**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-popup--docs) with\ncomposed hooks when you need a custom popup stack or behavior (for example omitting\n**`useInitialFocus`**). The W3C\n[Dialog (Modal) Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/) applies to\n[**Modal**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs); Dialog\nis intentionally non-modal.\n\n### Minimum Accessible Structure\n\nThe following matches the [Basic Example](#basic-example) layout: **`Dialog.CloseIcon`** before\n**`Dialog.Heading`** so open focus lands on the dismiss control first; primary actions use\n**`Dialog.CloseButton`** (which closes the dialog on activate).\n\n```tsx\n\n<Dialog>\n <Dialog.Target as={PrimaryButton}>Open</Dialog.Target>\n <Dialog.Popper>\n <Dialog.Card>\n <Dialog.CloseIcon aria-label="Close" />\n <Dialog.Heading>Title</Dialog.Heading>\n <Dialog.Body>\n <FormField>\n <FormField.Label>Email</FormField.Label>\n <FormField.Input as={TextInput} />\n </FormField>\n </Dialog.Body>\n <Dialog.ButtonGroup>\n <Dialog.CloseButton>Cancel</Dialog.CloseButton>\n <Dialog.CloseButton as={PrimaryButton}>Submit</Dialog.CloseButton>\n </Dialog.ButtonGroup>\n </Dialog.Card>\n </Dialog.Popper>\n</Dialog>;\n```\n\nInclude a dismiss control: **`Dialog.CloseButton`** with visible text (for example "Cancel" or\n"Close"), and/or **`Dialog.CloseIcon`** when the design uses an icon-only dismiss (requires\n**`aria-label`** or **`Tooltip`**). Use **`Dialog.CloseButton`** for actions that should also close\nthe dialog (for example "Submit").\n\n### Built-in Behaviors\n\nCanvas Kit applies these automatically via `useDialogModel` and Dialog subcomponents. **Do not\nduplicate them** in consuming code.\n\n**Popup behaviors** (_composed on the default model_):\n\n- `useInitialFocus` \u2014 moves focus into the dialog when it opens (default: first focusable element in\n DOM order; optional override via `initialFocusRef` on the model)\n- `useReturnFocus` \u2014 returns focus to `Dialog.Target` (or configured return target) when it closes\n- `useCloseOnEscape` \u2014 <kbd>Escape</kbd> closes the dialog\n- `useCloseOnOutsideClick` \u2014 pointer interaction outside closes the dialog\n- `useFocusRedirect` \u2014 <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> at the first or last\n focusable element inside the dialog closes it and moves focus to the next or previous focusable\n element on the page (non-modal; **not** a focus trap; does **not** change screen reader reading\n order)\n\n**ARIA and DOM** (_applied by hooks/subcomponents_):\n\n- `Dialog.Card`: `role="dialog"`, `aria-labelledby` referencing the heading `id` (non-modal; page\n content is not hidden with `aria-hidden`)\n- `Dialog.Popper`: sibling wrapper rendered when open with `aria-owns` pointing at `Dialog.Card` to\n remap the accessibility tree for sequential reading order in supported browsers\n- `Dialog.Heading`: `id` wired to `Dialog.Card`\'s `aria-labelledby`\n- `Dialog.CloseIcon` / `Dialog.CloseButton`: `onClick` that calls `model.events.hide()`\n- `Dialog.Target`: `ref` and `onClick` to open and to receive return focus\n\n**Keyboard** (_trigger is `Dialog.Target`, default `SecondaryButton`_):\n\n- <kbd>Enter</kbd> / <kbd>Space</kbd> on the trigger opens the dialog (standard button behavior)\n- On open and close, focus is managed by **`useInitialFocus`** and **`useReturnFocus`** (application\n overrides: see **Focus management** in Accessibility Requirements)\n- <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> move focus forward and backward through\n interactive elements inside the dialog (standard sequential focus behavior)\n- <kbd>Escape</kbd> closes the dialog and returns focus per `useReturnFocus`\n\n**Screen reader expectations** (_when built-in behaviors are used as intended_):\n\n- On open, assistive technology should announce the first focused control (often a dismiss control),\n the dialog name (`Dialog.Heading`), and `dialog` role\n- Background page content remains available to assistive technology\u2014Dialog does **not** apply\n **`aria-hidden`** to siblings or render the rest of the page inert (unlike\n [**Modal**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs))\n- Reading order may follow on-screen order where `aria-owns` is honored; support varies by browser\n and screen reader\n\n### Accessibility Requirements\n\nRequired in application code for an accessible Dialog. Hoist **`useDialogModel`** when you need to\nconfigure focus targets. Rows marked _(conditional)_ apply only when the situation matches\u2014otherwise\nomit.\n\n**If no design spec is provided:** use default focus behavior; omit **`initialFocusRef`**,\n**`returnFocusRef`**, **`aria-describedby`**, **`aria-expanded`**, and **`aria-haspopup`**.\n\n**Focus management \u2014 defaults and developer prompts:** Canvas Kit handles open and close focus\nautomatically. **State the default to the developer first.** Only set **`initialFocusRef`** or\n**`returnFocusRef`** after the developer (or an explicit design spec) chooses a non-default target.\n**Do not generate focus refs by default.**\n\n| When | Default behavior | Ask the developer before overriding |\n| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| 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`**. |\n| 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`**.) |\n\nIf close **removes the trigger from the DOM**, **`returnFocusRef`** alone is not enough\u2014move focus\nafter the UI updates (for example with **`useLayoutEffect`**). See\n[Modal > Return Focus](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs#return-focus).\n\n**Custom targets** _(conditional)_: Apply when using a custom **`as`** component on\n**`Dialog.Target`**. **`Dialog.Target`** adds **`onClick`** and **`ref`**. Custom targets must\nforward both to a **keyboard-focusable** element (prefer a native **`<button>`** or\n**`as={SecondaryButton}`** / another Canvas Kit button). Wrap the component in\n**`React.forwardRef`** when it does not forward refs by default (required if the dialog can open\nprogrammatically before the user clicks the target).\n\n| Requirement | How to satisfy |\n| ------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| 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. |\n| 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`**). |\n| Keyboard-operable trigger | See **Custom targets** above. |\n| 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). |\n| Open/closed state on the trigger _(conditional)_ | See **Wiring aria-expanded** below. **Default:** omit **`aria-expanded`** and **`aria-haspopup`**. |\n\n**Open focus below the heading** _(conditional; see supplementary copy row above)_:\n\nWhen open focus moves past the heading (for example into a form field), wire **`aria-describedby`**\nso assistive technology still announces the supplementary copy. For focusing a primary action\ninstead of an input, see\n[Popup > Initial Focus](https://workday.github.io/canvas-kit/?path=/docs/components-popups-popup--docs#initial-focus).\n\n```tsx\n\nconst Example = () => {\n const descriptionId = useUniqueId();\n const inputRef = React.useRef<HTMLInputElement>(null);\n const model = useDialogModel({initialFocusRef: inputRef});\n\n return (\n <Dialog model={model}>\n <Dialog.Target>Open</Dialog.Target>\n <Dialog.Popper>\n <Dialog.Card aria-describedby={descriptionId}>\n <Dialog.CloseIcon aria-label="Close" />\n <Dialog.Heading>Title</Dialog.Heading>\n <Dialog.Body>\n <p id={descriptionId}>Enter your email to continue.</p>\n <FormField>\n <FormField.Label>Email</FormField.Label>\n <FormField.Input as={TextInput} ref={inputRef} />\n </FormField>\n </Dialog.Body>\n <Dialog.CloseButton>Cancel</Dialog.CloseButton>\n </Dialog.Card>\n </Dialog.Popper>\n </Dialog>\n );\n};\n```\n\n**Summary for code generation:**\n\n- **REQUIRED:** accessible name, dismiss control, keyboard-operable trigger\n- **CONDITIONAL:** **`initialFocusRef`**, **`returnFocusRef`**, **`aria-describedby`**,\n **`aria-expanded`** / **`aria-haspopup`**, **`forwardRef`** on custom **`Dialog.Target`**\n\n**Wiring aria-expanded** _(conditional)_:\n\nThe **`aria-expanded`** pattern is **uncommon** for Dialog\u2014omit **`aria-expanded`** and\n**`aria-haspopup`** unless a review deliberately keeps open focus on the trigger (for example\n**`initialFocusRef`** on the trigger per design spec). When required, on **`Dialog.Target`** set\n**`aria-expanded={model.state.visibility !== \'hidden\'}`** and **`aria-haspopup="dialog"`**. See\n**Focus management** and the open/closed-state row above. If the design should not move focus into\nthe dialog on open, use\n[**Popup**](https://workday.github.io/canvas-kit/?path=/docs/components-popups-popup--docs) with\ncomposed hooks instead of overriding Dialog defaults.\n\n### Anti-Patterns\n\nDo **not** generate code that does the following (see **Accessibility Requirements** above for what\nto supply instead):\n\n- Manually set `role="dialog"`, `aria-labelledby`, `aria-owns`, or dialog `id` on **`Dialog.Card`**,\n **`Dialog.Popper`**, or **`Dialog.Heading`** \u2014 Canvas Kit hooks wire these\n- Omit **`Dialog.Popper`**, render **`Dialog.Card`** outside it, or add a custom portal/restructure\n instead of **`Dialog` \u2192 `Dialog.Popper` \u2192 `Dialog.Card`**\n- Use **`open`** / **`onClose`** props on **`Dialog`** \u2014 Dialog has no controlled visibility props;\n use **`useDialogModel`** and **`model.events.show()`** / **`model.events.hide()`**\n- Add **`useFocusTrap`**, **`aria-modal="true"`**, or **`aria-hidden`** on page siblings expecting\n modal behavior \u2014 Dialog is intentionally non-modal\n- Use **Modal** when the task is non-critical or the rest of the page must stay operable\n- Set **`initialFocusRef`** or **`returnFocusRef`** by default \u2014 state the default focus behavior\n first and ask the developer before overriding (see **Focus management** in Accessibility\n Requirements)\n- Add **`aria-expanded`** / **`aria-haspopup`** on the default Dialog path, or bind\n **`aria-expanded`** to a static value (see **Wiring aria-expanded** in Accessibility Requirements)\n- Use a custom **`Dialog.Target`** **`as`** component that does not forward **`ref`** to a focusable\n element \u2014 use **`React.forwardRef`** or a Canvas Kit button component instead\n- Rely on **`returnFocusRef`** alone when close **removes the trigger from the DOM** (see\n [Modal > Return Focus](https://workday.github.io/canvas-kit/?path=/docs/components-popups-modal--docs#return-focus))\n- Nest multiple **`Dialog`** instances without deliberate initial focus and return-focus planning\n- Assume **`useFocusRedirect`** fixes screen reader reading order, or that **`aria-owns`** remapping\n works in all browser and screen reader combinations \u2014 test your supported combinations'
|
|
520
520
|
},
|
|
521
521
|
checkbox: {
|
|
522
522
|
title: "Components/Inputs/Checkbox",
|