useDialog
Provides the behavior and accessibility implementation for a dialog component. A dialog is an overlay shown above other content in an application.
install | yarn add @react-aria/dialog |
---|---|
version | 3.0.0-alpha.3 |
usage | import {useDialog} from '@react-aria/dialog' |
API#
useDialog(
(props: AriaDialogProps,
, ref: RefObject<HTMLElement>
)): DialogAria
Features#
The HTML <dialog> element
can be used to build dialogs. However, it is not yet widely supported across browsers, and
building fully accessible custom dialogs from scratch is very difficult and error prone.
useDialog
, combined with useModal
,
helps achieve accessible dialogs that can be styled as needed.
Note: useDialog
only handles the dialog itself. It should be combined with other hooks and
components as described below to create a fully accessible modal dialog.
- Exposed to assistive technology as a
dialog
oralertdialog
with ARIA - Handles labelling the dialog by its title element
- Handles focusing the dialog on mount, unless a child element is already focused
- Contains focus inside the dialog when combined with <
FocusScope
> - Hides content behind the dialog from screen readers when combined with
useModal
- Prevents scrolling the page behind the dialog when combined with
usePreventScroll
- Handles closing the dialog when interacting outside and pressing the Escape key, when combined with
useOverlay
Anatomy#
A dialog consists of a container element and an optional title. useDialog
handles
exposing this to assistive technology using ARIA. It can be combined
with useModal
, usePreventScroll
,
and useOverlay
as needed
to create modal dialogs, popovers, and other types of overlays.
useDialog
returns props that you should spread onto the dialog container element:
Name | Type | Description |
dialogProps | HTMLAttributes<HTMLElement> | Props for the dialog container element. |
titleProps | HTMLAttributes<HTMLElement> | Props for the dialog title element. |
If a dialog does not have a visible title element, an aria-label
or aria-labelledby
prop must be passed instead to identify the element to assistive technology.
Example#
This example shows how to build a typical modal dialog. It has a partially transparent
backdrop above the rest of the page, prevents scrolling the body
using usePreventScroll
,
and hides content outside the dialog
with useModal
.
The modal can be closed by clicking or interacting outside the dialog if the isDismissable
prop is set to true, or by pressing the Escape key.
This is handled by useOverlay
.
Focus is contained within the dialog while it is open using a
<FocusScope
>. In addition,
the first focusable element is automatically focused when the dialog opens, and focus is restored
back to the button that opened it when it closes.
The application is contained in an OverlayProvider
,
which is used to hide the content from screen readers with aria-hidden
while a modal is open.
In addition, each modal must be contained in an OverlayContainer
,
which uses a React Portal to render the modal at the
end of the document body. If a nested modal is opened, then the first modal will also be set
to aria-hidden
, so that only the top-most modal is accessible.
import {
useOverlay
usePreventScroll
useModal
OverlayProvider
OverlayContainer
} from '@react-aria/overlays';
import {useDialog} from '@react-aria/dialog';
import {FocusScope} from '@react-aria/focus';
import {useButton} from '@react-aria/button';
function ModalDialog(props) {
let {title children} = props;
// Handle interacting outside the dialog and pressing
// the the Escape key to close the modal.
let ref = ReactuseRef();
let {overlayProps} = useOverlay(props ref);
// Prevent scrolling while the modal is open, and hide content
// outside the modal from screen readers.
usePreventScroll();
useModal();
// Get props for the dialog and its title
let {dialogProps titleProps} = useDialog(props ref);
return (
<div
style={
position: 'fixed'
zIndex: 100
top: 0
left: 0
bottom: 0
right: 0
background: 'rgba(0, 0, 0, 0.5)'
display: 'flex'
alignItems: 'center'
justifyContent: 'center'
}>
<FocusScope contain restoreFocus autoFocus>
<div
...overlayProps
...dialogProps
ref= ref
style={
background: 'white'
color: 'black'
padding: 30
}>
<h3
...titleProps
style={marginTop: 0}>
title
</h3>
children
</div>
</FocusScope>
</div>
);
}
function Example() {
let [isOpen setOpen] = ReactuseState();
// useButton ensures that focus management is handled correctly,
// across all browsers. Focus is restored to the button once the
// dialog closes.
let {buttonProps: openButtonProps} = useButton({
onPress: () => setOpen(true)
});
let {buttonProps: closeButtonProps} = useButton({
onPress: () => setOpen(false)
});
return <>
<button ...openButtonProps>Open Dialog</button>
isOpen &&
<OverlayContainer>
<ModalDialog
title="Enter your name"
isOpen
onClose=() => setOpen(false)
isDismissable>
<form style={display: 'flex' flexDirection: 'column'}>
<label>
First Name: <input placeholder="John" />
</label>
<label>
Last Name: <input placeholder="Smith" />
</label>
<button
...closeButtonProps
style={marginTop: 10}>
Submit
</button>
</form>
</ModalDialog>
</OverlayContainer>
</>;
}
// Application must be wrapped in an OverlayProvider so that it can be
// hidden from screen readers when a modal opens.
<OverlayProvider>
<Example />
</OverlayProvider>
import {
useOverlay
usePreventScroll
useModal
OverlayProvider
OverlayContainer
} from '@react-aria/overlays';
import {useDialog} from '@react-aria/dialog';
import {FocusScope} from '@react-aria/focus';
import {useButton} from '@react-aria/button';
function ModalDialog(props) {
let {title children} = props;
// Handle interacting outside the dialog and pressing
// the the Escape key to close the modal.
let ref = ReactuseRef();
let {overlayProps} = useOverlay(props ref);
// Prevent scrolling while the modal is open, and hide content
// outside the modal from screen readers.
usePreventScroll();
useModal();
// Get props for the dialog and its title
let {dialogProps titleProps} = useDialog(props ref);
return (
<div
style={
position: 'fixed'
zIndex: 100
top: 0
left: 0
bottom: 0
right: 0
background: 'rgba(0, 0, 0, 0.5)'
display: 'flex'
alignItems: 'center'
justifyContent: 'center'
}>
<FocusScope contain restoreFocus autoFocus>
<div
...overlayProps
...dialogProps
ref= ref
style={
background: 'white'
color: 'black'
padding: 30
}>
<h3 ...titleProps style={marginTop: 0}>
title
</h3>
children
</div>
</FocusScope>
</div>
);
}
function Example() {
let [isOpen setOpen] = ReactuseState();
// useButton ensures that focus management is handled correctly,
// across all browsers. Focus is restored to the button once the
// dialog closes.
let {buttonProps: openButtonProps} = useButton({
onPress: () => setOpen(true)
});
let {buttonProps: closeButtonProps} = useButton({
onPress: () => setOpen(false)
});
return (
<>
<button ...openButtonProps>Open Dialog</button>
isOpen && (
<OverlayContainer>
<ModalDialog
title="Enter your name"
isOpen
onClose=() => setOpen(false)
isDismissable>
<form
style={
display: 'flex'
flexDirection: 'column'
}>
<label>
First Name: <input placeholder="John" />
</label>
<label>
Last Name: <input placeholder="Smith" />
</label>
<button
...closeButtonProps
style={marginTop: 10}>
Submit
</button>
</form>
</ModalDialog>
</OverlayContainer>
)
</>
);
}
// Application must be wrapped in an OverlayProvider so that it can be
// hidden from screen readers when a modal opens.
<OverlayProvider>
<Example />
</OverlayProvider>
import {
useOverlay
usePreventScroll
useModal
OverlayProvider
OverlayContainer
} from '@react-aria/overlays';
import {useDialog} from '@react-aria/dialog';
import {FocusScope} from '@react-aria/focus';
import {useButton} from '@react-aria/button';
function ModalDialog(
props
) {
let {
title
children
} = props;
// Handle interacting outside the dialog and pressing
// the the Escape key to close the modal.
let ref = ReactuseRef();
let {
overlayProps
} = useOverlay(
props
ref
);
// Prevent scrolling while the modal is open, and hide content
// outside the modal from screen readers.
usePreventScroll();
useModal();
// Get props for the dialog and its title
let {
dialogProps
titleProps
} = useDialog(
props
ref
);
return (
<div
style={
position:
'fixed'
zIndex: 100
top: 0
left: 0
bottom: 0
right: 0
background:
'rgba(0, 0, 0, 0.5)'
display: 'flex'
alignItems:
'center'
justifyContent:
'center'
}>
<FocusScope
contain
restoreFocus
autoFocus>
<div
...overlayProps
...dialogProps
ref= ref
style={
background:
'white'
color:
'black'
padding: 30
}>
<h3
...titleProps
style={
marginTop: 0
}>
title
</h3>
children
</div>
</FocusScope>
</div>
);
}
function Example() {
let [
isOpen
setOpen
] = ReactuseState();
// useButton ensures that focus management is handled correctly,
// across all browsers. Focus is restored to the button once the
// dialog closes.
let {
buttonProps: openButtonProps
} = useButton({
onPress: () =>
setOpen(true)
});
let {
buttonProps: closeButtonProps
} = useButton({
onPress: () =>
setOpen(false)
});
return (
<>
<button
...openButtonProps>
Open Dialog
</button>
isOpen && (
<OverlayContainer>
<ModalDialog
title="Enter your name"
isOpen
onClose=() =>
setOpen(
false
)
isDismissable>
<form
style={
display:
'flex'
flexDirection:
'column'
}>
<label>
First
Name:' '
<input placeholder="John" />
</label>
<label>
Last
Name:' '
<input placeholder="Smith" />
</label>
<button
...closeButtonProps
style={
marginTop: 10
}>
Submit
</button>
</form>
</ModalDialog>
</OverlayContainer>
)
</>
);
}
// Application must be wrapped in an OverlayProvider so that it can be
// hidden from screen readers when a modal opens.
<OverlayProvider>
<Example />
</OverlayProvider>
Name | Type | Description |
role | 'dialog' | 'alertdialog' | The accessibility role for the dialog. |
id | string | |
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-details | string | Identifies the element (or elements) that provide a detailed, extended description for the object. |
Name | Type | Description |
dialogProps | HTMLAttributes<HTMLElement> | Props for the dialog container element. |
titleProps | HTMLAttributes<HTMLElement> | Props for the dialog title element. |
Hides content outside the current <OverlayContainer>
from screen readers
on mount and restores it on unmount. Typically used by modal dialogs and
other types over overlays to ensure that only the top-most modal is
accessible at once.
useModal(
(
)): void
A FocusScope manages focus for its descendants. It supports containing focus inside the scope, restoring focus to the previously focused element on unmount, and auto focusing children on mount. It also acts as a container for a programmatic focus management interface that can be used to move focus forward and back in response to user events.
Name | Type | Description |
children | ReactNode | The contents of the focus scope. |
contain | boolean | Whether to contain focus inside the scope, so users cannot move focus outside, for example in a modal dialog. |
restoreFocus | boolean | Whether to restore focus back to the element that was focused when the focus scope mounted, after the focus scope unmounts. |
autoFocus | boolean | Whether to auto focus the first focusable element in the focus scope on mount. |
Prevents scrolling on the document body on mount, and restores it on unmount. Also ensures that content does not shift due to the scrollbars disappearing.
usePreventScroll(
(
)): void
Provides the behavior for overlays such as dialogs, popovers, and menus.
Handles hiding the overlay when the user interacts outside it (if isDismissible
),
when the Escape key is pressed, or optionally, on blur. Handles multiple overlays
open at once as a stack: only the top-most overlay will close at once.
useOverlay(
(props: OverlayProps,
, ref: RefObject<HTMLElement>
)): OverlayAria
Name | Type | Description |
isOpen | boolean | Whether the overlay is currently open. |
onClose | (
(
)) => void | Handler that is called when the overlay should close. |
isDismissable | boolean | Whether to close the overlay when the user interacts outside it. |
shouldCloseOnBlur | boolean | Whether the overlay should close when focus is lost or moves outside it. |
Name | Type | Description |
overlayProps | HTMLAttributes<HTMLElement> | Props to apply to the overlay container element |
An OverlayProvider acts as a container for the top-level application.
Any application that uses modal dialogs or other overlays should
be wrapped in a <OverlayProvider>
. This is used to ensure that
the main content of the application is hidden from screen readers
if a modal or other overlay is opened. Only the top-most modal or
overlay should be accessible at once.
Name | Type | Description |
children | ReactNode |
A container for overlays like modals and popovers. Renders the overlay into a Portal which is placed at the end of the document body. Also ensures that the overlay is hidden from screen readers if a nested modal is opened. Only the top-most modal or overlay should be accessible at once.
Name | Type | Description |
children | ReactNode |