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.6.0 |
usage | import {useOverlayTrigger} from '@react-aria/overlays' |
API#
useOverlayTrigger(
props: ,
state: ,
ref: RefObject<HTMLElement>
):
Features#
There is no built in way to create popovers or other types of overlays in
HTML. ,
combined with
,
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
- Hides content behind the overlay from screen readers when combined with
- Handles closing the overlay when interacting outside and pressing the Escape key, when combined with
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 to handle
closing the overlay,
to handle hiding content behind the overlay from screen readers, and optionally
with
to
handle positioning the overlay relative to the trigger.
useOverlayTrigger
returns props that you should spread onto the trigger element and
the appropriate element:
Name | Type | Description |
triggerProps |
| Props for the trigger element. |
overlayProps | HTMLAttributes<HTMLElement> | Props for the overlay container element. |
State is managed by the
hook in
@react-stately/overlays
. The state object should be passed as an argument to useOverlayTrigger
.
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 .
The popover can be closed by clicking or interacting outside the popover, or by pressing the Escape key.
This is handled by .
When the popover is closed, focus is restored back to its trigger button by a
<
>.
Content outside the popover is hidden from screen readers
by .
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
<>
is added at the end of the dialog.
The application is contained in an ,
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 ,
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.
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
useOverlayTrigger
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 Escape key to close the modal.
let {overlayProps} = useOverlay(
{
onClose
isOpen
isDismissable: true
}
ref
);
// Hide content outside the modal from screen readers.
let {modalProps} = useModal();
// Get props for the dialog and its title
let {dialogProps titleProps} = useDialog({} ref);
return (
<FocusScope restoreFocus>
<div
...mergeProps(overlayProps dialogProps otherProps modalProps)
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 state = useOverlayTriggerState({});
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'}
state
triggerRef
);
// Get popover positioning props relative to the trigger
let {overlayProps: positionProps} = useOverlayPosition({
targetRef: triggerRef
overlayRef
placement: 'top'
offset: 5
isOpen: stateisOpen
});
// 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: () => stateopen()
}
triggerRef
);
return (
<>
<button ...buttonProps ...triggerProps ref= triggerRef>
Open Popover
</button>
stateisOpen && (
<OverlayContainer>
<Popover
...overlayProps
...positionProps
ref= overlayRef
title="Popover title"
isOpen= stateisOpen
onClose= stateclose>
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 {useOverlayTriggerState} from '@react-stately/overlays';
import {
useOverlay
useOverlayTrigger
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 Escape key to close the modal.
let {overlayProps} = useOverlay(
{
onClose
isOpen
isDismissable: true
}
ref
);
// Hide content outside the modal from screen readers.
let {modalProps} = useModal();
// Get props for the dialog and its title
let {dialogProps titleProps} = useDialog({} ref);
return (
<FocusScope restoreFocus>
<div
...mergeProps(
overlayProps
dialogProps
otherProps
modalProps
)
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 state = useOverlayTriggerState({});
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'}
state
triggerRef
);
// Get popover positioning props relative to the trigger
let {overlayProps: positionProps} = useOverlayPosition({
targetRef: triggerRef
overlayRef
placement: 'top'
offset: 5
isOpen: stateisOpen
});
// 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: () => stateopen()
}
triggerRef
);
return (
<>
<button
...buttonProps
...triggerProps
ref= triggerRef>
Open Popover
</button>
stateisOpen && (
<OverlayContainer>
<Popover
...overlayProps
...positionProps
ref= overlayRef
title="Popover title"
isOpen= stateisOpen
onClose= stateclose>
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 {useOverlayTriggerState} from '@react-stately/overlays';
import {
useOverlay
useOverlayTrigger
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 Escape key to close the modal.
let {
overlayProps
} = useOverlay(
{
onClose
isOpen
isDismissable: true
}
ref
);
// Hide content outside the modal from screen readers.
let {
modalProps
} = useModal();
// Get props for the dialog and its title
let {
dialogProps
titleProps
} = useDialog(
{}
ref
);
return (
<FocusScope
restoreFocus>
<div
...mergeProps(
overlayProps
dialogProps
otherProps
modalProps
)
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 state = useOverlayTriggerState(
{}
);
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'}
state
triggerRef
);
// Get popover positioning props relative to the trigger
let {
overlayProps: positionProps
} = useOverlayPosition(
{
targetRef: triggerRef
overlayRef
placement: 'top'
offset: 5
isOpen:
stateisOpen
}
);
// 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: () =>
stateopen()
}
triggerRef
);
return (
<>
<button
...buttonProps
...triggerProps
ref= triggerRef>
Open Popover
</button>
stateisOpen && (
<OverlayContainer>
<Popover
...overlayProps
...positionProps
ref=
overlayRef
title="Popover title"
isOpen=
stateisOpen
onClose=
stateclose
>
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>