Popover
A small panel that opens from a trigger and can hold controls: a filter, a few choices, a short form. Use a Tooltip for a hint, and a Modal when the page should stop until the reader answers.
Live preview
Rendered by the component itself from the same generated tokens as the rest of the site.
const [themes, setThemes] = useState({ light: true, dark: false })
<Popover
trigger={(props) => <Button {...props}>Filter themes</Button>}
>
<ThemeOptions value={themes} onChange={setThemes} />
</Popover>Installation
Install it with the shadcn CLI, along with everything it uses. New project? Set it up first, as Quick start shows.
npx shadcn@latest add https://www.graphite-ui.com/r/popover.jsonimport { Popover } from '@/components/ui/popover'Anatomy
A trigger you render and a panel the Popover owns. The trigger is a render prop, so it receives aria-expanded, aria-controls and the click handler to spread onto your own Button.
- TriggerRequired
- ContentRequiredCan include interactive elements.
Variants
Placement chooses the side and align chooses where along the trigger the panel sits; the caret stays on the trigger’s centre either way. The panel does not flip when it runs out of room. Tab tip is the kit’s second set: the open trigger and the panel join into one shape.
API reference
Generated from the contract, versioned with it, and checked by drift-check. This table cannot describe props the component does not have.
| Prop | Type | Default | Notes |
|---|---|---|---|
| placement | 'top' | 'bottom' | 'left' | 'right' | — | The kit's Position. The panel does not flip when it runs out of room. |
| align | 'start' | 'center' | 'end' | — | The kit's Alignment, Center by default as the kit orders it. The caret stays on the trigger's centre; Start and End put it 16 from that edge of the panel. |
| variant | 'default' | 'tab-tip' | — | The kit's Popover and Popover - Tab tip sets. Tab tip opens below only, aligned start or end, and joins the open trigger to the panel with no gap and no caret, the trigger taking the panel's fill. |
| modal | boolean | — | Whether it traps focus. |
| defaultOpen | boolean | — | Starts open. For documentation surfaces that need to show the open state; dismissal still comes from the shared Overlay base. |
| label | string | — | The accessible name of a modal Popover's dialog. Ignored without modal, where the panel has no role to name. |
Design tokens
Every swatch is live. Change the source color in the header and this table repaints, because it reads the same roles the component does.
elevationThe panel and caret fill, elevation-01, the kit's Layer/layer-01; and the open Tab tip trigger, which takes the same fill.shadowThe lift, shadow-overlay, the kit's Shadows/Menu, cast by the panel and caret as one shape (or by trigger and panel together on a Tab tip). No edge.spacingPadding, 16.radiusPanel corner.motionThe entrance fade, shared with the other overlays. No exit: content unmounts on close, which is what keeps the no-nesting throw off the prerender path.
Usage
The contract's prohibitions, written as the choices you will actually face.
Do
- Use a Popover when the reader needs to act on what is inside it: toggle a filter, pick an option, fill one field.
- Render the trigger as a Button and spread the props the Popover hands you, so aria-expanded and aria-controls land on the real control.
- Turn modal on when the content is a small task the reader should finish or dismiss before moving on, and give it a label so the dialog has a name.
- Choose the placement with the most room around the trigger. The panel will not move itself back on screen.
Don’t
- Nest a Popover inside another one. The inner one throws as soon as the outer one opens.
- Give one instance its own way of closing. Dismissal comes from the shared Overlay base, and a Popover that needs another pattern is a different component.
- Use a Popover for a line of help that appears on hover. That is a Tooltip.
- Fit a whole workflow into one. Once it needs a title and a footer of actions, it is a Modal.
Accessibility
What the component does for you, and what it leaves to you.
- Keyboard
- Enter and Space on the trigger open it. The panel follows the trigger in the document, so Tab moves into it. Escape closes it from anywhere.
- Roles
- The trigger gets
aria-expandedandaria-controls. With modal on, the panel isrole="dialog"witharia-modal; name it with thelabelprop. Without modal it has no role, and label is ignored. - Focus
- A modal Popover takes focus when it opens and keeps Tab inside it. A non-modal one leaves focus where it was. Either way, focus returns to the trigger when it closes. The content can be controlled from outside: a re-render while it is open leaves focus alone.
- Pointer
- Pressing the trigger again closes it, and so does a press anywhere outside the panel. There is no close button of its own.
- Contrast
- The panel is
elevation-01lifted byshadow-overlay, with no edge, as the kit draws every overlay. The shadow is what separates it from the page in Light. - Motion
- It fades in on the fast motion step and appears at once under prefers-reduced-motion. It does not animate out.
Figma parity
The kit’s Popover page draws three public sets: the popover itself, a Tab tip, and the Popover item its variants are built from. Placement, align and variant cover the drawn axes; modal and label have no kit axis because behaviour and a name are not drawn.
| Kit axis | Values | Code | How it maps |
|---|---|---|---|
| Position | Top · Bottom · Left · Right | placement | One to one. |
| Alignment | Start · Center · End | align | One to one, Center by default. Start and End put the caret 16 from that edge, so on the kit’s 32px trigger the panel overhangs by 6. |
| Visible | True · False | defaultOpen | Runtime state. The kit draws both because Figma has no other way to show it; defaultOpen only chooses where it starts. |
| Popover item: Caret tip | True in every variant | — | Always drawn: 12 by 6 in the panel fill, its base 4 from the trigger. |
| Popover item: Shadow | True in every variant | — | Always drawn: shadow-overlay, with no edge. Shadow=False is drawn by no Popover variant. |
| Popover item: Zero radius | True in every variant | — | Always square. The 2px corner behind it is drawn by no Popover variant. |
| Set | Popover - Tab tip | variant="tab-tip" | Alignment Start · End and Open. The open trigger takes the panel fill and joins it with no gap and no caret, under one shadow. The kit’s trigger is a 48px ghost icon-only button. |
