Skip to main content
Kaleidoscope

Popover

Display rich content within a panel in context of a trigger button

Basic example

Popovers are a simple way to group contextual actions into a toggle-able overlay that stays attached to the trigger button. Use Popovers to enclose sets of related actions. Avoid using Popovers to contain miscellaneous or unrelated actions.

Editable example
<Popover
  button={(buttonProps) => (
    <Button type="secondary" {...buttonProps}>
      Show popover
    </Button>
  )}
>
  <h2 className="text-h4">Popover panel content</h2>
  <p className="text-paragraph">You can put whatever you want in here</p>
  <TextInput label="Example input" />
</Popover>

Button

Any clickable element can be used as a button to trigger the Popover. Make sure whatever component you use renders a <button> HTML element. Most of the time a or should be used, but for special cased you can use a custom button element.

The button prop uses render props to pass through the onClick event and other properties for accessibility. The easiest way to use the render props is to spread them onto the button component.

Editable example
<Popover
  button={(buttonProps) => (
    <IconButton
      icon={<Edit />}
      tooltip={{ content: "Edit" }}
      {...buttonProps}
    />
  )}
>
  <span>Popover content</span>
</Popover>

Size

Choose from one of the preset sizes. For special cases you can use PopoverSize.Auto and the the panel will match the size of its content.

Editable example
<Popover
  size={PopoverSize.Small}
  button={(buttonProps) => (
    <Button type="secondary" {...buttonProps}>
      Small
    </Button>
  )}
>
  <span>Small Popover</span>
</Popover>

<Popover
  size={PopoverSize.Medium}
  button={(buttonProps) => (
    <Button type="secondary" {...buttonProps}>
      Medium
    </Button>
  )}
>
  <span>Medium Popover</span>
</Popover>

<Popover
  size={PopoverSize.Large}
  button={(buttonProps) => (
    <Button type="secondary" {...buttonProps}>
      Large
    </Button>
  )}
>
  <span>Large Popover</span>
</Popover>

<Popover
  size={PopoverSize.Auto}
  button={(buttonProps) => (
    <Button type="secondary" {...buttonProps}>
      Auto
    </Button>
  )}
>
  <span>Auto size</span>
</Popover>

Position

Set a preferred position for the Popover to appear. Appears to the bottom by default. If there's no space in the viewport for the preferred position, it will automatically flip to the other side.

Editable example
<Popover
  position={PopoverPosition.Top}
  button={(buttonProps) => (
    <Button type="secondary" {...buttonProps}>Top</Button>
  )}
>
  <span>Popover content</span>
</Popover>

<Popover
  position={PopoverPosition.Bottom}
  button={(buttonProps) => (
    <Button type="secondary" {...buttonProps}>Bottom</Button>
  )}
>
  <span>Popover content</span>
</Popover>

<Popover
  position={PopoverPosition.Left}
  button={(buttonProps) => (
    <Button type="secondary" {...buttonProps}>Left</Button>
  )}
>
  <span>Popover content</span>
</Popover>

<Popover
  position={PopoverPosition.Right}
  button={(buttonProps) => (
    <Button type="secondary" {...buttonProps}>Right</Button>
  )}
>
  <span>Popover content</span>
</Popover>

Alignment

Control the alignment of the Popover panel along the axis set by position. Options are PopoverAlignment.Start and PopoverAlignment.Center (the default).

Editable example
<Popover
  alignment={PopoverAlignment.Start}
  button={(buttonProps) => (
    <Button type="secondary" {...buttonProps}>
      Start-aligned
    </Button>
  )}
>
  <span>Aligned to the start of the trigger</span>
</Popover>

<Popover
  alignment={PopoverAlignment.Center}
  button={(buttonProps) => (
    <Button type="secondary" {...buttonProps}>
      Center-aligned (default)
    </Button>
  )}
>
  <span>Centered on the trigger</span>
</Popover>

Padding

Control the inner padding of the Popover panel. Accepts "small", "medium" (default), or "none".

Editable example
<Popover
  padding="none"
  button={(buttonProps) => (
    <Button type="secondary" {...buttonProps}>
      No padding
    </Button>
  )}
>
  <span>Edge-to-edge content</span>
</Popover>

<Popover
  padding="small"
  button={(buttonProps) => (
    <Button type="secondary" {...buttonProps}>
      Small padding
    </Button>
  )}
>
  <span>Small padding content</span>
</Popover>

Offset

Set the distance in pixels between the Popover panel and its trigger button. Defaults to 4.

Editable example
<Popover
  offset={16}
  button={(buttonProps) => (
    <Button type="secondary" {...buttonProps}>
      Large offset
    </Button>
  )}
>
  <span>16px away from the button</span>
</Popover>

Max height

Use maxHeight to constrain the panel height. Accepts a number (pixels) or CSS string value.

Editable example
<Popover
  maxHeight={150}
  button={(buttonProps) => (
    <Button type="secondary" {...buttonProps}>
      Constrained height
    </Button>
  )}
>
  <div style={{ height: 300 }}>Tall content that will scroll</div>
</Popover>

Caret

Displays an arrow pointing to the center of the trigger button from the Popover panel.

Editable example
<Popover
  caret
  button={(buttonProps) => (
    <Button type="secondary" {...buttonProps}>
      Popover with caret
    </Button>
  )}
>
  <span>Popover content</span>
</Popover>

Dark theme

Specify a dark theme for the Popover panel. The default theme is light.

Editable example
<Popover
  theme={PopoverTheme.Dark}
  button={(buttonProps) => (
    <Button type="secondary" {...buttonProps}>Dark popover</Button>
  )}
>
  <span>Popover content</span>
</Popover>

Controlled

By default, Popover automatically handles its open and closed state. In some cases you need to be able to directly control the state of the Popover externally. In these situations you can make it a controlled component with the isOpen and onChange props.

One thing to keep in mind is if you want the external trigger to toggle the Popover it will clash with the Popover's outer mouseDown handler, so you'll need to add event.nativeEvent.stopPropagation() on your external trigger's onMouseDown.

Editable example
() => {
  const [popoverOpen, setPopoverOpen] = React.useState(false);

  return (
    <>
      <Button
        type="secondary"
        decorator={ButtonDecorator.Arrow}
        onMouseDown={(event) => {
          if (popoverOpen) {
            event.nativeEvent.stopPropagation();
          }
        }}
        onClick={() => {
          setPopoverOpen(!popoverOpen);
        }}
      >
        Trigger Popover
      </Button>
      <Popover
        isOpen={popoverOpen}
        onChange={setPopoverOpen}
        button={(buttonProps) => (
          <Button type="secondary" {...buttonProps}>Popover target</Button>
        )}
      >
        <span>Popover content</span>
      </Popover>
    </>
  );
}

Close details

When using a controlled Popover, the onChange callback receives a second argument — a PopoverChangeDetails object — that tells you why the Popover is closing. This is useful when you want to ignore certain close reasons, for example keeping the Popover open when its trigger scrolls off-screen.

The reason field can be one of:

ReasonDescription
clicked-buttonThe user clicked the trigger button to close
clicked-outsideThe user clicked outside the Popover panel
scrolled-awayThe trigger button scrolled out of the viewport
pressed-escapeThe user pressed the Escape key
Editable example
() => {
  const [popoverOpen, setPopoverOpen] = React.useState(false);

  return (
    <Popover
      isOpen={popoverOpen}
      onChange={(isOpen, details) => {
        // Ignore close requests triggered by the button scrolling off-screen
        if (!isOpen && details.reason === "scrolled-away") {
          return;
        }
        setPopoverOpen(isOpen);
      }}
      button={(buttonProps) => (
        <Button type="secondary" {...buttonProps}>
          Stays open on scroll-away
        </Button>
      )}
    >
      <span>This Popover won't close when it scrolls out of view</span>
    </Popover>
  );
}

Styling

To override the styling of the Popover panel, add a className and drop your styles in there. This is useful if you need to remove the default padding for edge-to-edge content, or if you need to add a specific z-index.

Editable example
<style>
{`
  .custom-popover-example {
    z-index: 128;
  }

  .custom-popover-example .popover__panel-content {
    padding: 0;
  }
`}
</style>
<Popover
  className="custom-popover-example"
  button={(buttonProps) => (
    <Button type="secondary" {...buttonProps}>
      Popover with custom styles
    </Button>
  )}
>
  <span>No padding!</span>
</Popover>

Accessibility

  • Popover automatically handles focus management when interacting via a keyboard
  • Be careful with nesting Popovers — currently it has issues with the escape key closing the whole stack
  • When using a custom element with the button prop make sure it's actually rendering an HTML <button> element so that it can be interacted with via a keyboard.