DocumentationComponentsTheme CreatorGitHub
Theme CreatorGitHubIntroduction
InstallationUsageTheming
ComponentsAccordionAction SheetAlertAlertDialogArea ChartAspectRatioAvatarBadgeBannerBar ChartBottom NavBreadcrumbButtonButtonGroupCalendarCardCarouselChartChatBubbleChatBubbleNewCheckboxChipCoachMarkCodeBlockCollapsibleColor PickerComboboxCommandContainerContextMenuDate PickerDividerDrawerDropdown MenuEmptyStateFABFieldFieldsetFile UploadFlexFormGridHoverCardIconButtonLabelLine ChartLinkMenubarMeterModalNavigation MenuNumberFieldOTP InputPaginationPickerPie ChartPopoverProgressPromptInputRadar ChartRadial ChartRadioRatingScroll AreaSearchSegmentedControlSelectShortcutSidebarSkeletonSliderCircularSliderMediaTrimmerSpacerSpinnerSplit ButtonStackStatusStepperSurfaceSwitchTableTabsTextareaTime PickerToastToggleButtonToggleGroupTokenizerToolbarTooltipTop Header DesktopTop Header Mobile
Contributing
Components

Popover

A floating panel that appears next to its trigger and holds interactive content. Click-activated, with full keyboard support, positioning, an optional arrow, and compound parts for header, body, and footer.

Playground

Installation

pnpm add @tessinaui/ui

Usage

import {
  Popover,
  PopoverTrigger,
  PopoverContent,
  PopoverHeader,
  PopoverTitle,
  PopoverDescription,
  PopoverBody,
  PopoverFooter,
  PopoverArrow,
  PopoverClose,
} from "@tessinaui/ui";
<Popover size="md" rounded="md">
  <PopoverTrigger render={<Button>Open</Button>} />
  <PopoverContent side="bottom" align="center">
    <PopoverHeader showClose>
      <PopoverTitle>Popover title</PopoverTitle>
      <PopoverDescription>Brief description.</PopoverDescription>
    </PopoverHeader>
    <PopoverBody>
      <p>Any interactive content goes here — forms, lists, buttons…</p>
    </PopoverBody>
    <PopoverFooter>
      <PopoverClose render={<Button variant="secondary">Cancel</Button>} />
      <PopoverClose render={<Button>Confirm</Button>} />
    </PopoverFooter>
    <PopoverArrow />
  </PopoverContent>
</Popover>

Examples

Default

A basic popover with a header, description, body, and arrow.

Sizes

Four sizes — sm, md, lg, and xl — scale the width and text. Open each trigger to compare.

Intents

Semantic border colours — error, warning, success, and info.

Rounded

Corner radius of the popup surface, from none to full.

Curving your own content

Content you put inside — an image or swatch inside the panel — should curve like the chrome around it, or it reads as pasted on. The popover panel publishes its inner radius as a CSS variable:

<img className="rounded-[var(--popover-item-radius)]" />

It tracks whatever the component is actually doing: the explicit step when rounded is set (6px at md), and 0 at rounded="none" — so your content squares off exactly when the container does.

Positioning

Place the popup on any side of the trigger. Each trigger opens its popup on the side it names — near a viewport edge the popup still flips to stay visible.

Confirmation

A compact destructive confirmation with footer actions that close on click.

Scrollable

A popover taller than the viewport clamps to the space the positioner measured; the header — with its pinned search and filter row — and the footer hold their edges, and only the body scrolls.

Filter panel

The filter-panel archetype: section labels, chip filters, a from/to range, and a footer that clears or applies — every control inside is the design system's own.

When to use Popover vs. Tooltip vs. DropdownMenu vs. Modal

Use Popover when…Use Tooltip when…Use DropdownMenu when…Use Modal when…
You need interactive contentYou just need informational text on hoverThe content is a list of menu itemsYou need to block the rest of the page
The trigger is click-activatedThe trigger is hover/focusUsers pick one option then dismissThe decision is critical or blocking
The panel can contain a form, list, or buttonsThe tip is short and non-interactiveKeyboard arrow navigation between itemsContent is large or requires focus

Anatomy

Popover composes several parts. Only Popover, PopoverTrigger, and PopoverContent are required — everything else is optional.

<Popover>
  <PopoverTrigger>…</PopoverTrigger>
  <PopoverContent>
    <PopoverHeader>
      <PopoverTitle />
      <PopoverDescription />
    </PopoverHeader>
    <PopoverBody />
    <PopoverSeparator />
    <PopoverFooter />
    <PopoverArrow />
  </PopoverContent>
</Popover>

Size

SizeMin widthMax widthText
"sm"200px240pxxs
"md" (default)260px320pxsm
"lg"320px400pxsm
"xl"400px480pxbase

Intent

Applies a semantic border colour to the popup and arrow — useful for warning/error confirmations.

ValueBorder
"none" (default)border-border
"error"border-error
"warning"border-warning
"success"border-success
"info"border-info

Rounded

ValueCSS
"none"rounded-none
"sm"rounded-md
"md" (default)rounded-lg
"lg"rounded-xl
"full"rounded-3xl

Positioning

PopoverContent accepts side (top | right | bottom | left), align (start | center | end), sideOffset (px gap from trigger, default 18; with an arrow the component floors it at 21 so the tip cannot park on the trigger), alignOffset (px shift along the side, default 0), and collisionPadding (px gap kept from the viewport edges before the popup flips or shifts, default 8).

On phones the popup additionally clamps itself: width never exceeds 100dvw - 2rem, and height never exceeds the space the positioner measured — a longer PopoverBody scrolls instead of running off-screen.

<PopoverContent side="right" align="start" sideOffset={12}>
  …
</PopoverContent>

Arrow

Opt in per content: pass arrow on PopoverContent for the built-in tail, or compose <PopoverArrow/> yourself to style it (the explicit child wins). The same arrow prop exists on HoverCardContent and DropdownMenuContent.

The <PopoverArrow /> is an SVG speech-bubble tail whose outline continues the popup's own border, inheriting the current intent's colour. Its apex radius and its clearance from the popup's corner both follow the rounded cascade. It's entirely optional — omit it for a chip-style floating panel.

Backdrop & modal behaviour

By default the popover is non-modal — clicks outside dismiss but the page remains interactive. For destructive confirmations or flows that need focus-trapping, pass modal:

<Popover modal>
  …
</Popover>

To dim the background, render <PopoverBackdrop /> inside the popover:

<Popover modal>
  <PopoverTrigger render={<Button>Delete</Button>} />
  <PopoverBackdrop />
  <PopoverContent>…</PopoverContent>
</Popover>

Controlled

Popover accepts open, defaultOpen, and onOpenChange.

const [open, setOpen] = React.useState(false);

<Popover open={open} onOpenChange={setOpen}>
  <PopoverTrigger render={<Button>Open</Button>} />
  <PopoverContent>…</PopoverContent>
</Popover>

Close button in header

PopoverHeader accepts showClose — renders a small X button wired to close the popover. Only use when the popover is long-lived or contains interactive content that makes the dismiss affordance non-obvious.

<PopoverHeader showClose>
  <PopoverTitle>Rename file</PopoverTitle>
</PopoverHeader>

Accessibility

  • The trigger and popup are linked via ARIA — screen readers announce the popup's content when it opens.
  • PopoverTitle maps to aria-labelledby, PopoverDescription to aria-describedby.
  • Focus moves to the first focusable element in the popup on open, and returns to the trigger on close.
  • Escape closes the popover, and so does a click outside it. To keep it open through both, control open yourself and ignore the dismissal in onOpenChange — the callback's reason tells you which one fired.
  • Use intent="error" for destructive confirmations so assistive tech surfaces the semantic colour via the rendered border.

RTL

Pass dir="rtl" to the root. PopoverHeader's close button, PopoverFooter actions, and alignment all flip to follow the reading direction.

<Popover dir="rtl">
  <PopoverTrigger render={<Button>فتح</Button>} />
  <PopoverContent side="bottom" align="start">…</PopoverContent>
</Popover>

API Reference

Popover (root)

PropTypeDefaultDescription
size"sm" | "md" | "lg" | "xl""md"Min/max width + text size
rounded"none" | "sm" | "md" | "lg" | "full""md"Corner radius of popup
intent"none" | "error" | "warning" | "success" | "info""none"Semantic border colour
dir"ltr" | "rtl""ltr"Reading direction
openboolean—Controlled open state
defaultOpenbooleanfalseUncontrolled initial state
onOpenChange(open, event) => void—Fired when open state changes
modalbooleanfalseTrap focus and block page interaction
delaynumber—Deprecated — has never had an effect. Base UI takes a hover delay on the Trigger (with openOnHover), not the root. Pass it there instead.

PopoverContent

PropTypeDefaultDescription
side"top" | "right" | "bottom" | "left""bottom"Which side of the trigger to position on
align"start" | "center" | "end""center"Alignment along the side axis
sideOffsetnumber18Distance in px from the trigger. With arrow, floored at 21 (13px tail + 8px air)
arrowbooleanfalseRender the speech-bubble tail pointing at the trigger
collisionAvoidanceobject—Base UI collision config — pass { side: "none" } to keep a declared side instead of flipping at a viewport edge. Reference-only, same reason as container
alignOffsetnumber0Shift along the side axis
portalbooleantrueRender inside a portal. false keeps the popup inside the component's own subtree, where it scales with a transformed ancestor — there is a Portal toggle in the playground above
containerHTMLElement | nulldocument.bodyPortal target. Reference-only — it exists for embedding a popover in a harness you control, so any example would picture the scaffolding rather than the popover
collisionPaddingnumber8Minimum gap from the viewport edges
positionerClassNamestring—Extra classes on the positioner — raise its z-50 when the popover sits in a higher stacking context
size, rounded, intent—inheritedOverride the root's variant

PopoverHeader

PropTypeDefaultDescription
iconReactNode—Leading icon rendered beside the title
trailingReactNode—Trailing slot on the title row — a badge, count, or status
showClosebooleanfalseRender the built-in X close button

PopoverTrigger, PopoverClose

Render a <button>. Use render={<CustomElement />} to swap in a Button or any element. Standard Base UI render-prop pattern.

Sub-components

PopoverTitle, PopoverDescription, PopoverBody, PopoverSeparator, PopoverFooter, PopoverBackdrop, PopoverArrow — all accept className and any HTML props. PopoverFooter runs the library's footer rhythm (pt-2, stepping up to pt-3 directly after a PopoverSeparator so actions never crowd the hairline). PopoverBody is the scroll region when content exceeds the available height. PopoverSkeleton renders the popup chrome with placeholder bars while content loads (size, rounded, intent, showTitle, lines, showFooter, footerActions).

Notes

  • Built on @base-ui/react/popover — the same headless primitive used by the design system for tooltips and menus.
  • Enter/exit animations use Base UI's data-[starting-style] and data-[ending-style] plus position-aware data-[side=*] attributes, so the popup "lifts off" from whichever side it opens on.
  • The arrow is an SVG path drawn above the popup, so its fill covers the popup's border across the tail's base and its own outline carries on where that border stops — one continuous silhouette rather than a diamond stuck to an edge. It inherits the border colour automatically when intent changes.
Pie ChartProgress

On this page

PlaygroundInstallationUsageExamplesDefaultSizesIntentsRoundedCurving your own contentPositioningConfirmationScrollableFilter panelWhen to use Popover vs. Tooltip vs. DropdownMenu vs. ModalAnatomySizeIntentRoundedPositioningArrowBackdrop & modal behaviourControlledClose button in headerAccessibilityRTLAPI ReferencePopover (root)PopoverContentPopoverHeaderPopoverTrigger, PopoverCloseSub-componentsNotes