useOverlayTrigger
Handles the behavior and accessibility for an overlay trigger, e.g. a button that opens a popover, menu, or other overlay that is positioned relative to the trigger.
install | yarn add @react-aria/overlays |
---|---|
version | 3.0.0-alpha.1 |
usage | import {useOverlayTrigger} from '@react-aria/overlays' |
API#
useOverlayTrigger(
(props: OverlayTriggerProps,
, ref: RefObject<HTMLElement>
)): OverlayTriggerAria
Features#
There is no built in way to create popovers or other types of overlays in
HTML. useOverlayTrigger
,
combined with useOverlayPosition
,
helps achieve accessible overlays that can be styled as needed.
Note: useOverlayTrigger
only handles the overlay itself. It should be combined
with useDialog to create fully accessible popovers. For menus,
see useMenuTrigger, which builds on useOverlayTrigger
and provides
additional functionality specific to menus.
- Exposes overlay trigger and connects trigger to overlay with ARIA
- Positions the overlay relative to the trigger when combined with
useOverlayPosition
- Hides content behind the overlay from screen readers when combined with
useModal
- Handles closing the overlay when interacting outside and pressing the Escape key, when combined with
useOverlay
Anatomy#
An overlay trigger consists of a trigger element (e.g. button) and an overlay
(e.g. popover, menu, listbox, etc.). useOverlayTrigger
handles exposing the
trigger and overlay to assistive technology with ARIA. It should be combined
with useOverlay
to handle
closing the overlay, useModal
to handle hiding content behind the overlay from screen readers, and optionally
with useOverlayPosition
to
handle positioning the overlay relative to the trigger.
useOverlayTrigger
returns props that you should spread onto the trigger element and
the overlay container element:
Name | Type | Description |
triggerProps | AriaButtonProps | Props for the trigger element. |
overlayProps | HTMLAttributes<HTMLElement> | Props for the overlay container element. |
Example#
This example shows how to build a typical popover overlay that is positioned relative to
a trigger button. The content of the popover is a dialog, built
with useDialog
.
The popover can be closed by clicking or interacting outside the popover, or by pressing the Escape key.
This is handled by useOverlay
.
When the popover is closed, focus is restored back to its trigger button by a
<FocusScope
>.
Content outside the popover is hidden from screen readers
by useModal
.
This improves the experience for screen reader users by ensuring that they don't
navigate out of context. This is especially important when the popover is rendered
into a portal at the end of the document, and the content just before it is
unrelated to the original trigger.
To allow screen reader users to more easily dismiss the popover, a visually hidden
<DismissButton
>
is added at the end of the dialog.
The application is contained in an OverlayProvider
,
which is used to hide the content from screen readers with aria-hidden
while an overlay is open.
In addition, each overlay must be contained in an OverlayContainer
,
which uses a React Portal to render the overlay at the
end of the document body. If a nested overlay is opened, then the first overlay will also be set
to aria-hidden
, so that only the top-most overlay is accessible to screen readers.
import {
useOverlay
useOverlayPosition
useModal
OverlayProvider
OverlayContainer
DismissButton
} from '@react-aria/overlays';
import {useDialog} from '@react-aria/dialog';
import {FocusScope} from '@react-aria/focus';
import {useButton} from '@react-aria/button';
import {mergeProps} from '@react-aria/utils';
const Popover = ReactforwardRef(({
title
children
isOpen
onClose
style
...otherProps
} ref) => {
// Handle interacting outside the dialog and pressing
// the the Escape key to close the modal.
let {overlayProps} = useOverlay({
onClose
isOpen
isDismissable: true
} ref);
// Hide content outside the modal from screen readers.
useModal();
// Get props for the dialog and its title
let {dialogProps titleProps} = useDialog({} ref);
return (
<FocusScope restoreFocus>
<div
...mergeProps(overlayProps mergeProps(dialogProps otherProps))
ref= ref
style={
background: 'white'
color: 'black'
padding: 30
...style
}>
<h3
...titleProps
style={marginTop: 0}>
title
</h3>
children
<DismissButton onDismiss= onClose />
</div>
</FocusScope>
);
});
function Example() {
let [isOpen setOpen] = ReactuseState(false);
let triggerRef = ReactuseRef();
let overlayRef = ReactuseRef();
// Get props for the trigger and overlay. This also handles
// hiding the overlay when a parent element of the trigger scrolls
// (which invalidates the popover positioning).
let {triggerProps overlayProps} = useOverlayTrigger({
type: 'dialog'
onClose: () => setOpen(false)
isOpen
} triggerRef);
// Get popover positioning props relative to the trigger
let {overlayProps: positionProps} = useOverlayPosition({
targetRef: triggerRef
overlayRef
placement: 'top'
offset: 5
isOpen
});
// useButton ensures that focus management is handled correctly,
// across all browsers. Focus is restored to the button once the
// popover closes.
let {buttonProps} = useButton({
onPress: () => setOpen(true)
});
return <>
<button
...buttonProps
...triggerProps
ref= triggerRef>
Open Popover
</button>
isOpen &&
<OverlayContainer>
<Popover
...overlayProps
...positionProps
ref= overlayRef
title="Popover title"
isOpen
onClose=() => setOpen(false)>
This is the content of the popover.
</Popover>
</OverlayContainer>
</>;
}
// Application must be wrapped in an OverlayProvider so that it can be
// hidden from screen readers when an overlay opens.
<OverlayProvider>
<Example />
</OverlayProvider>
import {
useOverlay
useOverlayPosition
useModal
OverlayProvider
OverlayContainer
DismissButton
} from '@react-aria/overlays';
import {useDialog} from '@react-aria/dialog';
import {FocusScope} from '@react-aria/focus';
import {useButton} from '@react-aria/button';
import {mergeProps} from '@react-aria/utils';
const Popover = ReactforwardRef(
(
{
title
children
isOpen
onClose
style
...otherProps
}
ref
) => {
// Handle interacting outside the dialog and pressing
// the the Escape key to close the modal.
let {overlayProps} = useOverlay(
{
onClose
isOpen
isDismissable: true
}
ref
);
// Hide content outside the modal from screen readers.
useModal();
// Get props for the dialog and its title
let {dialogProps titleProps} = useDialog({} ref);
return (
<FocusScope restoreFocus>
<div
...mergeProps(
overlayProps
mergeProps(dialogProps otherProps)
)
ref= ref
style={
background: 'white'
color: 'black'
padding: 30
...style
}>
<h3 ...titleProps style={marginTop: 0}>
title
</h3>
children
<DismissButton onDismiss= onClose />
</div>
</FocusScope>
);
}
);
function Example() {
let [isOpen setOpen] = ReactuseState(false);
let triggerRef = ReactuseRef();
let overlayRef = ReactuseRef();
// Get props for the trigger and overlay. This also handles
// hiding the overlay when a parent element of the trigger scrolls
// (which invalidates the popover positioning).
let {triggerProps overlayProps} = useOverlayTrigger(
{
type: 'dialog'
onClose: () => setOpen(false)
isOpen
}
triggerRef
);
// Get popover positioning props relative to the trigger
let {overlayProps: positionProps} = useOverlayPosition({
targetRef: triggerRef
overlayRef
placement: 'top'
offset: 5
isOpen
});
// useButton ensures that focus management is handled correctly,
// across all browsers. Focus is restored to the button once the
// popover closes.
let {buttonProps} = useButton({
onPress: () => setOpen(true)
});
return (
<>
<button
...buttonProps
...triggerProps
ref= triggerRef>
Open Popover
</button>
isOpen && (
<OverlayContainer>
<Popover
...overlayProps
...positionProps
ref= overlayRef
title="Popover title"
isOpen
onClose=() => setOpen(false)>
This is the content of the popover.
</Popover>
</OverlayContainer>
)
</>
);
}
// Application must be wrapped in an OverlayProvider so that it can be
// hidden from screen readers when an overlay opens.
<OverlayProvider>
<Example />
</OverlayProvider>
import {
useOverlay
useOverlayPosition
useModal
OverlayProvider
OverlayContainer
DismissButton
} from '@react-aria/overlays';
import {useDialog} from '@react-aria/dialog';
import {FocusScope} from '@react-aria/focus';
import {useButton} from '@react-aria/button';
import {mergeProps} from '@react-aria/utils';
const Popover = ReactforwardRef(
(
{
title
children
isOpen
onClose
style
...otherProps
}
ref
) => {
// Handle interacting outside the dialog and pressing
// the the Escape key to close the modal.
let {
overlayProps
} = useOverlay(
{
onClose
isOpen
isDismissable: true
}
ref
);
// Hide content outside the modal from screen readers.
useModal();
// Get props for the dialog and its title
let {
dialogProps
titleProps
} = useDialog(
{}
ref
);
return (
<FocusScope
restoreFocus>
<div
...mergeProps(
overlayProps
mergeProps(
dialogProps
otherProps
)
)
ref= ref
style={
background:
'white'
color:
'black'
padding: 30
...style
}>
<h3
...titleProps
style={
marginTop: 0
}>
title
</h3>
children
<DismissButton
onDismiss=
onClose
/>
</div>
</FocusScope>
);
}
);
function Example() {
let [
isOpen
setOpen
] = ReactuseState(
false
);
let triggerRef = ReactuseRef();
let overlayRef = ReactuseRef();
// Get props for the trigger and overlay. This also handles
// hiding the overlay when a parent element of the trigger scrolls
// (which invalidates the popover positioning).
let {
triggerProps
overlayProps
} = useOverlayTrigger(
{
type: 'dialog'
onClose: () =>
setOpen(false)
isOpen
}
triggerRef
);
// Get popover positioning props relative to the trigger
let {
overlayProps: positionProps
} = useOverlayPosition(
{
targetRef: triggerRef
overlayRef
placement: 'top'
offset: 5
isOpen
}
);
// useButton ensures that focus management is handled correctly,
// across all browsers. Focus is restored to the button once the
// popover closes.
let {
buttonProps
} = useButton({
onPress: () =>
setOpen(true)
});
return (
<>
<button
...buttonProps
...triggerProps
ref= triggerRef>
Open Popover
</button>
isOpen && (
<OverlayContainer>
<Popover
...overlayProps
...positionProps
ref=
overlayRef
title="Popover title"
isOpen
onClose=() =>
setOpen(
false
)
>
This is the
content of
the popover.
</Popover>
</OverlayContainer>
)
</>
);
}
// Application must be wrapped in an OverlayProvider so that it can be
// hidden from screen readers when an overlay opens.
<OverlayProvider>
<Example />
</OverlayProvider>
Name | Type | Description |
type | 'dialog'
| 'menu'
| 'listbox'
| 'tree'
| 'grid' | Type of overlay that is opened by the trigger. |
isOpen | boolean | Whether the overlay is currently open. |
onClose | (
(
)) => void | Handler that is called when the overlay should close. |
Name | Type | Description |
triggerProps | AriaButtonProps | Props for the trigger element. |
overlayProps | HTMLAttributes<HTMLElement> | Props for the overlay container element. |
Handles the behavior and accessibility for an overlay trigger, e.g. a button that opens a popover, menu, or other overlay that is positioned relative to the trigger.
useOverlayTrigger(
(props: OverlayTriggerProps,
, ref: RefObject<HTMLElement>
)): OverlayTriggerAria
Handles positioning overlays like popovers and menus relative to a trigger element, and updating the position when the window resizes.
useOverlayPosition(
(props: AriaPositionProps
)): PositionAria
Name | Type | Description |
targetRef | RefObject<HTMLElement> | |
overlayRef | RefObject<HTMLElement> | |
scrollRef | RefObject<HTMLElement> | |
shouldUpdatePosition | boolean | |
placement | Placement | |
containerPadding | number | |
offset | number | |
crossOffset | number | |
shouldFlip | boolean | |
boundaryElement | HTMLElement | |
isOpen | boolean |
'bottom'
| 'bottom left'
| 'bottom right'
| 'bottom start'
| 'bottom end'
| 'top'
| 'top left'
| 'top right'
| 'top start'
| 'top end'
| 'left'
| 'left top'
| 'left bottom'
| 'start'
| 'start top'
| 'start bottom'
| 'right'
| 'right top'
| 'right bottom'
| 'end'
| 'end top'
| 'end bottom'
Name | Type | Description |
overlayProps | HTMLAttributes<Element> | |
arrowProps | HTMLAttributes<Element> | |
placement | PlacementAxis |
Axis | 'center'
'top'
| 'bottom'
| 'left'
| 'right'
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
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 |
Provides the behavior and accessibility implementation for a dialog component. A dialog is an overlay shown above other content in an application.
useDialog(
(props: AriaDialogProps,
, ref: RefObject<HTMLElement>
)): DialogAria
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. |
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. |
A visually hidden button that can be used to allow screen reader users to dismiss a modal or popup when there is no visual affordance to do so.
Name | Type | Description |
onDismiss | (
(
)) => void | Called when the dismiss button is activated. |
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 |