Popover

An overlay positioned relative to a trigger.

There is no built-in way to create popovers in HTML. Popover helps achieve accessible popovers that can be styled as needed.

Features

  • Accessible and keyboard navigable.
  • Easily styleable, including entry and exit animations.
  • Focus is moved into the popover on mount, and restored to the trigger element on unmount.
  • While open, focus is contained within the popover, preventing the user from tabbing outside.
  • Exposed to assistive technology as a dialog with ARIA.
  • The popover is automatically labeled by a nested PopoverTitle element.
  • Content outside the popover is hidden from assistive technologies while it is open.
  • Esc closes the component automatically.
  • Automatically flips and adjusts position to avoid overlapping with the edge of the viewport.

Keyboard interactions

PropTypeDefault
SpaceOpens the popover.
EnterOpens the popover.
TabMoves focus to the next focusable element.
Shift + TabMoves focus to the previous focusable element.
EscCloses the popover and moves focus to the PopoverTrigger.

Parts and their API

PopoverTrigger

The PopoverTrigger component does not render any DOM elements. It only passes through its children.

Props

PropTypeDefaultDescription
children *ReactNodePopoverTrigger needs at least two children: a <Button> and a <Popover>.
isOpenbooleanWhether the popover is open by default (controlled).
defaultOpenbooleanWhether the overlay is open by default (uncontrolled).

Events

NameTypeDescription
onOpenChange(isOpen: boolean) => voidHandler that is called when the overlay's open state changes.

Popover

An overlay positioned relative to a trigger.

Props

PropTypeDefaultDescription
side'center' | 'left' | 'right' | 'top' | 'bottom''center'Side of the screen the popover will open from.
offset"number"Spacing between the popover and the trigger across the main axis.
crossOffset"number"Spacing between the popover and the trigger across the cross axis.
childrenReactNode | (opts: PopoverRenderProps) => ReactNodeChildren of the popover. A function may be provided to access a function to close the popover.
classNamestring | (values: ButtonRenderProps) => stringThe CSS className for the element. A function may be provided to compute the class based on component state.
styleCSSPropertiesThe inline style for the element.

Accessibility

NameTypeDescription
idstringThe element's unique identifier.
aria-labelstringDefines a string value that labels the current element.
aria-labelledbystringIdentifies the element (or elements) that labels the current element.
aria-describedbystringIdentifies the element (or elements) that describes the object.
aria-detailsstringIdentifies the element (or elements) that provide a detailed, extended description for the object.

The <PopoverTitle> has the same props as an <H2> and is mandatory so assistive technology announces the popover correctly.

If you want to visually hide the title, wrap it with <VisuallyHidden>.

Usage

PopoverExample.tsx
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
  30. 30
  31. 31
  32. 32
  33. 33
  34. 34
  35. 35
  36. 36
  37. 37
  38. 38
  39. 39
  40. 40
  41. 41
  42. 42
  43. 43
  44. 44
  45. 45
  46. 46
  47. 47
  48. 48
  49. 49
  50. 50
  51. 51
  52. 52
  53. 53
  54. 54
  55. 55
  56. 56
  57. 57
  58. 58
  59. 59
  60. 60
  61. 61
  62. 62
<PopoverTrigger>
<Button variant="ghost" className="border text-muted-400">
<Icon name="avatar" />
<VisuallyHidden>Account</VisuallyHidden>
</Button>
<Popover>
{({ close }) => (
<>
<VisuallyHidden>
<PopoverTitle>Account</PopoverTitle>
</VisuallyHidden>
<Flex orientation="vertical">
<Flex orientation="vertical" gap="4" className="w-full border-b border-muted-100 px-5 py-3">
<Flex gap="8" className="mb-3">
<Icon name="avatar" className="h-10 w-10 text-muted-300" />
<Flex orientation="vertical">
<Span weight="semibold" className="leading-snug">
John Doe
</Span>
<Span size="xs" className="leading-tight text-muted-400">
john@gmail.com
</Span>
</Flex>
</Flex>
<Button variant="ghost" className="flex w-full justify-start gap-2 font-normal text-muted-500" autoFocus onPress={close}>
<Icon name="magnifying-glass" />
<Span>Search</Span>
</Button>
<Button variant="ghost" className="flex w-full justify-start gap-2 font-normal text-muted-500" onPress={close}>
<Icon name="avatar" />
<Span>Profile</Span>
</Button>
<Button variant="ghost" className="flex w-full justify-start gap-2 font-normal text-muted-500" onPress={close}>
<Icon name="dashboard" />
<Span>Feed</Span>
</Button>
<Button variant="ghost" className="flex w-full justify-start gap-2 font-normal text-muted-500" onPress={close}>
<Icon name="bar-chart" />
<Span>Analytics</Span>
</Button>
</Flex>
<Flex orientation="vertical" gap="4" className="w-full border-b border-muted-100 px-5 py-3">
<Button variant="ghost" className="flex w-full justify-start gap-2 font-normal text-muted-500" onPress={close}>
<Icon name="gear" />
<Span>Settings</Span>
</Button>
<Button variant="ghost" className="flex w-full justify-start gap-2 font-normal text-muted-500" onPress={close}>
<Icon name="question-mark" />
<Span>Support</Span>
</Button>
</Flex>
<Flex orientation="vertical" gap="4" className="w-full px-5 py-3">
<Button variant="ghost" className="flex w-full justify-start gap-2 font-normal text-muted-500" onPress={close}>
<Icon name="exit" />
<Span>Log out</Span>
</Button>
</Flex>
</Flex>
</>
)}
</Popover>
</PopoverTrigger>

Here's the <Popover /> component in action.

Source

Styling

tailwind.css
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
/* ### Overlays ### */
@layer components {
/* Popover */
.popover {
@apply min-w-[18rem] rounded-xl border bg-background;
}
.popover-arrow {
@apply fill-background stroke-muted-300 stroke-1;
}
}