Popover
Popover
is a non-modal dialog that floats around its disclosure. It's commonly used for displaying additional rich content on top of something.
#Installation
npm install reakit
Learn more in Get started.
#Usage
import { usePopoverState, Popover, PopoverDisclosure, PopoverArrow, } from "reakit/Popover"; function Example() { const popover = usePopoverState(); return ( <> <PopoverDisclosure {...popover}>Open Popover</PopoverDisclosure> <Popover {...popover} aria-label="Welcome"> <PopoverArrow {...popover} /> Welcome to Reakit! </Popover> </> ); }
#Placement
You can control how Popover
is positioned by setting the placement
option on usePopoverState
.
import { usePopoverState, Popover, PopoverDisclosure, PopoverArrow, } from "reakit/Popover"; function Example() { const popover = usePopoverState({ placement: "right-start" }); return ( <> <PopoverDisclosure {...popover}>Open Popover</PopoverDisclosure> <Popover {...popover} aria-label="Welcome"> <PopoverArrow {...popover} /> Welcome to Reakit! </Popover> </> ); }
#Gutter
You can control the margin between Popover
and PopoverDisclosure
by setting the gutter
option on usePopoverState
.
import { usePopoverState, Popover, PopoverDisclosure } from "reakit/Popover"; function Example() { const popover = usePopoverState({ gutter: 0, placement: "bottom-start" }); return ( <> <PopoverDisclosure {...popover}>Open Popover</PopoverDisclosure> <Popover {...popover} aria-label="Welcome"> Welcome to Reakit! </Popover> </> ); }
#Initial focus
When opening Popover
, focus is usually set on the first tabbable element within the popover, including itself. So, if you want to set the initial focus on the popover element, you can simply pass tabIndex={0}
to it. It'll be also included in the tab order.
import { Button } from "reakit/Button"; import { usePopoverState, Popover, PopoverDisclosure } from "reakit/Popover"; function Example() { const popover = usePopoverState(); return ( <> <PopoverDisclosure {...popover}>Open Popover</PopoverDisclosure> <Popover {...popover} tabIndex={0} aria-label="Welcome"> <Button onClick={popover.hide}>Close</Button> </Popover> </> ); }
Alternatively, you can define another element to get the initial focus with React hooks:
import React from "react"; import { Button } from "reakit/Button"; import { usePopoverState, Popover, PopoverDisclosure } from "reakit/Popover"; function Example() { const popover = usePopoverState(); const ref = React.useRef(); React.useEffect(() => { if (popover.visible) { ref.current.focus(); } }, [popover.visible]); return ( <> <PopoverDisclosure {...popover}>Open Popover</PopoverDisclosure> <Popover {...popover} aria-label="Welcome"> <Button>By default, initial focus would go here</Button> <br /> <br /> <Button ref={ref}>But now it goes here</Button> </Popover> </> ); }
#Animating
Popover
uses DisclosureContent underneath, so you can use the same approaches as described in the Animating section there.
The only difference is that Reakit automatically adds inline styles to the Popover
container so that it's correctly positioned according to PopoverDisclosure
. In this example, we're animating an inner wrapper element, so we don't need to overwrite Popover
positioning styles.
import { css } from "emotion"; import { Button } from "reakit/Button"; import { usePopoverState, Popover, PopoverArrow, PopoverDisclosure, } from "reakit/Popover"; const styles = css` background-color: white; padding: 16px; border: 1px solid rgba(33, 33, 33, 0.25); border-radius: 4px; transition: opacity 250ms ease-in-out, transform 250ms ease-in-out; opacity: 0; transform-origin: top center; transform: translate3d(0, -20px, 0); [data-enter] & { opacity: 1; transform: translate3d(0, 0, 0); } `; function Example() { const popover = usePopoverState({ animated: 250 }); return ( <> <PopoverDisclosure {...popover}>Open popover</PopoverDisclosure> <Popover {...popover} aria-label="Welcome" style={{ border: 0, background: "none", padding: 0 }} > <div className={styles}> <PopoverArrow {...popover} /> Welcome to Reakit <Button onClick={popover.hide}>Close</Button> </div> </Popover> </> ); }
#Abstracting
You can build your own Popover
component with a different API on top of Reakit.
import React from "react"; import { usePopoverState, Popover as BasePopover, PopoverDisclosure, PopoverArrow, } from "reakit/Popover"; function Popover({ disclosure, ...props }) { const popover = usePopoverState(); return ( <> <PopoverDisclosure {...popover} ref={disclosure.ref} {...disclosure.props} > {(disclosureProps) => React.cloneElement(disclosure, disclosureProps)} </PopoverDisclosure> <BasePopover {...popover} {...props}> <PopoverArrow {...popover} /> {props.children} </BasePopover> </> ); } function Example() { return ( <Popover aria-label="Custom popover" disclosure={<button>Open custom popover</button>} > Custom popover </Popover> ); }
#Accessibility
Popover
extends the accessibility features of Dialog.PopoverDisclosure
extends the accessibility features of DialogDisclosure.
Learn more in Accessibility.
#Composition
Popover
uses Dialog, and is used by Menu.PopoverArrow
uses Role, and is used by TooltipArrow.PopoverBackdrop
uses DialogBackdrop.PopoverDisclosure
uses DialogDisclosure, and is used by MenuButton.
Learn more in Composition.
#Props
#usePopoverState
-
baseId
string
ID that will serve as a base for all the items IDs.
-
visible
boolean
Whether it's visible or not.
-
animated
number | boolean
If
true
,animating
will be set totrue
whenvisible
is updated. It'll wait forstopAnimation
to be called or a CSS transition ends. Ifanimated
is set to anumber
,stopAnimation
will be called only after the same number of milliseconds have passed. -
modal
boolean
Toggles Dialog's
modal
state.- Non-modal:
preventBodyScroll
doesn't work and focus is free. - Modal:
preventBodyScroll
is automatically enabled, focus is trapped within the dialog and the dialog is rendered within aPortal
by default.
- Non-modal:
-
placement
"auto-start" | "auto" | "auto-end" | "top-start...
Actual
placement
. -
unstable_fixed
boolean | undefined
Whether or not the popover should have
position
set tofixed
. -
unstable_flip
boolean | undefined
Flip the popover's placement when it starts to overlap its reference element.
-
unstable_offset
[string | number, string | number] | undefined
Offset between the reference and the popover: [main axis, alt axis]. Should not be combined with
gutter
. -
gutter
number | undefined
Offset between the reference and the popover on the main axis. Should not be combined with
unstable_offset
. -
unstable_preventOverflow
boolean | undefined
Prevents popover from being positioned outside the boundary.
#Popover
-
hideOnEsc
boolean | undefined
When enabled, user can hide the dialog by pressing
Escape
. -
hideOnClickOutside
boolean | undefined
When enabled, user can hide the dialog by clicking outside it.
-
preventBodyScroll
boolean | undefined
When enabled, user can't scroll on body when the dialog is visible. This option doesn't work if the dialog isn't modal.
-
unstable_initialFocusRef
RefObject<HTMLElement> | undefined
The element that will be focused when the dialog shows. When not set, the first tabbable element within the dialog will be used.
-
unstable_finalFocusRef
RefObject<HTMLElement> | undefined
The element that will be focused when the dialog hides. When not set, the disclosure component will be used.
-
unstable_orphan
boolean | undefined
Whether or not the dialog should be a child of its parent. Opening a nested orphan dialog will close its parent dialog if
hideOnClickOutside
is set totrue
on the parent. It will be set tofalse
ifmodal
isfalse
.
7 state props
These props are returned by the state hook. You can spread them into this component (
{...state}
) or pass them separately. You can also provide these props from your own state logic.
-
baseId
string
ID that will serve as a base for all the items IDs.
-
visible
boolean
Whether it's visible or not.
-
animated
number | boolean
If
true
,animating
will be set totrue
whenvisible
is updated. It'll wait forstopAnimation
to be called or a CSS transition ends. Ifanimated
is set to anumber
,stopAnimation
will be called only after the same number of milliseconds have passed. -
animating
boolean
Whether it's animating or not.
-
stopAnimation
() => void
Stops animation. It's called automatically if there's a CSS transition.
-
modal
boolean
Toggles Dialog's
modal
state.- Non-modal:
preventBodyScroll
doesn't work and focus is free. - Modal:
preventBodyScroll
is automatically enabled, focus is trapped within the dialog and the dialog is rendered within aPortal
by default.
- Non-modal:
-
hide
() => void
Changes the
visible
state tofalse
#PopoverArrow
-
size
string | number | undefined
Arrow's size
1 state props
These props are returned by the state hook. You can spread them into this component (
{...state}
) or pass them separately. You can also provide these props from your own state logic.
-
placement
"auto-start" | "auto" | "auto-end" | "top-start...
Actual
placement
.
#PopoverBackdrop
6 state props
These props are returned by the state hook. You can spread them into this component (
{...state}
) or pass them separately. You can also provide these props from your own state logic.
-
baseId
string
ID that will serve as a base for all the items IDs.
-
visible
boolean
Whether it's visible or not.
-
animated
number | boolean
If
true
,animating
will be set totrue
whenvisible
is updated. It'll wait forstopAnimation
to be called or a CSS transition ends. Ifanimated
is set to anumber
,stopAnimation
will be called only after the same number of milliseconds have passed. -
animating
boolean
Whether it's animating or not.
-
stopAnimation
() => void
Stops animation. It's called automatically if there's a CSS transition.
-
modal
boolean
Toggles Dialog's
modal
state.- Non-modal:
preventBodyScroll
doesn't work and focus is free. - Modal:
preventBodyScroll
is automatically enabled, focus is trapped within the dialog and the dialog is rendered within aPortal
by default.
- Non-modal:
#PopoverDisclosure
-
disabled
boolean | undefined
Same as the HTML attribute.
-
focusable
boolean | undefined
When an element is
disabled
, it may still befocusable
. It works similarly toreadOnly
on form elements. In this case, onlyaria-disabled
will be set.
4 state props
These props are returned by the state hook. You can spread them into this component (
{...state}
) or pass them separately. You can also provide these props from your own state logic.
-
visible
boolean
Whether it's visible or not.
-
baseId
string
ID that will serve as a base for all the items IDs.
-
toggle
() => void
Toggles the
visible
state -
unstable_referenceRef
RefObject<HTMLElement | null>
The reference element.