Popover
Shows a few lines or a small control anchored to what it is about.
Examples
Usage
When to use it
- Use
- A small filter, form or picker anchored to its button.
- Use
- A hover preview of the thing a link points to.
- Use
- The explanation of a term beside it (PopoverInfo).
- Not for
- A list of actions. Use Dropdown menu.
- Not for
- A one-line label on an icon. Use Tooltip.
- Not for
- Content that needs the whole page or a decision. Use Dialog.
- Not for
- Content taller than 320px. Use Sheet.
Rules
10 rules
- Must
- Use PopoverInfo for an info icon that explains a term, never a Tooltip. Because a tooltip never opens on touch and closes when the pointer leaves, so the definition is unreadable on a phone.
- Must
- Pass title (it names the button
About <title>) or label to PopoverInfo. Because without either every info button is named "More information" and a screen reader cannot tell them apart. - Must
- Write plain text only in a PopoverInfo; put a link next to the term or use a Popover. Because focus never enters a PopoverInfo, so a link inside it cannot be reached by keyboard.
- Must
- Make an openOnHover trigger a link that works on its own, and show nothing the linked page does not. Because touch and keyboard never see a hover preview, so the trigger has to reach the same facts alone.
- Never
- Put actions in a hover preview. Move them to a DropdownMenu or the linked page. Because a peek closes when the pointer leaves and never opens on touch, so the actions slip away or never appear.
- Should
- Apply a choice on change and keep the popover open, with a PopoverClose "Done" for touch.
- Should
- Lead a preview with the name, one line of what it is, then 2 to 4 facts with numbers.
- Should
- Keep a PopoverInfo to 1 or 2 sentences under 200 characters, with the term as its title.
- Never
- Add an arrow. Keep the arrowless paper panel 6px off the anchor. Because the 6px offset and overlay shadow already tie the panel to its trigger, and an arrow misses the forced-colors frame.
- Should
- Let content scroll inside --available-height and move content past 320px tall into a Sheet.
Accessibility
5 checks
- Enter or Space on the trigger opens or closes it, and pins a peeking preview; Enter on a hover-preview link follows it.
- Tab moves through the content; leaving the panel closes it.
- Escape closes this popover only and returns focus to the trigger.
- PopoverInfo text is also the button description; the button is named `About <title>`.
- Hover previews never peek on touch; PopoverInfo opens by tap.
Edge cases
9 edge cases
- Long content and trace ID
- The title breaks; the panel scrolls inside the available height.
- Hover preview
- Waits 600ms, peeks without focus, closes when the pointer leaves; a press inside pins it.
- Opened at a screen edge
- The panel shifts and flips to stay 16px inside.
- Right to left
- The panel computes rtl inside an RTL portal scope.
- Empty content
- Opens a 32px padded box; nothing is announced but "dialog".
- Slow save inside
- A click outside still dismisses it; the save keeps running.
- Ten clicks on the trigger
- It toggles ten times and ends where the count says.
- Inside a Dialog
- Escape closes the popover first; focus returns to its trigger.
- Zoom 200%
- The panel stays 288px and wraps.
API
8 props
Popover open, defaultOpen, onOpenChangeBase UIdefaultfalsePopoverTrigger openOnHoverbooleandefaultfalsePopoverTrigger delay, closeDelaynumberdefault600, 300PopoverContent side, align, sideOffsetBase UIdefault'bottom', 'center', 6PopoverContent collisionPaddingnumberdefault16PopoverInfo titleReactNodePopoverInfo labelstringdefault'About ' + titlePopoverInfo side, alignBase UIdefault'top', 'center'
Tokens
14 tokens
--surface-page- var(--paper)
--elev-overlay- 0 0 0 1px var(--line-frame), 0 12px 24px -12px oklch(var(--text-on-field-c) / var(--shadow-alpha))
--radius-frame- 10px
--popover- 288px
--space-4- 16px
--dur-base- 160ms
--dur-fast- 120ms
--ease-step- cubic-bezier(0.2, 0, 0, 1)
--type-label- 400 14px/16px var(--font-sans)
--type-body-sm- 380 13px/18px var(--font-sans)
--text-1- var(--ink)
--text-2- oklch(var(--ink-c) / 0.80)
--text-3- oklch(var(--ink-c) / 0.66)
--popover-sm- 256px