Popover
A click-triggered floating dialog anchored to a trigger element.
Examples
Section titled “Examples”Default
Section titled “Default”<Popover {...args} content={popoverContent} padding={3}> <Button label="Open popover" /></Popover>With Layout
Section titled “With Layout”<Popover {...args} content={ <Layout content={ <LayoutContent> <Text as="p" color="secondary"> Review project activity and open follow-ups. </Text> </LayoutContent> } header={<LayoutHeader title="Project details" />} height="auto" /> }> <Button label="Open layout popover" /></Popover>Placements
Section titled “Placements”<div style={{ display: 'grid', gap: 24, justifyItems: 'center', padding: '80px', }}> <Popover content={popoverContent} label="Above" padding={3} placement="above"> <Button label="Above" /> </Popover> <HStack gap={6}> <Popover content={popoverContent} label="Start" padding={3} placement="start"> <Button label="Start" /> </Popover> <Popover content={popoverContent} label="End" padding={3} placement="end"> <Button label="End" /> </Popover> </HStack> <Popover content={popoverContent} label="Below" padding={3} placement="below"> <Button label="Below" /> </Popover></div>Alignments
Section titled “Alignments”<HStack gap={4} style={{padding: '40px'}}> <Popover alignment="start" content={popoverContent} label="Start" padding={3} placement="below"> <Button label="Start" /> </Popover> <Popover alignment="center" content={popoverContent} label="Center" padding={3} placement="below"> <Button label="Center" /> </Popover> <Popover alignment="end" content={popoverContent} label="End" padding={3} placement="below"> <Button label="End" /> </Popover></HStack><div dir="rtl" style={{ display: 'grid', gap: '220px 320px', gridTemplateColumns: 'repeat(2, max-content)', justifyContent: 'center', minHeight: 640, minWidth: 960, padding: '160px 240px', }}> <Popover content={<Text as="p">Logical start (right in RTL)</Text>} hasAutoFocus={false} hasCloseButton={false} isDismissable={false} isOpen label="Logical start" padding={3} placement="start" width={176}> <Button label="placement=start" /> </Popover> <Popover content={<Text as="p">Logical end (left in RTL)</Text>} hasAutoFocus={false} hasCloseButton={false} isDismissable={false} isOpen label="Logical end" padding={3} placement="end" width={176}> <Button label="placement=end" /> </Popover> <Popover alignment="start" content={<Text as="p">Below, aligned to logical start</Text>} hasAutoFocus={false} hasCloseButton={false} isDismissable={false} isOpen label="Below, start aligned" padding={3} placement="below" width={176}> <Button label="alignment=start" /> </Popover> <Popover alignment="end" content={<Text as="p">Below, aligned to logical end</Text>} hasAutoFocus={false} hasCloseButton={false} isDismissable={false} isOpen label="Below, end aligned" padding={3} placement="below" width={176}> <Button label="alignment=end" /> </Popover></div>Controlled
Section titled “Controlled”() => { const [isOpen, setIsOpen] = useState(false); return ( <HStack gap={2}> <Button label={isOpen ? 'Close externally' : 'Open externally'} onClick={() => setIsOpen(v => !v)} variant="secondary" /> <Popover content={popoverContent} isOpen={isOpen} label="Controlled" onOpenChange={setIsOpen} padding={3} placement="below"> <Button label="Trigger" /> </Popover> </HStack> );}Disabled
Section titled “Disabled”<Popover {...args} content={popoverContent} isEnabled={false} padding={3}> <Button label="Disabled popover" /></Popover>No Close Button
Section titled “No Close Button”<Popover {...args} content={popoverContent} hasCloseButton={false} padding={3}> <Button label="No close button" /></Popover>Match Trigger Width
Section titled “Match Trigger Width”<Popover {...args} content={<Text as="p">This popover matches the trigger width.</Text>} padding={3}> <Button label="Wide trigger button" /></Popover>Custom Width
Section titled “Custom Width”<Popover {...args} content={<Text as="p">A fixed-width popover for richer panels.</Text>} padding={3}> <Button label="Open fixed panel" /></Popover>Nested Popovers
Section titled “Nested Popovers”<Popover content={ <VStack gap={2}> <Text as="p">Outer popover content</Text> <Popover content={ <VStack gap={2}> <Text as="p">Inner popover content</Text> <Button label="Apply" size="sm" variant="primary" /> </VStack> } label="Inner settings" padding={3}> <Button label="Open inner popover" size="sm" /> </Popover> </VStack> } label="Outer settings" padding={3}> <Button label="Open outer popover" /></Popover>Popover
Section titled “Popover”A click-triggered floating dialog anchored to a trigger element.
| Prop | Type | Default | Description |
|---|---|---|---|
alignment | "center" | "start" | "end" | 'start' | Alignment along the placement axis. |
anchorRef | RefObject<HTMLElement | null> | — | External trigger element. When provided without children, Popover attaches click and ARIA behavior directly to this element. |
children | ReactNode | — | Trigger content. Must contain a <button> or [role="button"]. |
className | string | — | Additional CSS class names applied to the popover content. |
closeButtonLabel | string | 'Close popover' | Label for the hidden close button. |
content* | ReactNode | — | Content displayed inside the popover dialog. |
data-testid | string | — | Test ID applied to the popover content. |
hasAutoFocus | boolean | true | Whether to focus the first focusable item after opening. |
hasCloseButton | boolean | true | Whether to include a keyboard-accessible close button. |
id | string | — | Id applied to the popover content element. Falls back to a generated id. Supply this when another element needs a stable aria-controls reference to the popover. |
isDismissable | boolean | true | Whether clicking outside or pressing Escape closes the popover. |
isEnabled | boolean | true | Whether trigger interactions open the popover. |
isLazy | boolean | true | When true, the popover content is not mounted until it first opens; after that it stays mounted, so state inside the content survives close/reopen exactly as if it were always mounted. Set to false when closed content must stay in the accessibility tree or be reachable in the DOM before the first open. |
isOpen | boolean | — | Controlled open state. |
label | string | — | Accessible label for the popover dialog. |
offsetX | number | — | Gap in pixels between the popover and its trigger along the inline axis (for start/end placements). Applied as a logical margin so it stays on the trigger-facing side even when the popover flips. |
offsetY | number | — | Gap in pixels between the popover and its trigger along the block axis (for above/below placements). Applied as a logical margin so it stays on the trigger-facing side even when the popover flips. |
onOpenChange | (isOpen: boolean) => void | — | Callback fired when open state changes. |
padding | 0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | 0 | Inner padding of the popover content. |
placement | "start" | "end" | "above" | "below" | 'below' | Position relative to the trigger. |
ref | Ref<HTMLDivElement> | — | Ref forwarded to the popover content element. |
role | 'dialog' | 'menu' | 'dialog' | ARIA role for the floating content. |
style | CSSProperties | — | Inline styles applied to the popover content. |
width | SizeValue | — | Width of the popover content. Numbers are pixels, strings are used as-is. |