Skip to content

Popover

A sheet that opens beside the control that opened it. Unlike a tooltip it stays up and can be reached, so what is inside can be clicked and typed into.

tsx
import { Button, Popover } from 'neba';

<Popover trigger={<Button variant="outline">Share</Button>} title="Share this page">
  <TextField size="sm" label="Link" defaultValue="https://…" readOnly />
</Popover>;

Props

PropTypeDefaultDescription
localestringBCP 47 tag naming the × in that language. Unsupported tags fall back to English
triggerReactElementThe element the popup hangs off and that opens it. One element that accepts a ref and spreads props: every Neba component does
sizeshared'xs' | 'sm' | 'md' | 'lg' | 'xl''md'The type scale, the padding, and how wide the popup may get
colorshared'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info''primary'Semantic colour role. The sheet is never dyed, so it reaches the edge and the focus ring
densityshared'default' | 'compact''default'Padding only
titleReactNodeThe heading. Rendered as the element that names the popup
descriptionReactNodeA line under the title, and the popup's accessible description
childrenReactNodeThe body
side'top' | 'right' | 'bottom' | 'left''bottom'Which edge of the trigger it appears on. Flips to the opposite side when there is no room
align'start' | 'center' | 'end''center'Where it sits along that edge
sideOffsetnumber6Distance from the trigger, in pixels
alignOffsetnumber0Shift along that edge, in pixels
arrowbooleanfalseThe little wedge pointing at the trigger. Off by default, unlike Tooltip: this surface is translucent, and a wedge outside the popup cannot carry that backdrop with it
openbooleanWhether it is open. With onOpenChange
defaultOpenbooleanfalseThe initial state of an uncontrolled popover
onOpenChange(open: boolean) => voidCalled when it opens or closes
modalboolean | 'trap-focus'falseWhether the page behind is taken away. Off by default, and that is what separates a popover from a Dialog: it sits beside the page, not instead of it
dismissiblebooleantrueWhether Escape or a click outside closes it. A PopoverClose still gets through when it is off, so it is never a trap
showClosebooleanfalseThe × in the corner
closeLabelstringAccessible name of the × button
widthnumber | stringA hard cap overriding the one size implies. Numbers are pixels

Native <div> attributes pass through to the popup. Only color, title and children are excluded, since the table above spells them differently.

PopoverClose is Base UI's Popover.Close, re-exported. Give it a render prop and any element dismisses the popup it is inside: <PopoverClose render={<Button>Apply</Button>} />.

The shared axes are described in prop conventions.

Examples

side and align

side is which edge of the trigger the popup sits on; align is where it sits along that edge. The side flips automatically when the window has no room. sideOffset sets the gap and alignOffset shifts it along the edge.

A form in a popup

The popup holds focusable content, so a filter panel, a small form or a colour picker all belong here rather than in a Dialog: the page behind stays readable while the form is filled in. width caps the popup when its content should decide the measure.

Controlled

Pass open with onOpenChange and the caller owns the state, so anything else on the page can open or close it. Without them the popover manages itself and defaultOpen sets the starting state.

arrow

arrow draws a wedge pointing at the trigger. It is off by default: this surface is translucent over a blurred backdrop, and a wedge sticking out past the popup's own box cannot carry that backdrop with it. Turn it on where the trigger is far enough away that the popup has to say what it belongs to.

tsx
<Popover arrow trigger={<Button>Details</Button>}>
  Anchored to the button it came from.
</Popover>

Accessibility

  • The popup carries role="dialog". title names it and description describes it, wired with aria-labelledby and aria-describedby; a popover with neither needs an aria-label of its own.
  • Focus moves into the popup when it opens and returns to the trigger when it closes.
  • Escape closes it, and so does a click outside. dismissible={false} cancels both: a PopoverClose still gets through, so it is never a trap.
  • modal is false by default, so the page behind stays scrollable and usable. Use 'trap-focus' for a popup that must be answered before anything else is touched.
  • locale decides the ×'s accessible name; closeLabel writes it out instead.

Released under the MIT License