Popover
- Component overview: Click-triggered overlay container for more complex action content, descriptive content, or custom layouts.
- Component group: Tips
- Implementation note:
PopoverContentretains the Radix positioning, Portal, and animation structure; the design layer owns the overlay card visuals. - Default interaction:
childrenis the trigger element by default. - Figma spec
Basic Usage
Result
Loading...
Live Editor
render( <Popover content="Popover content"> <Button variant="secondary" size="sm"> Open Popover </Button> </Popover>, )
Position Variants
Popover supports 8 primary positions controlled by combining side and align.
Result
Loading...
Live Editor
render( <div style={{ display: 'flex', flexDirection: 'column', gap: 24, padding: '48px 0' }}> <div style={{ display: 'flex', justifyContent: 'center', gap: 24 }}> {[ { label: 'Top-Start', side: 'top', align: 'start' }, { label: 'Top', side: 'top', align: 'center' }, { label: 'Top-End', side: 'top', align: 'end' }, ].map(({ label, side, align }) => ( <Popover key={label} title={label} content="Popover content" side={side} align={align} defaultOpen > <Button variant="secondary" size="sm"> {label} </Button> </Popover> ))} </div> <div style={{ display: 'flex', justifyContent: 'space-between', padding: '0 120px' }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 24 }}> {[ { label: 'Left-Start', side: 'left', align: 'start' }, { label: 'Left', side: 'left', align: 'center' }, { label: 'Left-End', side: 'left', align: 'end' }, ].map(({ label, side, align }) => ( <Popover key={label} title={label} content="Popover content" side={side} align={align} defaultOpen > <Button variant="secondary" size="sm"> {label} </Button> </Popover> ))} </div> <div style={{ display: 'flex', flexDirection: 'column', gap: 24 }}> {[ { label: 'Right-Start', side: 'right', align: 'start' }, { label: 'Right', side: 'right', align: 'center' }, { label: 'Right-End', side: 'right', align: 'end' }, ].map(({ label, side, align }) => ( <Popover key={label} title={label} content="Popover content" side={side} align={align} defaultOpen > <Button variant="secondary" size="sm"> {label} </Button> </Popover> ))} </div> </div> <div style={{ display: 'flex', justifyContent: 'center', gap: 24 }}> {[ { label: 'Bottom-Start', side: 'bottom', align: 'start' }, { label: 'Bottom', side: 'bottom', align: 'center' }, { label: 'Bottom-End', side: 'bottom', align: 'end' }, ].map(({ label, side, align }) => ( <Popover key={label} title={label} content="Popover content" side={side} align={align} defaultOpen > <Button variant="secondary" size="sm"> {label} </Button> </Popover> ))} </div> </div>, )
Title and Content
Result
Loading...
Live Editor
render( <Popover title="Popover Title" content="Popover is suited for more complex interactive content, not just a single line of hint text." > <Button variant="tertiary" size="sm"> Open </Button> </Popover>, )
Custom Content Area
Result
Loading...
Live Editor
render( <Popover className="w-[320px]" content={ <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}> <div style={{ fontWeight: 600 }}>Custom Content</div> <div style={{ fontSize: 14, lineHeight: 1.6 }}> Custom nodes can be passed directly — the internal Popover shell is still used. </div> </div> } > <Button variant="tertiary" size="sm"> Open Custom </Button> </Popover>, )
Popover Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Trigger element. By default the popover uses children as the trigger. |
content | ReactNode | — | Overlay content |
title | ReactNode | — | Overlay title |
side | 'top' | 'bottom' | 'left' | 'right' | 'bottom' | Popup direction |
align | 'start' | 'center' | 'end' | 'center' | Alignment |
sideOffset | number | 8 | Distance from the trigger element (px) |
className | string | — | Custom style class |
asChild | boolean | true | Whether to use children as the trigger itself |