XDSPopover@xds/core · Popover

Usage

A click-triggered overlay anchored to a button or trigger element. Use it for secondary actions, inline confirmations, or supplementary information that does not warrant a full dialog. For hover previews use HoverCard, for brief helper text use Tooltip.

Best practices

GuidancePractices
DoKeep popover content focused on a single task or piece of information.
DoProvide a clear way to close — either by clicking outside or with an explicit close button.
Don'tNest popovers inside other popovers — it creates confusing focus and navigation.
Don'tUse a popover for content that requires heavy user input — use a Dialog instead.
Don'tPut too much content in a popover — if it needs scrolling, use a Dialog instead.

Anatomy

ElementDescription
HeaderrequiredContains the title, optional subheader, and close button.
BodyrequiredMain content area of the popover.
Trigger ElementrequiredThe button or link that toggles the popover open.

Import

ts
import {XDSPopover} from '@xds/core/Popover'

Props

PropTypeDescription
contentrequired
ReactNodeContent to display inside the popover.
children
ReactNodeTrigger element. Must contain a <button> or [role="button"] element.
anchorRef
React.RefObject<HTMLElement>External ref to use as the popover anchor in sibling mode.
placement
LayerPlacement (default: 'below')Position placement relative to the trigger.
alignment
LayerAlignment (default: 'start')Alignment along the placement axis.
isOpen
booleanWhether the popover is shown in controlled mode.
onOpenChange
(isOpen: boolean) => voidCallback fired when the popover visibility changes.
isEnabled
boolean (default: true)When false, trigger interactions are ignored.
width
number | string (default: 'auto')Width of the popover container.
label
stringAccessible label for the popover dialog.
hasCloseButton
boolean (default: true)Whether to include a hidden close button for accessibility.
closeButtonLabel
string (default: 'Close popover')Label for the hidden close button.
xstyle
StyleXStylesStyleX styles for layout customization (margins, positioning, sizing). Must be a stylex.create() value — not an inline style object like style={{}}.

Sub-components

Popover is a compound component with 1 sub-component.

XDSPopover

A click-triggered popover for displaying interactive content anchored to a trigger element.
PropTypeDescription
contentrequired
ReactNodeContent to display inside the popover.
children
ReactNodeTrigger element. Must contain a <button> or [role="button"] element.
anchorRef
React.RefObject<HTMLElement>External ref to use as the popover anchor in sibling mode.
placement
LayerPlacement (default: 'below')Position placement relative to the trigger.
alignment
LayerAlignment (default: 'start')Alignment along the placement axis.
isOpen
booleanWhether the popover is shown in controlled mode.
onOpenChange
(isOpen: boolean) => voidCallback fired when the popover visibility changes.
isEnabled
boolean (default: true)When false, trigger interactions are ignored.
width
number | string (default: 'auto')Width of the popover container.
label
stringAccessible label for the popover dialog.
hasCloseButton
boolean (default: true)Whether to include a hidden close button for accessibility.
closeButtonLabel
string (default: 'Close popover')Label for the hidden close button.
xstyle
StyleXStylesStyleX styles for layout customization (margins, positioning, sizing). Must be a stylex.create() value — not an inline style object like style={{}}.

Examples

Common configurations, variations, and states.
Popover — Confirm ActionInline confirmation popover for destructive actions with delete and cancel buttons.
tsx
'use client';
import {useState} from 'react';
import {XDSPopover} from '@xds/core/Popover';
import {XDSButton} from '@xds/core/Button';
import {XDSVStack, XDSHStack} from '@xds/core/Layout';
import {XDSHeading, XDSText} from '@xds/core/Text';
export default function PopoverConfirmAction() {
const [isOpen, setIsOpen] = useState(false);
return (
<XDSPopover
placement="below"
label="Confirm deletion"
width={300}
isOpen={isOpen}
onOpenChange={setIsOpen}
content={
<XDSVStack gap={3}>
<XDSHeading level={4}>Delete project?</XDSHeading>
<XDSText type="body">
This will permanently delete the project and all its data. This
action cannot be undone.
</XDSText>
<XDSHStack gap={2} hAlign="end">
<XDSButton
label="Delete"
variant="destructive"
onClick={() => setIsOpen(false)}>
Delete
</XDSButton>
<XDSButton
label="Cancel"
variant="ghost"
onClick={() => setIsOpen(false)}>
Cancel
</XDSButton>
</XDSHStack>
</XDSVStack>
}>
<XDSButton label="Delete project" variant="destructive">
Delete project
</XDSButton>
</XDSPopover>
);
}
Popover — Filter PanelPopover with checkbox filters and apply/reset actions.
tsx
'use client';
import {useState} from 'react';
import {XDSPopover} from '@xds/core/Popover';
import {XDSButton} from '@xds/core/Button';
import {XDSVStack, XDSHStack} from '@xds/core/Layout';
import {XDSHeading} from '@xds/core/Text';
import {XDSCheckboxInput} from '@xds/core/CheckboxInput';
import {XDSDivider} from '@xds/core/Divider';
export default function PopoverFilterPanel() {
const [isOpen, setIsOpen] = useState(false);
const [filters, setFilters] = useState({
active: true,
archived: false,
drafts: true,
shared: false,
});
const toggle = (key: keyof typeof filters) =>
setFilters(prev => ({...prev, [key]: !prev[key]}));
return (
<XDSPopover
placement="below"
label="Filter"
width={240}
isOpen={isOpen}
onOpenChange={setIsOpen}
content={
<XDSVStack gap={3}>
<XDSHeading level={4}>Filter by status</XDSHeading>
<XDSDivider />
<XDSCheckboxInput
label="Active"
value={filters.active}
onChange={() => toggle('active')}
/>
<XDSCheckboxInput
label="Archived"
value={filters.archived}
onChange={() => toggle('archived')}
/>
<XDSCheckboxInput
label="Drafts"
value={filters.drafts}
onChange={() => toggle('drafts')}
/>
<XDSCheckboxInput
label="Shared with me"
value={filters.shared}
onChange={() => toggle('shared')}
/>
<XDSDivider />
<XDSHStack gap={2} hAlign="end">
<XDSButton
label="Apply"
variant="primary"
onClick={() => setIsOpen(false)}>
Apply
</XDSButton>
<XDSButton
label="Reset"
variant="ghost"
onClick={() =>
setFilters({
active: true,
archived: false,
drafts: true,
shared: false,
})
}>
Reset
</XDSButton>
</XDSHStack>
</XDSVStack>
}>
<XDSButton label="Filter">Filter</XDSButton>
</XDSPopover>
);
}
Popover — Keyboard ShortcutsPopover displaying a list of keyboard shortcuts with key and description pairs.
tsx
'use client';
import {XDSPopover} from '@xds/core/Popover';
import {XDSButton} from '@xds/core/Button';
import {XDSVStack, XDSHStack} from '@xds/core/Layout';
import {XDSHeading, XDSText} from '@xds/core/Text';
import {XDSDivider} from '@xds/core/Divider';
const shortcuts = [
{key: '⌘K', action: 'Command palette'},
{key: '⌘/', action: 'Toggle sidebar'},
{key: '⌘.', action: 'Quick actions'},
];
export default function PopoverKeyboardShortcuts() {
return (
<XDSPopover
placement="below"
label="Keyboard shortcuts"
width={260}
content={
<XDSVStack gap={2}>
<XDSHeading level={4}>Keyboard shortcuts</XDSHeading>
<XDSDivider />
{shortcuts.map(s => (
<XDSHStack key={s.key} gap={3}>
<XDSText type="body" weight="bold">
{s.key}
</XDSText>
<XDSText type="body">{s.action}</XDSText>
</XDSHStack>
))}
</XDSVStack>
}>
<XDSButton label="Shortcuts">Shortcuts</XDSButton>
</XDSPopover>
);
}
Popover — Settings PanelPopover with toggle switches for managing user preferences like notifications, dark mode, and sounds.
tsx
'use client';
import {useState} from 'react';
import {XDSPopover} from '@xds/core/Popover';
import {XDSButton} from '@xds/core/Button';
import {XDSVStack} from '@xds/core/Layout';
import {XDSHeading} from '@xds/core/Text';
import {XDSSwitch} from '@xds/core/Switch';
import {XDSDivider} from '@xds/core/Divider';
export default function PopoverSettingsPanel() {
const [notifications, setNotifications] = useState(true);
const [darkMode, setDarkMode] = useState(false);
const [sounds, setSounds] = useState(true);
return (
<XDSPopover
placement="below"
label="Settings"
width={280}
content={
<XDSVStack gap={3}>
<XDSHeading level={4}>Settings</XDSHeading>
<XDSDivider />
<XDSSwitch
label="Notifications"
description="Receive push notifications"
value={notifications}
onChange={setNotifications}
/>
<XDSSwitch
label="Dark mode"
description="Use dark color theme"
value={darkMode}
onChange={setDarkMode}
/>
<XDSSwitch
label="Sounds"
description="Play sounds for actions"
value={sounds}
onChange={setSounds}
/>
</XDSVStack>
}>
<XDSButton label="Settings">Settings</XDSButton>
</XDSPopover>
);
}

Showcase source

tsx
'use client';
import {useState} from 'react';
import {XDSPopover} from '@xds/core/Popover';
import {XDSButton} from '@xds/core/Button';
import {XDSVStack} from '@xds/core/Layout';
import {XDSText, XDSHeading} from '@xds/core/Text';
import {XDSDivider} from '@xds/core/Divider';
export default function PopoverShowcase() {
const [isOpen, setIsOpen] = useState(true);
return (
<XDSPopover
isOpen={isOpen}
onOpenChange={setIsOpen}
placement="below"
label="Settings"
width={280}
content={
<XDSVStack gap={3}>
<XDSHeading level={4} tabIndex={0}>Settings</XDSHeading>
<XDSDivider />
<XDSText type="body">Notifications, dark mode, and sound preferences.</XDSText>
</XDSVStack>
}>
<XDSButton label="Settings">Settings</XDSButton>
</XDSPopover>
);
}