Popover

Open free-form content next to its trigger: a share link, a short form, or the story behind a number.

Installation

You can add the popover using our CLI or manually:

Dark mode ready - this component follows BoardUI semantic tokens automatically. Set up theme switching.

The CLI copies the component source (with its internal dependencies) into your project and installs any required npm packages.

npx boardui@latest add popover

Using a shared UI package or custom import aliases? Configure your installation paths before running the CLI. Copy-paste examples use the default imports.

Anatomy

PopoverTrigger takes the trigger, any BoardUI button, followed by the Popover. PopoverHeader names the popover for assistive technology and PopoverFooter lines up actions. The popover positions itself, flips when it runs out of room, and grows out of the point closest to its trigger.

It is non-modal like Select and Dropdown: the page keeps scrolling, a press outside closes it, and so does pressing the trigger again. Use Modal for content that needs the whole screen.

Arrow

Set showArrow to draw the caret Tooltip uses, in the popover's own colours. It points at the middle of the trigger on every side.

Net revenue retention
112%
<span className="text-body-medium text-text-secondary">Net revenue retention</span><PopoverTrigger> <IconButton icon={RiInformationLine} size="small" aria-label="About net revenue retention" /> <Popover showArrow placement="top" className="w-72"> <PopoverHeader title="Net revenue retention" description="Revenue kept from existing customers over 12 months, upgrades included and churn taken out. Above 100% the base grows on its own." /> </Popover></PopoverTrigger>

Forms

Control the open state with isOpen and onOpenChange to close the popover when a form submits. Anchor it with placement="bottom start" so it stays put while the trigger's label changes.

const [isOpen, setIsOpen] = useState(false); <PopoverTrigger isOpen={isOpen} onOpenChange={setIsOpen}> <Button variant="secondary" leadingIcon={RiLinkM}>Add link</Button> <Popover placement="bottom start"> <form className="flex flex-col gap-3" onSubmit={(event) => { event.preventDefault(); addLink(); setIsOpen(false); }}> <PopoverHeader title="Add link" /> <Input label="URL" type="url" placeholder="https://" inputDir="ltr" autoFocus isRequired /> <Input label="Text" placeholder="Shown instead of the URL" /> <PopoverFooter> <Button variant="secondary" size="small" onClick={() => setIsOpen(false)}>Cancel</Button> <Button type="submit" size="small">Add link</Button> </PopoverFooter> </form> </Popover></PopoverTrigger>

Placement

placement takes a side and an optional alignment. Start and end follow the reading direction, and every side flips to the opposite one when the screen edge is too close.

<PopoverTrigger> <Button variant="secondary">Top</Button> <Popover showArrow placement="top" className="w-auto">…</Popover></PopoverTrigger> // Also: "bottom" (the default), "start", "end",// and aligned variants such as "bottom start" or "top end".

Right-to-left

Wrap the region in DirectionProvider. The popover carries the direction into its portal, so its text, alignment, and start and end placements mirror.

import { DirectionProvider } from "@/components/foundations/direction/direction"; <DirectionProvider locale="ar"> <PopoverTrigger> <Button variant="secondary">مشاركة</Button> <Popover showArrow> <PopoverHeader title="مشاركة لوحة المعلومات" description="يمكن لأي شخص في مساحة العمل لديه الرابط عرضها." /> <Input aria-label="رابط لوحة المعلومات" value={url} isReadOnly inputDir="ltr" /> </Popover> </PopoverTrigger></DirectionProvider>

Keyboard and accessibility

Opening the popover moves focus into it. Escape closes it and returns focus to the trigger, and tabbing out of it closes it too. The trigger announces whether the popover is open.

Reduced motion keeps the fade and drops the scale and blur.