useModalOverlay
Provides the behavior and accessibility implementation for a modal component. A modal is an overlay element which blocks interaction with elements outside it.
| install | yarn add react-aria |
|---|---|
| version | 3.18.0 |
| usage | import {useModalOverlay} from 'react-aria' |
API#
useModalOverlay(
props: AriaModalOverlayProps,
state: OverlayTriggerState,
ref: RefObject<HTMLElement>
): ModalOverlayAria
Features#
The HTML <dialog> element can be used to build modal overlays. However, it is not yet widely supported across browsers, and can be difficult to style and customize. useModalOverlay, helps achieve accessible modal overlays that can be styled as needed.
- Hides content behind the modal from assistive technology while open
- Prevents scrolling the page behind the modal
- Contains focus inside the modal, preventing the user from tabbing outside
- Restores focus to the triggering element on unmount
- Optionally handles closing the modal when interacting outside and pressing the Escape key
Note: useModalOverlay only handles the overlay itself. It should be combined
with useDialog to create fully accessible modal dialogs. Other overlays
such as menus may also be placed in a modal overlay.
Anatomy#
A modal overlay consists of an overlay container element, and an underlay. The overlay may contain a dialog, or another element such as a menu or listbox when used within a component such as a select or combobox. The underlay is typically a partially transparent element that covers the rest of the screen behind the overlay, and prevents the user from interacting with the elements behind it.
useModalOverlay returns props that you should spread onto the overlay and underlay elements:
| Name | Type | Description |
modalProps | DOMAttributes | Props for the modal element. |
underlayProps | DOMAttributes | Props for the underlay element. |
State is managed by the useOverlayTriggerState
hook in @react-stately/overlays. The state object should be passed as an argument to useModalOverlay.
Example#
This example shows how to build a typical modal dialog, by combining useModalOverlay with useDialog. The Dialog component used in this example can also be reused within a popover or other types of overlays.
The Modal component uses an <Overlay> to render its contents in a React Portal at the end of the document body, which ensures it is not clipped by other elements. It also acts as a focus scope, containing focus within the modal and restoring it to the trigger when it unmounts. useModalOverlay handles preventing page scrolling while the modal is open, hiding content outside the modal from screen readers, and optionally closing it when the user interacts outside or presses the Escape key.
import {Overlay, useModalOverlay} from 'react-aria';
function Modal({ state, children, ...props }) {
let ref = React.useRef();
let { modalProps, underlayProps } = useModalOverlay(props, state, ref);
return (
<Overlay>
<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}
>
<div
{...modalProps}
ref={ref}
style={{
background: 'var(--page-background)',
border: '1px solid gray'
}}
>
{children}
</div>
</div>
</Overlay>
);
}
import {Overlay, useModalOverlay} from 'react-aria';
function Modal({ state, children, ...props }) {
let ref = React.useRef();
let { modalProps, underlayProps } = useModalOverlay(
props,
state,
ref
);
return (
<Overlay>
<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}
>
<div
{...modalProps}
ref={ref}
style={{
background: 'var(--page-background)',
border: '1px solid gray'
}}
>
{children}
</div>
</div>
</Overlay>
);
}
import {
Overlay,
useModalOverlay
} from 'react-aria';
function Modal(
{
state,
children,
...props
}
) {
let ref = React
.useRef();
let {
modalProps,
underlayProps
} = useModalOverlay(
props,
state,
ref
);
return (
<Overlay>
<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}
>
<div
{...modalProps}
ref={ref}
style={{
background:
'var(--page-background)',
border:
'1px solid gray'
}}
>
{children}
</div>
</div>
</Overlay>
);
}
The below ModalTrigger component uses the useOverlayTrigger hook to show the modal when a button is pressed. It accepts a function as children, which is called with a callback that closes the modal. This can be used to implement a close button.
import {useOverlayTrigger} from 'react-aria';
import {useOverlayTriggerState} from 'react-stately';
// Reuse the Button from your component library. See below for details.
import {Button} from 'your-component-library';
function ModalTrigger({ label, children, ...props }) {
let state = useOverlayTriggerState(props);
let { triggerProps, overlayProps } = useOverlayTrigger(
{ type: 'dialog' },
state
);
return (
<>
<Button {...triggerProps}>Open Dialog</Button>
{state.isOpen &&
(
<Modal state={state}>
{React.cloneElement(children(state.close), overlayProps)}
</Modal>
)}
</>
);
}
import {useOverlayTrigger} from 'react-aria';
import {useOverlayTriggerState} from 'react-stately';
// Reuse the Button from your component library. See below for details.
import {Button} from 'your-component-library';
function ModalTrigger({ label, children, ...props }) {
let state = useOverlayTriggerState(props);
let { triggerProps, overlayProps } = useOverlayTrigger({
type: 'dialog'
}, state);
return (
<>
<Button {...triggerProps}>Open Dialog</Button>
{state.isOpen &&
(
<Modal state={state}>
{React.cloneElement(
children(state.close),
overlayProps
)}
</Modal>
)}
</>
);
}
import {useOverlayTrigger} from 'react-aria';
import {useOverlayTriggerState} from 'react-stately';
// Reuse the Button from your component library. See below for details.
import {Button} from 'your-component-library';
function ModalTrigger(
{
label,
children,
...props
}
) {
let state =
useOverlayTriggerState(
props
);
let {
triggerProps,
overlayProps
} = useOverlayTrigger({
type: 'dialog'
}, state);
return (
<>
<Button
{...triggerProps}
>
Open Dialog
</Button>
{state.isOpen &&
(
<Modal
state={state}
>
{React
.cloneElement(
children(
state
.close
),
overlayProps
)}
</Modal>
)}
</>
);
}
Now, we can render an example modal containing a dialog, with a button that closes it using the function provided by ModalTrigger.
// Reuse the Dialog from your component library. See below for details.
import {Dialog} from 'your-component-library';
<ModalTrigger label="Open Dialog">
{close =>
<Dialog title="Enter your name">
<form style={{display: 'flex', flexDirection: 'column'}}>
<label htmlFor="first-name">First Name:</label>
<input id="first-name" />
<label htmlFor="last-name">Last Name:</label>
<input id="last-name" />
<Button
onPress={close}
style={{marginTop: 10}}>
Submit
</Button>
</form>
</Dialog>
}
</ModalTrigger>
// Reuse the Dialog from your component library. See below for details.
import {Dialog} from 'your-component-library';
<ModalTrigger label="Open Dialog">
{(close) => (
<Dialog title="Enter your name">
<form
style={{
display: 'flex',
flexDirection: 'column'
}}
>
<label htmlFor="first-name">First Name:</label>
<input id="first-name" />
<label htmlFor="last-name">Last Name:</label>
<input id="last-name" />
<Button
onPress={close}
style={{ marginTop: 10 }}
>
Submit
</Button>
</form>
</Dialog>
)}
</ModalTrigger>
// Reuse the Dialog from your component library. See below for details.
import {Dialog} from 'your-component-library';
<ModalTrigger label="Open Dialog">
{(close) => (
<Dialog title="Enter your name">
<form
style={{
display:
'flex',
flexDirection:
'column'
}}
>
<label htmlFor="first-name">
First Name:
</label>
<input id="first-name" />
<label htmlFor="last-name">
Last Name:
</label>
<input id="last-name" />
<Button
onPress={close}
style={{
marginTop:
10
}}
>
Submit
</Button>
</form>
</Dialog>
)}
</ModalTrigger>
Dialog#
The Dialog component is rendered within the ModalOverlay component. It is built using the useDialog hook, and can also be used in other overlay containers such as popovers.
Show code
import {useDialog} from 'react-aria';
function Dialog({ title, children, ...props }) {
let ref = React.useRef();
let { dialogProps, titleProps } = useDialog(props, ref);
return (
<div {...dialogProps} ref={ref} style={{ padding: 30 }}>
{title &&
(
<h3 {...titleProps} style={{ marginTop: 0 }}>
{title}
</h3>
)}
{children}
</div>
);
}
import {useDialog} from 'react-aria';
function Dialog({ title, children, ...props }) {
let ref = React.useRef();
let { dialogProps, titleProps } = useDialog(props, ref);
return (
<div {...dialogProps} ref={ref} style={{ padding: 30 }}>
{title &&
(
<h3 {...titleProps} style={{ marginTop: 0 }}>
{title}
</h3>
)}
{children}
</div>
);
}
import {useDialog} from 'react-aria';
function Dialog(
{
title,
children,
...props
}
) {
let ref = React
.useRef();
let {
dialogProps,
titleProps
} = useDialog(
props,
ref
);
return (
<div
{...dialogProps}
ref={ref}
style={{
padding: 30
}}
>
{title &&
(
<h3
{...titleProps}
style={{
marginTop:
0
}}
>
{title}
</h3>
)}
{children}
</div>
);
}
Button#
The Button component is used in the above example to toggle the popover. It is built using the useButton hook, and can be shared with many other components.
Show code
import {useButton} from 'react-aria';
function Button(props) {
let ref = props.buttonRef;
let { buttonProps } = useButton(props, ref);
return (
<button {...buttonProps} ref={ref} style={props.style}>
{props.children}
</button>
);
}
import {useButton} from 'react-aria';
function Button(props) {
let ref = props.buttonRef;
let { buttonProps } = useButton(props, ref);
return (
<button {...buttonProps} ref={ref} style={props.style}>
{props.children}
</button>
);
}
import {useButton} from 'react-aria';
function Button(props) {
let ref =
props.buttonRef;
let { buttonProps } =
useButton(
props,
ref
);
return (
<button
{...buttonProps}
ref={ref}
style={props.style}
>
{props.children}
</button>
);
}