Black Lives Matter.  Support the Equal Justice Initiative.
Skip to main content
Reakit
DocumentationNewsletter
GitHub
GitHub

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

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 to true when visible is updated. It'll wait for stopAnimation to be called or a CSS transition ends. If animated is set to a number, 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 a Portal by default.
  • placement "auto-start" | "auto" | "auto-end" | "top-start...

    Actual placement.

  • unstable_fixed boolean | undefined

    Whether or not the popover should have position set to fixed.

  • 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 to true on the parent. It will be set to false if modal is false.

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 to true when visible is updated. It'll wait for stopAnimation to be called or a CSS transition ends. If animated is set to a number, 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 a Portal by default.
  • hide () => void

    Changes the visible state to false

#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 to true when visible is updated. It'll wait for stopAnimation to be called or a CSS transition ends. If animated is set to a number, 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 a Portal by default.

#PopoverDisclosure

  • disabled boolean | undefined

    Same as the HTML attribute.

  • focusable boolean | undefined

    When an element is disabled, it may still be focusable. It works similarly to readOnly on form elements. In this case, only aria-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.