Popover

A popover opens a small panel next to the control that opened it, such as a filter form or a share panel.

A popover opens a panel beside its trigger and leaves the rest of the page in reach. Use it for a few controls that belong to one button, such as filters or a share link. A list of actions is a Dropdown Menu, a short label on hover is a Tooltip, and a task that must be finished before people go on is a Dialog.

Playground

Usage

import { Button, Checkbox, Flex, Popover, Text } from '@kushagradhawan/kookie-ui';
import { HugeiconsIcon } from '@hugeicons/react';
import { FilterIcon } from '@hugeicons/core-free-icons';
 
export function StatusFilter() {
  return (
    <Popover.Root>
      <Popover.Trigger>
        <Button>
          <HugeiconsIcon icon={FilterIcon} strokeWidth={1.75} />
          Filter
        </Button>
      </Popover.Trigger>
      <Popover.Content width="240px">
        <Flex direction="column" gap="3">
          {['Open', 'In review', 'Closed'].map((status, index) => (
            <Flex gap="2" align="center" key={status}>
              <Checkbox id={`status-${index}`} defaultChecked={status === 'Open'} />
              <Text as="label" htmlFor={`status-${index}`} size="2">
                {status}
              </Text>
            </Flex>
          ))}
        </Flex>
      </Popover.Content>
    </Popover.Root>
  );
}

Anatomy

<Popover.Root>
  <Popover.Trigger />
  <Popover.Anchor />
  <Popover.Content>
    <Popover.Toolbar>
      <Popover.Toolbar.Left />
      <Popover.Toolbar.Center>
        <Popover.Toolbar.Title />
      </Popover.Toolbar.Center>
      <Popover.Toolbar.Right />
    </Popover.Toolbar>
    <Popover.Close />
  </Popover.Content>
</Popover.Root>
  • Root: Holds the open state.
  • Trigger: The element that opens the popover. It takes one child element, usually a Button.
  • Anchor: Optional. Places the popover against a different element than the trigger.
  • Content: The panel, rendered in a portal. It is at least as wide as the trigger.
  • Toolbar: Optional. A row pinned to the top of the panel, with left, center and right sections. It stays in place while the content scrolls.
  • Toolbar.Title: A title in the toolbar. It names the popover for screen readers.
  • Close: Closes the popover when its child is pressed. It takes one child element.

Best practices

Keep the content small. A popover holds a few controls. If it needs scrolling or several sections, use a Dialog or a page.

Apply changes as people make them. A filter that updates the list on each tick needs no Apply button. If changes must wait, add one loud button at the end.

Open it from a press, not a hover. A popover holds controls people use, and hover content vanishes when the pointer moves. Use a Tooltip for a label on hover.

Don't put required steps in it. People close a popover by pressing anywhere else. Anything they must finish belongs in a Dialog.

Size

size sets the padding and the corner, on the same steps as a Card. Size 2 is the default, and an unset size follows the Theme size. Match the popover's size to the controls inside it.

Placement

The panel opens below the trigger with an 8px gap and lines up with the trigger's start edge. It flips and shifts to stay at least 10px inside the window. Set side ('top', 'right', 'bottom', 'left'), align ('start', 'center', 'end') or sideOffset to change it.

Examples

Share panel with a toolbar

Use the toolbar for a title and a close button that stay pinned while the content scrolls.

<Popover.Content width="320px">
  <Popover.Toolbar>
    <Popover.Toolbar.Left>
      <Popover.Toolbar.Title>Share</Popover.Toolbar.Title>
    </Popover.Toolbar.Left>
    <Popover.Toolbar.Right>
      <Popover.Close>
        <Button iconOnly emphasis="quiet" size="1" aria-label="Close">
          <HugeiconsIcon icon={Cancel01Icon} strokeWidth={1.75} />
        </Button>
      </Popover.Close>
    </Popover.Toolbar.Right>
  </Popover.Toolbar>
  {/* share link and people */}
</Popover.Content>

Anchored to a field

Popover.Anchor places the panel against a wider element, such as a search field, while a small button inside it opens the popover.

Content that bleeds to the edge

The panel publishes its padding, so a child with m="bleed" reaches the panel's edges, such as a full-width list.

Props

Root

PropTypeDefaultDescription
openbooleanThe open state. Controlled
defaultOpenbooleanfalseThe open state on first render
onOpenChange(open: boolean) => voidCalled when the popover opens or closes
modalbooleanfalseBlocks the rest of the page and keeps focus inside

Content

PropTypeDefaultDescription
size'1' | '2' | '3' | '4', responsiveTheme sizePadding and corner
side'top' | 'right' | 'bottom' | 'left''bottom'Which side of the trigger it opens on
align'start' | 'center' | 'end''start'How it lines up with the trigger
sideOffsetnumber8The gap to the trigger, in pixels
collisionPaddingnumber10The space kept from the window's edges
widthstring, responsiveA fixed width
minWidthstring, responsiveTrigger widthThe narrowest width
maxWidthstring, responsive'480px'The widest width
heightstring, responsiveA fixed height
minHeightstring, responsiveThe shortest height
maxHeightstring, responsiveThe tallest height. Content scrolls past it
containerHTMLElementdocument.bodyWhere the portal renders
forceMountbooleanKeeps the popover in the DOM while closed
onEscapeKeyDown(event) => voidCalled on Escape. Call preventDefault() to keep it open
onInteractOutside(event) => voidCalled on a press or focus outside. Call preventDefault() to keep it open

Content also accepts every attribute of the HTML div element.

Toolbar

Popover.Toolbar, its Left, Center and Right sections, and Toolbar.Title take no props of their own. Each accepts the attributes of its element: div for the toolbar and sections, span for the title.

Trigger, Close and Anchor

Trigger and Close take one child element and pass their behaviour to it. Anchor renders a div around its children.

Accessibility

KeyAction
Enter or SpaceOpens or closes the popover from the trigger
EscapeCloses the popover and returns focus to the trigger
TabMoves focus through the panel, then out of it
  • Name: Popover.Toolbar.Title names the panel with aria-labelledby. Without a toolbar, set aria-label or aria-labelledby on Content.
  • State: The trigger sets aria-expanded and aria-controls, so a screen reader announces whether the panel is open.
  • Focus: Focus moves into the panel when it opens and returns to the trigger when it closes. The page stays in reach, so Tab can leave the panel unless modal is set.
  • Dismissal: Escape and a press outside the panel both close it.