Dialog
Dialogs are windows containing contextual information, tasks, or workflows that appear over the user interface. Depending on the kind of Dialog, further interactions may be blocked until the Dialog is acknowledged.
install | yarn add @react-spectrum/dialog |
---|---|
version | 3.0.0-alpha.1 |
usage | import {Dialog} from '@react-spectrum/dialog' |
Example#
import {ActionButton Button} from '@react-spectrum/button';
import {ButtonGroup} from '@react-spectrum/buttongroup';
import {Content Header} from '@react-spectrum/view';
import {Dialog DialogTrigger} from '@react-spectrum/dialog';
import {Divider} from '@react-spectrum/divider';
import {Heading Text} from '@react-spectrum/typography';
<DialogTrigger>
<ActionButton>Check connectivity</ActionButton>
(close) => (
<Dialog>
<Heading>Internet Speed Test</Heading>
<Header>Connection status: Connected</Header>
<Divider />
<Content>
<Text>
Start speed test?
</Text>
</Content>
<ButtonGroup>
<Button variant="secondary" onPress= close>Cancel</Button>
<Button variant="cta" onPress= close>Confirm</Button>
</ButtonGroup>
</Dialog>
)
</DialogTrigger>
Content#
A standard Dialog has the following anatomy:
These sections can be populated by providing the following components to your Dialog as children:
Header
, Heading
(title), Divider
, Content
(body), ButtonGroup
, and Footer
. Each of these components are
required in a Spectrum compliant Dialog except for Header
, Footer
, and Divider
so be sure to design your Dialog accordingly.
Examples#
A typical Dialog with a title, contents, and action buttons can be created like so:
<DialogTrigger>
<ActionButton>Publish</ActionButton>
(close) => (
<Dialog>
<Heading>Publish 3 pages</Heading>
<Divider />
<Content>Confirm publish?</Content>
<ButtonGroup>
<Button variant="secondary" onPress= close>Cancel</Button>
<Button variant="cta" onPress= close autoFocus>Confirm</Button>
</ButtonGroup>
</Dialog>
)
</DialogTrigger>
A dismissable Dialog forgoes its ButtonGroup in favor of rendering a close button at the top right of the Dialog. If your Dialog is dismissable, make sure that you do not include a ButtonGroup in the render. While the Dialog will hide any elements occupying the ButtonGroup section, it will not remove them from the DOM, resulting in possible tab order issues.
<DialogTrigger isDismissable>
<ActionButton>Status</ActionButton>
<Dialog>
<Heading>Status</Heading>
<Divider />
<Content>Printer Status: Connected</Content>
</Dialog>
</DialogTrigger>
It is important to note that the Heading
, Header
, Content
, and Footer
content elements accept any renderable
node, not just strings. This allows you to create Dialogs for more complex workflows, such as including a form
for the user to fill out or adding confirmation checkboxes.
<DialogTrigger>
<ActionButton>Register</ActionButton>
(close) => (
<Dialog>
<Heading>
<Flex alignItems="center">
<Book size="S" />
<Text>
Register for newsletter
</Text>
</Flex>
</Heading>
<Header>
<Link>
<a href="//example.com" target="_blank">What is this?</a>
</Link>
</Header>
<Divider />
<Content>
<Form>
<TextField label="First Name" placeholder="John" autoFocus />
<TextField label="Last Name" placeholder="Smith" />
<TextField label="Street Address" placeholder="123 Any Street" />
<TextField label="City" placeholder="San Francisco" />
</Form>
</Content>
<Footer>
<Checkbox>
I want to receive updates for exclusive offers in my area.
</Checkbox>
</Footer>
<ButtonGroup>
<Button variant="secondary" onPress= close>Cancel</Button>
<Button variant="cta" onPress= close>Register</Button>
</ButtonGroup>
</Dialog>
)
</DialogTrigger>
The example below illustrates how a Dialog with a hero image could be rendered via the hero
slot:
<DialogTrigger>
<ActionButton>Upload</ActionButton>
(close) => (
<Dialog>
<Image slot="hero" src="https://i.imgur.com/Z7AzH2c.png" objectFit="cover" />
<Heading>Upload file</Heading>
<Divider />
<Content>Are you sure you want to upload this file?</Content>
<ButtonGroup>
<Button variant="secondary" onPress= close>Cancel</Button>
<Button variant="cta" onPress= close autoFocus>Confirm</Button>
</ButtonGroup>
</Dialog>
)
</DialogTrigger>
Accessibility#
Keep in mind when creating your Dialog that the tab order within the Dialog follows the order of the children provided. You are also responsible for determining what component, if any, should be automatically focused on Dialog render.
Labeling#
Accessibility#
The title of a Dialog is typically provided via its heading. By default, the Dialog sets its
aria-labelledby
to match the heading id, but this can be overridden by providing an aria-labelledby
prop
to the Dialog directly. If a visible label isn't specified, an aria-label
must be provided instead.
<DialogTrigger>
<ActionButton>See Terms and Conditions</ActionButton>
(close) => (
<Dialog aria-labelledby="heading-id">
<Text slot="header" id="heading-id">Terms and Conditions</Text>
<Divider />
<Content>
<Text>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Mi proin sed libero enim. Mattis ullamcorper velit sed ullamcorper morbi tincidunt. Sed enim ut sem viverra aliquet eget sit amet tellus. Diam quis enim lobortis scelerisque fermentum dui faucibus in ornare. Diam quam nulla porttitor massa id. Eleifend mi in nulla posuere sollicitudin. Turpis nunc eget lorem dolor sed viverra ipsum nunc. Faucibus in ornare quam viverra. Risus commodo viverra maecenas accumsan lacus vel facilisis volutpat est. Nam libero justo laoreet sit amet cursus sit. Netus et malesuada fames ac. Dictum fusce ut placerat orci nulla pellentesque dignissim enim sit. Eros donec ac odio tempor orci. Ut etiam sit amet nisl purus in mollis nunc. Nisl rhoncus mattis rhoncus urna neque viverra. Convallis aenean et tortor at risus. Diam phasellus vestibulum lorem sed risus ultricies.</Text>
</Content>
<ButtonGroup>
<Button autoFocus variant="cta" onPress= close>Acknowledge</Button>
</ButtonGroup>
</Dialog>
)
</DialogTrigger>
Events#
For Dialogs, user defined callbacks should be chained with the DialogTrigger's close
function in the press/click handler
of the Dialog's action buttons. The following example alerts if the Dialog's save or cancel button is clicked.
function Example() {
let alertSave = (cb) => {
cb();
alert('Profile saved!');
}
let alertCancel = (cb) => {
cb();
alert('Profile not saved!');
}
return (
<DialogTrigger>
<ActionButton>Set Profile</ActionButton>
(close) => (
<Dialog>
<Heading>Profile</Heading>
<Divider />
<ButtonGroup>
<Button autoFocus variant="cta" onPress=() => alertSave(close)>Save</Button>
<Button variant="secondary" onPress=() => alertCancel(close)>Cancel</Button>
</ButtonGroup>
<Content>
<Form>
<TextField label="Name" />
<Checkbox>Make private</Checkbox>
</Form>
</Content>
</Dialog>
)
</DialogTrigger>
);
}
Additionally, DialogTrigger accepts an onOpenChange
prop which is triggered whenever the Dialog is opened or closed.
For more information on the onOpenChange
prop see the DialogTrigger docs.
Dismissable dialogs#
Dismissable Dialogs support an onDismiss
prop which is triggered whenever the Dialog's close button is clicked.
Similar to non-dismissable dialogs, you must chain the DialogTrigger's close
function with whatever callback you provide as
onDismiss
.
function Example() {
let alertDismiss = (cb) => {
cb();
alert('Dialog dismissed.');
}
return (
<DialogTrigger isDismissable>
<ActionButton>Info</ActionButton>
(close) => (
<Dialog onDismiss=() => alertDismiss(close)>
<Heading>Version Info</Heading>
<Divider />
<Content>
<Text>
Version 1.0.0, Copyright 2020
</Text>
</Content>
</Dialog>
)
</DialogTrigger>
);
}
Note that onDismiss
is optional. If you don't need to add a onDismiss
handler to your dismissable Dialog, you may omit
the wrapping close
function surrounding the Dialog. An example of this can be found in the Examples section above.
Props#
Name | Type | Default | Description |
children | ReactNode | — | The contents of the Dialog. |
size | 'S' | 'M' | 'L' | — | The size of the Dialog. Only applies to "modal" type Dialogs. |
isDismissable | boolean | — | Whether the Dialog is dismissable. See the examples for more details. |
UNSAFE_className | string | — | |
UNSAFE_style | CSSProperties | — |
Events
Name | Type | Default | Description |
onDismiss | () => void | — | Handler that is called when the 'x' button of a dismissable Dialog is clicked. |
Layout
Name | Type | Default | Description |
flex | string | number | boolean | — | |
flexGrow | number | — | |
flexShrink | number | — | |
flexBasis | number | string | — | |
alignSelf | 'auto'
| 'normal'
| 'start'
| 'end'
| 'flex-start'
| 'flex-end'
| 'self-start'
| 'self-end'
| 'center'
| 'stretch' | — | |
justifySelf | 'auto'
| 'normal'
| 'start'
| 'end'
| 'flex-start'
| 'flex-end'
| 'self-start'
| 'self-end'
| 'center'
| 'left'
| 'right'
| 'stretch' | — | |
flexOrder | number | — | |
gridArea | string | — | |
gridColumn | string | — | |
gridRow | string | — | |
gridColumnStart | string | — | |
gridColumnEnd | string | — | |
gridRowStart | string | — | |
gridRowEnd | string | — |
Spacing
Name | Type | Default | Description |
margin | DimensionValue | — | |
marginTop | DimensionValue | — | |
marginLeft | DimensionValue | — | |
marginRight | DimensionValue | — | |
marginBottom | DimensionValue | — | |
marginStart | DimensionValue | — | |
marginEnd | DimensionValue | — | |
marginX | DimensionValue | — | |
marginY | DimensionValue | — |
Sizing
Name | Type | Default | Description |
width | DimensionValue | — | |
minWidth | DimensionValue | — | |
maxWidth | DimensionValue | — | |
height | DimensionValue | — | |
minHeight | DimensionValue | — | |
maxHeight | DimensionValue | — |
Positioning
Name | Type | Default | Description |
position | 'static'
| 'relative'
| 'absolute'
| 'fixed'
| 'sticky' | — | |
top | DimensionValue | — | |
bottom | DimensionValue | — | |
left | DimensionValue | — | |
right | DimensionValue | — | |
start | DimensionValue | — | |
end | DimensionValue | — | |
zIndex | number | — | |
isHidden | boolean | — |
Accessibility
Name | Type | Default | Description |
role | 'dialog' | 'alertdialog' | — | The role of the Dialog. |
id | string | — | |
tabIndex | number | — | |
aria-label | string | — | Defines a string value that labels the current element. |
aria-labelledby | string | — | Identifies the element (or elements) that labels the current element. |
aria-describedby | string | — | Identifies the element (or elements) that describes the object. |
aria-controls | string | — | Identifies the element (or elements) whose contents or presence are controlled by the current element. |
aria-owns | string | — | Identifies an element (or elements) in order to define a visual, functional, or contextual parent/child relationship between DOM elements where the DOM hierarchy cannot be used to represent the relationship. |
aria-hidden | boolean | 'false' | 'true' | — | Indicates that the element is perceivable but disabled, so it is not editable or otherwise operable. |
Visual options#
Dialog types#
Dialogs can be rendered as modals, popovers, or trays. See the DialogTrigger docs for more information.
<DialogTrigger isDismissable type="modal">
<ActionButton>Trigger Modal</ActionButton>
<Dialog>
<Heading>Modal</Heading>
<Divider />
<Content>
<Text>
This is a modal.
</Text>
</Content>
</Dialog>
</DialogTrigger>
<DialogTrigger type="popover">
<ActionButton>Trigger Popover</ActionButton>
<Dialog>
<Heading>Popover</Heading>
<Divider />
<Content>
<Text>
This is a popover.
</Text>
</Content>
</Dialog>
</DialogTrigger>
<DialogTrigger type="tray">
<ActionButton>Trigger Tray</ActionButton>
<Dialog>
<Heading>Tray</Heading>
<Divider />
<Content>
<Text>
This is a tray.
</Text>
</Content>
</Dialog>
</DialogTrigger>
Size#
Only modal
type Dialogs support a user defined size prop. Note that the fullscreen
and fullscreenTakeover
sizes
require the DialogTrigger type
prop to be set as fullscreen
and fullscreenTakeover
respectively for container sizing considerations. Modal sizes on mobile devices are also unaffected by this prop due to screen constraints.
<DialogTrigger>
<ActionButton>Small</ActionButton>
(close) => (
<Dialog size="S">
<Heading>Profile</Heading>
<Divider />
<ButtonGroup>
<Button autoFocus variant="cta" onPress= close>Save</Button>
<Button variant="secondary" onPress= close>Cancel</Button>
</ButtonGroup>
<Content>
<Form>
<TextField label="Name" />
<Checkbox>Make private</Checkbox>
</Form>
</Content>
</Dialog>
)
</DialogTrigger>
<DialogTrigger>
<ActionButton>Medium</ActionButton>
(close) => (
<Dialog size="M">
<Heading>Profile</Heading>
<Divider />
<ButtonGroup>
<Button autoFocus variant="cta" onPress= close>Save</Button>
<Button variant="secondary" onPress= close>Cancel</Button>
</ButtonGroup>
<Content>
<Form>
<TextField label="Name" />
<Checkbox>Make private</Checkbox>
</Form>
</Content>
</Dialog>
)
</DialogTrigger>
<DialogTrigger>
<ActionButton>Large</ActionButton>
(close) => (
<Dialog size="L">
<Heading>Profile</Heading>
<Divider />
<ButtonGroup>
<Button autoFocus variant="cta" onPress= close>Save</Button>
<Button variant="secondary" onPress= close>Cancel</Button>
</ButtonGroup>
<Content>
<Form>
<TextField label="Name" />
<Checkbox>Make private</Checkbox>
</Form>
</Content>
</Dialog>
)
</DialogTrigger>