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.1.4 |
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.
- 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 appropriate 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.
Note: useModal
only hides content within parent OverlayProvider
components. However, if you have additional content
in your application outside any OverlayProvider
, then you should use the @react-aria/aria-modal-polyfill
package to ensure
that this content is hidden while modals are open as well. See the watchModals docs for more information.
import {useOverlayTriggerState} from '@react-stately/overlays';
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 Escape key to close the modal.
let ref = ReactuseRef();
let {overlayProps underlayProps} = useOverlay(props ref);
// Prevent scrolling while the modal is open, and hide content
// outside the modal from screen readers.
usePreventScroll();
let {modalProps} = 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'
}
...underlayProps>
<FocusScope contain restoreFocus autoFocus>
<div
...overlayProps
...dialogProps
...modalProps
ref= ref
style={
background: 'white'
color: 'black'
padding: 30
}>
<h3 ...titleProps style={marginTop: 0}>
title
</h3>
children
</div>
</FocusScope>
</div>
);
}
function Example() {
let state = useOverlayTriggerState({});
let openButtonRef = ReactuseRef();
let closeButtonRef = ReactuseRef();
// 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: () => stateopen()
}
openButtonRef
);
let {buttonProps: closeButtonProps} = useButton(
{
onPress: () => stateclose()
}
closeButtonRef
);
return (
<>
<button ...openButtonProps ref= openButtonRef>
Open Dialog
</button>
stateisOpen && (
<OverlayContainer>
<ModalDialog
title="Enter your name"
isOpen
onClose= stateclose
isDismissable>
<form style={display: 'flex' flexDirection: 'column'}>
<label>
First Name: <input placeholder="John" />
</label>
<label>
Last Name: <input placeholder="Smith" />
</label>
<button
...closeButtonProps
ref= closeButtonRef
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 {useOverlayTriggerState} from '@react-stately/overlays';
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 Escape key to close the modal.
let ref = ReactuseRef();
let {overlayProps underlayProps} = useOverlay(
props
ref
);
// Prevent scrolling while the modal is open, and hide content
// outside the modal from screen readers.
usePreventScroll();
let {modalProps} = 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'
}
...underlayProps>
<FocusScope contain restoreFocus autoFocus>
<div
...overlayProps
...dialogProps
...modalProps
ref= ref
style={
background: 'white'
color: 'black'
padding: 30
}>
<h3 ...titleProps style={marginTop: 0}>
title
</h3>
children
</div>
</FocusScope>
</div>
);
}
function Example() {
let state = useOverlayTriggerState({});
let openButtonRef = ReactuseRef();
let closeButtonRef = ReactuseRef();
// 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: () => stateopen()
}
openButtonRef
);
let {buttonProps: closeButtonProps} = useButton(
{
onPress: () => stateclose()
}
closeButtonRef
);
return (
<>
<button ...openButtonProps ref= openButtonRef>
Open Dialog
</button>
stateisOpen && (
<OverlayContainer>
<ModalDialog
title="Enter your name"
isOpen
onClose= stateclose
isDismissable>
<form
style={
display: 'flex'
flexDirection: 'column'
}>
<label>
First Name: <input placeholder="John" />
</label>
<label>
Last Name: <input placeholder="Smith" />
</label>
<button
...closeButtonProps
ref= closeButtonRef
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 {useOverlayTriggerState} from '@react-stately/overlays';
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 Escape key to close the modal.
let ref = ReactuseRef();
let {
overlayProps
underlayProps
} = useOverlay(
props
ref
);
// Prevent scrolling while the modal is open, and hide content
// outside the modal from screen readers.
usePreventScroll();
let {
modalProps
} = 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'
}
...underlayProps>
<FocusScope
contain
restoreFocus
autoFocus>
<div
...overlayProps
...dialogProps
...modalProps
ref= ref
style={
background:
'white'
color:
'black'
padding: 30
}>
<h3
...titleProps
style={
marginTop: 0
}>
title
</h3>
children
</div>
</FocusScope>
</div>
);
}
function Example() {
let state = useOverlayTriggerState(
{}
);
let openButtonRef = ReactuseRef();
let closeButtonRef = ReactuseRef();
// 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: () =>
stateopen()
}
openButtonRef
);
let {
buttonProps: closeButtonProps
} = useButton(
{
onPress: () =>
stateclose()
}
closeButtonRef
);
return (
<>
<button
...openButtonProps
ref=
openButtonRef
>
Open Dialog
</button>
stateisOpen && (
<OverlayContainer>
<ModalDialog
title="Enter your name"
isOpen
onClose=
stateclose
isDismissable>
<form
style={
display:
'flex'
flexDirection:
'column'
}>
<label>
First
Name:' '
<input placeholder="John" />
</label>
<label>
Last
Name:' '
<input placeholder="Smith" />
</label>
<button
...closeButtonProps
ref=
closeButtonRef
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>