Noctis

Popover

A popover: a trigger opens a floating, elevated panel anchored to it — for transient detail, a small form, or a confirmation. Decoupled from any particular trigger — pass your own, commonly a Button rendered through the trigger.

Basic

A Popover.Trigger opens the Popover.Popup. Compose the trigger from a Button via render so it inherits the button's look, hover, and focus; the popup holds an optional Title and Description over any content. The popup portals and positions itself — no Portal/Positioner wiring needed for the common case.

With actions

Drop a Popover.Close inside — rendered through a Button for a footer action — and it closes the popover and returns focus to the trigger. Several Closes can coexist (a Cancel and a destructive confirm), and focus is managed for you: it moves into the popup on open and back to the trigger on close.

Positioning

The popup opens below the trigger, aligned to its start, by default. Steer it with side and align (and sideOffset / alignOffset) on Popover.Popup — alignment is direction-aware, so start/end flip under RTL. Positioning is collision-aware: the popup flips to the opposite side and shifts to stay on screen, with collisionPadding (default 8) keeping it off the viewport edges. Noctis popovers sit flush against their anchor — there is no arrow/caret part (Base UI offers one; this system doesn't use it).

A form

A small form is the canonical popover — a quick edit anchored to what it changes. Give Popover.Popup an initialFocus ref so focus lands on the first field, not the popup, the moment it opens. A Popover.Close rendered through a submit Button (type="submit" form="…") closes the popover and returns focus to the trigger; on a real submit, keep it open and surface validation in place rather than closing optimistically.

Focus

Popover.Popup forwards Base UI's initialFocus and finalFocus. initialFocus chooses where focus lands on open — a ref (the first field of a form), true for the first tabbable element, or false for the popup itself; finalFocus chooses where it returns on close. With no focusable content, the popup itself receives focus, so a detail-only popover is still announced to screen readers. Leave both unset for the considered default: focus moves into the popup on open and back to the trigger on close.

Overflow

A popover taller than the screen never clips. The popup caps at the space Base UI leaves to the viewport edge (--available-height) and an inner viewport scrolls past it, with the scrollbar flush at the popup edge; keyboard focus inside stays in view as you tab. Short content is unaffected.

Match the trigger width

By default the popup floors at its own min-width and never reads narrower than 12rem. For a combobox-style panel that should be at least as wide as the control it drops from, override the public min-width with the trigger width on Popover.Popup: --noctis-popover-popup-min-width: var(--anchor-width). The max-width still caps the popup at the viewport, so wide content can't overrun.

Open on hover

Popover.Trigger forwards Base UI's openOnHover, delay, and closeDelay for hover-activated context cards — a profile or preview that appears as the pointer rests on a link. Treat hover-open as a pointer enhancement, never the sole affordance: the trigger stays a real button, so it still opens on Enter/Space and on focus, and the content stays reachable for keyboard and touch users.

Reviewed by .

Keyboard

KeyAction
Enter / SpaceOn the trigger: open the popover and move focus into the popup (a keyboard-opened popover appears instantly, without the pointer-open animation).
Tab / Shift+TabMove focus through the popup's focusable elements (direction-aware under RTL). Non-modal: focus then leaves the popup and continues through the page. Modal: focus wraps within the popup.
EscClose the popover and return focus to the trigger.

The popover is non-modal by default: the page stays interactive, Tab eventually moves past the popup into the rest of the page, and clicking outside closes it. Pass modal on Popover.Root for a focus-trapped, scroll-locked popover — focus wraps inside it and the rest of the page is hidden from assistive tech until it closes (Base UI isolates the page directly rather than via aria-modal). Either way, Escape and an outside click dismiss it and focus returns to the trigger.

Advanced

Two layouts reach past the convenience parts; both stay within Base UI, with no new Noctis API:

  • Detached / multi-trigger — anchor a single popup to several triggers, or to an element that isn't the trigger, with Popover.createHandle() (passed as handle to Popover.Root and each Popover.Trigger) and the Popover.Positioner anchor prop. Drive the popup with Popover.Portal + Popover.Positioner directly instead of Popover.Popup so you own the positioning container.
  • Modal backdrop — for a modal popover that should dim the page, add a <Popover.Backdrop> inside the portal and paint it with a sharp overlay scrim. Noctis ships no backdrop by default — a popover is a lightweight overlay — so reach for the Base UI part when a true modal dialog is what you want (and consider Dialog instead).

Anatomy

Compose a popover from its parts. Popover.Root owns the open state (it accepts every Base UI Popover.Root prop — open, defaultOpen, onOpenChange, modal).

  • Popover.Root — owns the open state; renders no element of its own. Non-modal by default; pass modal for a focus-trapped, scroll-locked popover.
  • Popover.Trigger — opens the popover. Style it directly or compose a Button through render.
  • Popover.Popup — the floating, elevated, animated panel. Portals and positions itself; props: side (default bottom), align (default start), sideOffset, alignOffset, collisionPadding.
  • Popover.Portal + Popover.Positioner — the lower-level portal and positioning container, for when you need to wrap the popup yourself.
  • Popover.Title — the popup's accessible name, linked via aria-labelledby (renders an <h2>).
  • Popover.Description — supporting copy under the title, linked via aria-describedby (renders a <p>).
  • Popover.Close — closes the popover; renders a bare button, composing with any Button through render.

The popup is painted by a composed Surface at the menu elevation — the same anchored-overlay tier as Menu, so the two never drift — and its children are wrapped in an internal scrollable region (popover-viewport) that handles overflow. Every rendered part carries a data-slot (popover-trigger, popover-popup, popover-viewport, popover-title, popover-description, popover-close) for host-side styling — pair it with the Base UI state attributes (data-popup-open, data-side, data-starting-style, data-ending-style, data-instant).

Design tokens

Generated from the component's declaration — the same graph that mints the CSS, so a variable name or its resolution default can't drift. The minted tokens are the public override seam: set one on any ancestor and every popover in that region retunes — e.g. .marketing { --noctis-popover-popup-min-width: 18rem; } widens every popup opened beneath it. Knobs that aren't minted are reached through the part's data-slot. See Customization for the full override ladder and Tokens for the whole graph.

Token

API reference

Generated from the component's types — every prop, type, default, and description comes straight from the source. Each part gets its own table; parts that only forward to Base UI list just the props they pass through.

Popover.Root

No props of its own — forwards to the underlying Base UI part.

Popover.Trigger

Prop

Popover.Portal

Prop

Popover.Positioner

Prop

Popover.Popup

Prop

Popover.Title

Prop

Popover.Description

Prop

Popover.Close

Prop

AttributeDescription
data-slotThe trigger element.
data-popup-openPresent on the trigger while its popover is open.
data-sideThe side of the anchor the popup actually rendered on (`bottom`, `top`, `inline-start`, …).
data-starting-stylePresent on the popup for the first frame after mount — the transition's start state.
data-ending-stylePresent on the popup while it transitions out before unmounting.
data-instantPresent on the popup when the change should not animate (keyboard open, dismissal).