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
Popoverextends the accessibility features of Dialog.PopoverDisclosureextends the accessibility features of DialogDisclosure.
Learn more in Accessibility.
#Composition
Popoveruses Dialog, and is used by Menu.PopoverArrowuses Role, and is used by TooltipArrow.PopoverBackdropuses DialogBackdrop.PopoverDisclosureuses DialogDisclosure, and is used by MenuButton.
Learn more in Composition.
#Props
#usePopoverState
-
baseIdstringID that will serve as a base for all the items IDs.
-
visiblebooleanWhether it's visible or not.
-
animatednumber | booleanIf
true,animatingwill be set totruewhenvisibleis updated. It'll wait forstopAnimationto be called or a CSS transition ends. Ifanimatedis set to anumber,stopAnimationwill be called only after the same number of milliseconds have passed. -
modalbooleanToggles Dialog's
modalstate.- Non-modal:
preventBodyScrolldoesn't work and focus is free. - Modal:
preventBodyScrollis automatically enabled, focus is trapped within the dialog and the dialog is rendered within aPortalby default.
- Non-modal:
-
placement"auto-start" | "auto" | "auto-end" | "top-start...Actual
placement. -
unstable_fixedboolean | undefinedWhether or not the popover should have
positionset tofixed. -
unstable_flipboolean | undefinedFlip the popover's placement when it starts to overlap its reference element.
-
unstable_offset[string | number, string | number] | undefinedOffset between the reference and the popover: [main axis, alt axis]. Should not be combined with
gutter. -
gutternumber | undefinedOffset between the reference and the popover on the main axis. Should not be combined with
unstable_offset. -
unstable_preventOverflowboolean | undefinedPrevents popover from being positioned outside the boundary.
#Popover
-
hideOnEscboolean | undefinedWhen enabled, user can hide the dialog by pressing
Escape. -
hideOnClickOutsideboolean | undefinedWhen enabled, user can hide the dialog by clicking outside it.
-
preventBodyScrollboolean | undefinedWhen enabled, user can't scroll on body when the dialog is visible. This option doesn't work if the dialog isn't modal.
-
unstable_initialFocusRefRefObject<HTMLElement> | undefinedThe element that will be focused when the dialog shows. When not set, the first tabbable element within the dialog will be used.
-
unstable_finalFocusRefRefObject<HTMLElement> | undefinedThe element that will be focused when the dialog hides. When not set, the disclosure component will be used.
-
unstable_orphanboolean | undefinedWhether or not the dialog should be a child of its parent. Opening a nested orphan dialog will close its parent dialog if
hideOnClickOutsideis set totrueon the parent. It will be set tofalseifmodalisfalse.
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.
-
baseIdstringID that will serve as a base for all the items IDs.
-
visiblebooleanWhether it's visible or not.
-
animatednumber | booleanIf
true,animatingwill be set totruewhenvisibleis updated. It'll wait forstopAnimationto be called or a CSS transition ends. Ifanimatedis set to anumber,stopAnimationwill be called only after the same number of milliseconds have passed. -
animatingbooleanWhether it's animating or not.
-
stopAnimation() => voidStops animation. It's called automatically if there's a CSS transition.
-
modalbooleanToggles Dialog's
modalstate.- Non-modal:
preventBodyScrolldoesn't work and focus is free. - Modal:
preventBodyScrollis automatically enabled, focus is trapped within the dialog and the dialog is rendered within aPortalby default.
- Non-modal:
-
hide() => voidChanges the
visiblestate tofalse
#PopoverArrow
-
sizestring | number | undefinedArrow'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.
-
baseIdstringID that will serve as a base for all the items IDs.
-
visiblebooleanWhether it's visible or not.
-
animatednumber | booleanIf
true,animatingwill be set totruewhenvisibleis updated. It'll wait forstopAnimationto be called or a CSS transition ends. Ifanimatedis set to anumber,stopAnimationwill be called only after the same number of milliseconds have passed. -
animatingbooleanWhether it's animating or not.
-
stopAnimation() => voidStops animation. It's called automatically if there's a CSS transition.
-
modalbooleanToggles Dialog's
modalstate.- Non-modal:
preventBodyScrolldoesn't work and focus is free. - Modal:
preventBodyScrollis automatically enabled, focus is trapped within the dialog and the dialog is rendered within aPortalby default.
- Non-modal:
#PopoverDisclosure
-
disabledboolean | undefinedSame as the HTML attribute.
-
focusableboolean | undefinedWhen an element is
disabled, it may still befocusable. It works similarly toreadOnlyon form elements. In this case, onlyaria-disabledwill 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.
-
visiblebooleanWhether it's visible or not.
-
baseIdstringID that will serve as a base for all the items IDs.
-
toggle() => voidToggles the
visiblestate -
unstable_referenceRefRefObject<HTMLElement | null>The reference element.