Dialog
Dialogs are interactive pop-ups reserved for situations which do not require immediate attention.
Component Type
Popup
Platform
Web
Component
Sana Canvas
Delivery Channels
Web
Version
16.1.7Experience Surfaces
Page Body Inline
Anatomy

- Card: The Card contains content for a Dialog, it uses the depth 6 token for the drop-shadow styling.
- Heading (Optional): Heading should display the title of the content or task.
- Body: Dialogs contain many different types of content in the body. Typical types of content include media, alerts, dialogs, and/or task-oriented flows.
- In-line Buttons (Optional): Action should be at the bottom of the container when used. There are multiple alignments available for use; Left (Default), Center, Full Width & Full Width Stacked, or Right aligned.
- Close “X” Icon (Optional): Users must be able to intentionally dismiss a Dialog. This icon inherits styling and interactions from our Tertiary Icon-Only Button Variant.
Usage Guidance
- Dialogs allow for entry of data or alert users on any given page after an action has been initiated and it doesn’t require immediate attention.
- On web platforms with browser windows 767px or wider, Dialogs show up next to the button that activated it.
- On web platforms with browser windows less than 767px width, Dialogs show up at the bottom of the screen and in front of an overlay.
- Dialogs are often used to display media, alerts, dialogs, and/or task-oriented flows. Links, buttons, field sets, icons, text inputs, and prompts can all exist within Dialogs.
- In-line buttons used in Dialogs can be aligned Left (Default), Center, Full Width & Full Width Stacked, or Right aligned.
When to Use
- Use Dialog to gather input from the user without blocking interaction with the rest of the page.
- Use Dialog when alert content and text are too large for a standard Toast or Pop-up notification.
When to Use Something Else
- Use Modal to gather immediate input from the user by blocking interaction with the rest of the page.
- Do not use Dialogs to serve up easily accessible links or simple messages that can be dismissed quickly (use Toasts or Popups for this).
- Do not use Dialogs to display dense information, such as Tables or Multi-View Containers.
- Consider a Toast if you are communicating status or confirmation of the application process to the user.
- Consider a Menu if the input is a single selection of options.
Responsive View
Dialog components adjust width and content presentation based on screen size. When content exceeds the length of the screen, the Dialog content will become scrollable in the body section of the Dialog. For long content on a small screen, inline buttons will continue to scroll with the content.
Touch Based Behavior
The overlay on Dialogs are not click or touch enabled to close the Dialog component view on small screens between 320-767px. This accounts for accidental touch on mobile devices. Background overlays will close the Dialog when clicked on larger devices when the screen reaches the minimum width.
Examples
Basic Example
The following example shows a typical Dialog with heading, close control, and form content.
import React from 'react';
import {PrimaryButton} from '@workday/canvas-kit-react/button';
import {Dialog} from '@workday/canvas-kit-react/dialog';
import {FormField} from '@workday/canvas-kit-react/form-field';
import {Flex} from '@workday/canvas-kit-react/layout';
import {TextInput} from '@workday/canvas-kit-react/text-input';
import {system} from '@workday/canvas-tokens-web';
export default () => {
const [value, setValue] = React.useState('');
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setValue(event.target.value);
};
const handleEmail = () => {
console.log('Email Submitted');
};
return (
<Dialog>
<Dialog.Target as={PrimaryButton}>Open for Offer</Dialog.Target>
<Dialog.Popper>
<Dialog.Card>
<Dialog.CloseIcon aria-label="Close" />
<Dialog.Heading cs={{paddingBlockStart: system.padding.md}}>
Sign Up for 15% Off Your Next Order
</Dialog.Heading>
<Dialog.Body>
<FormField>
<FormField.Label>Email</FormField.Label>
<FormField.Input as={TextInput} grow onChange={handleChange} value={value} />
</FormField>
</Dialog.Body>
<Dialog.ButtonGroup>
<Dialog.CloseButton>Cancel</Dialog.CloseButton>
<Dialog.CloseButton as={PrimaryButton} onClick={handleEmail}>
Submit
</Dialog.CloseButton>
</Dialog.ButtonGroup>
</Dialog.Card>
</Dialog.Popper>
</Dialog>
);
};
Focus Redirect
Dialog does not trap keyboard focus like the Modal component does. The default useDialogModel
composes useFocusRedirect: Tab / Shift+Tab at the last or first
focusable element inside the dialog closes it and moves focus to the next or previous focusable
element on the page. Dialog is non-modal and is not a focus trap; it does not change screen
reader reading order. The following example shows how Dialog manages focus at those edges.
import React from 'react';
import {PrimaryButton} from '@workday/canvas-kit-react/button';
import {Dialog} from '@workday/canvas-kit-react/dialog';
import {FormField} from '@workday/canvas-kit-react/form-field';
import {Flex} from '@workday/canvas-kit-react/layout';
import {TextInput} from '@workday/canvas-kit-react/text-input';
import {system} from '@workday/canvas-tokens-web';
export default () => {
const [value, setValue] = React.useState('');
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setValue(event.target.value);
};
const handleEmail = () => {
console.log('Email Submitted');
};
return (
<Flex cs={{gap: system.gap.lg}}>
<Dialog>
<Dialog.Target as={PrimaryButton}>Open for Offer</Dialog.Target>
<Dialog.Popper>
<Dialog.Card>
<Dialog.CloseIcon aria-label="Close" />
<Dialog.Heading cs={{paddingBlockStart: system.padding.md}}>
Sign Up for 15% Off Your Next Order
</Dialog.Heading>
<Dialog.Body>
<FormField>
<FormField.Label>Email</FormField.Label>
<FormField.Input as={TextInput} grow onChange={handleChange} value={value} />
</FormField>
</Dialog.Body>
<Dialog.ButtonGroup>
<Dialog.CloseButton>Cancel</Dialog.CloseButton>
<Dialog.CloseButton as={PrimaryButton} onClick={handleEmail}>
Submit
</Dialog.CloseButton>
</Dialog.ButtonGroup>
</Dialog.Card>
</Dialog.Popper>
</Dialog>
<PrimaryButton>Focus #1</PrimaryButton>
<PrimaryButton>Focus #2</PrimaryButton>
</Flex>
);
};
Accessibility Note: Focus redirect will not have any effect on the reading order of a screen reader.
Alt Example
The alt variant is designed for use on alternative page backgrounds
(system.color.bg.alt.default). Use this variant to maintain proper visual hierarchy when placing
components on colored backgrounds. While the default variant should be used on
system.color.bg.default backgrounds, the alt variant ensures the component remains visually
elevated on system.color.bg.alt.default backgrounds.
import React from 'react';
import {SecondaryButton} from '@workday/canvas-kit-react/button';
import {Dialog} from '@workday/canvas-kit-react/dialog';
import {createStyles, px2rem} from '@workday/canvas-kit-styling';
import {system} from '@workday/canvas-tokens-web';
const altBackgroundStyles = createStyles({
background: system.color.bg.alt.default,
padding: system.padding.xl,
borderRadius: system.shape.md,
minHeight: px2rem(400),
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
});
export default () => {
return (
<div className={altBackgroundStyles}>
<Dialog>
<Dialog.Target as={SecondaryButton}>Open Dialog</Dialog.Target>
<Dialog.Popper>
<Dialog.Card variant="alt">
<Dialog.CloseIcon aria-label="Close" />
<Dialog.Heading>Dialog with Alt Variant</Dialog.Heading>
<Dialog.Body>
This dialog uses the alt variant for proper contrast on colored backgrounds.
</Dialog.Body>
<Dialog.ButtonGroup>
<Dialog.CloseButton as={SecondaryButton}>Cancel</Dialog.CloseButton>
<Dialog.CloseButton>OK</Dialog.CloseButton>
</Dialog.ButtonGroup>
</Dialog.Card>
</Dialog.Popper>
</Dialog>
</div>
);
};
Accessibility
Ensure users of assistive technology can discover, name, and operate a non-modal dialog: the
rest of the page stays available (no inert background), the dialog has an accessible name that
matches its visible heading, keyboard users can open and dismiss it predictably, and screen reader
reading order is improved where aria-owns is supported (see
Guides > Accessibility > Inline Popups ).
For blocking tasks, use
Modal instead.
Prefer Dialog for the standard non-modal dialog; use
Popup with
composed hooks when you need a custom popup stack or behavior (for example omitting
useInitialFocus). The W3C
Dialog (Modal) Pattern applies to
Modal; Dialog
is intentionally non-modal.
Minimum Accessible Structure
The following matches the Basic Example layout: Dialog.CloseIcon before
Dialog.Heading so open focus lands on the dismiss control first; primary actions use
Dialog.CloseButton (which closes the dialog on activate).
import {PrimaryButton} from '@workday/canvas-kit-react/button';
import {Dialog} from '@workday/canvas-kit-react/dialog';
import {FormField} from '@workday/canvas-kit-react/form-field';
import {TextInput} from '@workday/canvas-kit-react/text-input';
<Dialog>
<Dialog.Target as={PrimaryButton}>Open</Dialog.Target>
<Dialog.Popper>
<Dialog.Card>
<Dialog.CloseIcon aria-label="Close" />
<Dialog.Heading>Title</Dialog.Heading>
<Dialog.Body>
<FormField>
<FormField.Label>Email</FormField.Label>
<FormField.Input as={TextInput} />
</FormField>
</Dialog.Body>
<Dialog.ButtonGroup>
<Dialog.CloseButton>Cancel</Dialog.CloseButton>
<Dialog.CloseButton as={PrimaryButton}>Submit</Dialog.CloseButton>
</Dialog.ButtonGroup>
</Dialog.Card>
</Dialog.Popper>
</Dialog>;Include a dismiss control: Dialog.CloseButton with visible text (for example “Cancel” or
“Close”), and/or Dialog.CloseIcon when the design uses an icon-only dismiss (requires
aria-label or Tooltip). Use Dialog.CloseButton for actions that should also close
the dialog (for example “Submit”).
Built-in Behaviors
Canvas Kit applies these automatically via useDialogModel and Dialog subcomponents. Do not
duplicate them in consuming code.
Popup behaviors (composed on the default model):
useInitialFocus— moves focus into the dialog when it opens (default: first focusable element in DOM order; optional override viainitialFocusRefon the model)useReturnFocus— returns focus toDialog.Target(or configured return target) when it closesuseCloseOnEscape— Escape closes the dialoguseCloseOnOutsideClick— pointer interaction outside closes the dialoguseFocusRedirect— Tab / Shift+Tab at the first or last focusable element inside the dialog closes it and moves focus to the next or previous focusable element on the page (non-modal; not a focus trap; does not change screen reader reading order)
ARIA and DOM (applied by hooks/subcomponents):
Dialog.Card:role="dialog",aria-labelledbyreferencing the headingid(non-modal; page content is not hidden witharia-hidden)Dialog.Popper: sibling wrapper rendered when open witharia-ownspointing atDialog.Cardto remap the accessibility tree for sequential reading order in supported browsersDialog.Heading:idwired toDialog.Card’saria-labelledbyDialog.CloseIcon/Dialog.CloseButton:onClickthat callsmodel.events.hide()Dialog.Target:refandonClickto open and to receive return focus
Keyboard (trigger is Dialog.Target, default SecondaryButton):
- Enter / Space on the trigger opens the dialog (standard button behavior)
- On open and close, focus is managed by
useInitialFocusanduseReturnFocus(application overrides: see Focus management in Accessibility Requirements) - Tab / Shift+Tab move focus forward and backward through interactive elements inside the dialog (standard sequential focus behavior)
- Escape closes the dialog and returns focus per
useReturnFocus
Screen reader expectations (when built-in behaviors are used as intended):
- On open, assistive technology should announce the first focused control (often a dismiss control),
the dialog name (
Dialog.Heading), anddialogrole - Background page content remains available to assistive technology—Dialog does not apply
aria-hiddento siblings or render the rest of the page inert (unlike Modal) - Reading order may follow on-screen order where
aria-ownsis honored; support varies by browser and screen reader
Accessibility Requirements
Required in application code for an accessible Dialog. Hoist useDialogModel when you need to
configure focus targets. Rows marked (conditional) apply only when the situation matches—otherwise
omit.
If no design spec is provided: use default focus behavior; omit initialFocusRef,
returnFocusRef, aria-describedby, aria-expanded, and aria-haspopup.
Focus management — defaults and developer prompts: Canvas Kit handles open and close focus
automatically. State the default to the developer first. Only set initialFocusRef or
returnFocusRef after the developer (or an explicit design spec) chooses a non-default target.
Do not generate focus refs by default.
| When | Default behavior | Ask the developer before overriding |
|---|---|---|
| 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. |
| 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.) |
If close removes the trigger from the DOM, returnFocusRef alone is not enough—move focus
after the UI updates (for example with useLayoutEffect). See
Modal > Return Focus .
Custom targets (conditional): Apply when using a custom as component on
Dialog.Target. Dialog.Target adds onClick and ref. Custom targets must
forward both to a keyboard-focusable element (prefer a native <button> or
as={SecondaryButton} / another Canvas Kit button). Wrap the component in
React.forwardRef when it does not forward refs by default (required if the dialog can open
programmatically before the user clicks the target).
| Requirement | How to satisfy |
|---|---|
| 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. |
| 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). |
| Keyboard-operable trigger | See Custom targets above. |
| 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 (button-focus variant). |
| Open/closed state on the trigger (conditional) | See Wiring aria-expanded below. Default: omit aria-expanded and aria-haspopup. |
Open focus below the heading (conditional; see supplementary copy row above):
When open focus moves past the heading (for example into a form field), wire aria-describedby
so assistive technology still announces the supplementary copy. For focusing a primary action
instead of an input, see
Popup > Initial Focus .
import React from 'react';
import {useUniqueId} from '@workday/canvas-kit-react/common';
import {Dialog, useDialogModel} from '@workday/canvas-kit-react/dialog';
import {FormField} from '@workday/canvas-kit-react/form-field';
import {TextInput} from '@workday/canvas-kit-react/text-input';
const Example = () => {
const descriptionId = useUniqueId();
const inputRef = React.useRef<HTMLInputElement>(null);
const model = useDialogModel({initialFocusRef: inputRef});
return (
<Dialog model={model}>
<Dialog.Target>Open</Dialog.Target>
<Dialog.Popper>
<Dialog.Card aria-describedby={descriptionId}>
<Dialog.CloseIcon aria-label="Close" />
<Dialog.Heading>Title</Dialog.Heading>
<Dialog.Body>
<p id={descriptionId}>Enter your email to continue.</p>
<FormField>
<FormField.Label>Email</FormField.Label>
<FormField.Input as={TextInput} ref={inputRef} />
</FormField>
</Dialog.Body>
<Dialog.CloseButton>Cancel</Dialog.CloseButton>
</Dialog.Card>
</Dialog.Popper>
</Dialog>
);
};Summary for code generation:
- REQUIRED: accessible name, dismiss control, keyboard-operable trigger
- CONDITIONAL:
initialFocusRef,returnFocusRef,aria-describedby,aria-expanded/aria-haspopup,forwardRefon customDialog.Target
Wiring aria-expanded (conditional):
The aria-expanded pattern is uncommon for Dialog—omit aria-expanded and
aria-haspopup unless a review deliberately keeps open focus on the trigger (for example
initialFocusRef on the trigger per design spec). When required, on Dialog.Target set
aria-expanded={model.state.visibility !== 'hidden'} and aria-haspopup="dialog". See
Focus management and the open/closed-state row above. If the design should not move focus into
the dialog on open, use
Popup with
composed hooks instead of overriding Dialog defaults.
Anti-Patterns
Do not generate code that does the following (see Accessibility Requirements above for what to supply instead):
- Manually set
role="dialog",aria-labelledby,aria-owns, or dialogidonDialog.Card,Dialog.Popper, orDialog.Heading— Canvas Kit hooks wire these - Omit
Dialog.Popper, renderDialog.Cardoutside it, or add a custom portal/restructure instead ofDialog→Dialog.Popper→Dialog.Card - Use
open/onCloseprops onDialog— Dialog has no controlled visibility props; useuseDialogModelandmodel.events.show()/model.events.hide() - Add
useFocusTrap,aria-modal="true", oraria-hiddenon page siblings expecting modal behavior — Dialog is intentionally non-modal - Use Modal when the task is non-critical or the rest of the page must stay operable
- Set
initialFocusReforreturnFocusRefby default — state the default focus behavior first and ask the developer before overriding (see Focus management in Accessibility Requirements) - Add
aria-expanded/aria-haspopupon the default Dialog path, or bindaria-expandedto a static value (see Wiring aria-expanded in Accessibility Requirements) - Use a custom
Dialog.Targetascomponent that does not forwardrefto a focusable element — useReact.forwardRefor a Canvas Kit button component instead - Rely on
returnFocusRefalone when close removes the trigger from the DOM (see Modal > Return Focus ) - Nest multiple
Dialoginstances without deliberate initial focus and return-focus planning - Assume
useFocusRedirectfixes screen reader reading order, or thataria-ownsremapping works in all browser and screen reader combinations — test your supported combinations
Component API
Dialog
This component is the container component and does not render any semantic elements. It provides
a React Context model for the Dialog subcomponents. If you manually pass a model to all
subcomponents, this container component isn't needed. If you do not pass a model, the Dialog
container component will build a default one using useDialogModel. Dialog is a composition of a
component and has a similar structure to Popup.
Props
Props extend from . If a model is passed, props from DialogModelConfig are ignored.
| Name | Type | Description | Default |
|---|---|---|---|
model | | Optional model to pass to the component. This will override the default model created for the component. This can be useful if you want to access to the state and events of the model, or if you have nested components of the same type and you need to override the model provided by React Context. | |
elemPropsHook | ( | Optional hook that receives the model and all props to be applied to the element. If you use this, it is your responsibility to return props, merging as appropriate. For example, returning an empty object will disable all elemProps hooks associated with this component. This allows finer control over a component without creating a new one. |
Dialog.Card
A Dialog.Card is a wrapper around the component, but hooked up to a
. By default, this element has a role=dialog, aria-labelledby and an id.
The behavior hook used is called .
Layout Component
Dialog.Card supports all props from thelayout component.
Props
Props extend from div. Changing the as prop will change the element interface.
| Name | Type | Description | Default |
|---|---|---|---|
children | ReactNode | Children of the Card. Should contain a | |
variant | 'alt' | 'tonal' | The variant of the Card. Can be | 'default' |
cs | | The | |
as | React.ElementType | Optional override of the default element used by the component. Any valid tag or Component. If you provided a Component, this component should forward the ref using Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care. | div |
ref | React.Ref<R = div> | Optional ref. If the component represents an element, this ref will be a reference to the real DOM element of the component. If | |
model | | Optional model to pass to the component. This will override the default model created for the component. This can be useful if you want to access to the state and events of the model, or if you have nested components of the same type and you need to override the model provided by React Context. | |
elemPropsHook | ( | Optional hook that receives the model and all props to be applied to the element. If you use this, it is your responsibility to return props, merging as appropriate. For example, returning an empty object will disable all elemProps hooks associated with this component. This allows finer control over a component without creating a new one. |
Dialog.Popper
A Dialog.Popper is a wrapper around . The behavior
hook used is called .
Props
Props extend from div. Changing the as prop will change the element interface.
| Name | Type | Description | Default |
|---|---|---|---|
placement | | The placement of the | |
fallbackPlacements | [] | Define fallback placements by providing a list of | |
popperOptions | <PopperOptions> | The additional options passed to the Popper's | |
anchorElement | <Element> | Element | null | The reference element used to position the Popper. Popper content will try to follow the
| |
children | ((props: { | The content of the Popper. If a function is provided, it will be treated as a Render Prop and
pass the | |
getAnchorClientRect | () => | When provided, this optional callback will be used to determine positioning for the Popper element
instead of calling | |
open | boolean | Determines if | true |
onPlacementChange | (placement: ) => void | A callback function that will be called whenever PopperJS chooses a placement that is different
from the provided | |
portal | boolean | If false, render the Popper within the
DOM hierarchy of its parent. A non-portal Popper will constrained by the parent container
overflows. If you set this to | true |
popperInstanceRef | Ref<> | Reference to the PopperJS instance. Useful for making direct method calls on the popper
instance like | |
as | React.ElementType | Optional override of the default element used by the component. Any valid tag or Component. If you provided a Component, this component should forward the ref using Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care. | div |
ref | React.Ref<R = div> | Optional ref. If the component represents an element, this ref will be a reference to the real DOM element of the component. If | |
model | | Optional model to pass to the component. This will override the default model created for the component. This can be useful if you want to access to the state and events of the model, or if you have nested components of the same type and you need to override the model provided by React Context. | |
elemPropsHook | ( | Optional hook that receives the model and all props to be applied to the element. If you use this, it is your responsibility to return props, merging as appropriate. For example, returning an empty object will disable all elemProps hooks associated with this component. This allows finer control over a component without creating a new one. |