Unified UI

Popover

A floating content panel with arrow, close button, and configurable placement. Built on Radix UI.

Basic

A floating content panel that appears relative to a trigger element. Built on Radix UI with support for arrow indicators, close buttons, and 4-side placement.

Installation

Install the component via the CLI in one command.

npx @work-rjkashyap/unified-ui add popover
pnpm dlx @work-rjkashyap/unified-ui add popover
npx @work-rjkashyap/unified-ui add popover
bunx @work-rjkashyap/unified-ui add popover

If you haven't initialized your project yet, run npx @work-rjkashyap/unified-ui init first. See the CLI docs for details.

Or install the full package

Use this approach if you prefer to install the entire design system as a dependency instead of copying individual components.

npm install @work-rjkashyap/unified-ui
pnpm add @work-rjkashyap/unified-ui
yarn add @work-rjkashyap/unified-ui
bun add @work-rjkashyap/unified-ui

Anatomy

import {
	Popover,
	PopoverTrigger,
	PopoverContent,
	PopoverClose,
	PopoverArrow,
	Button,
	Input,
} from "@work-rjkashyap/unified-ui";

Basic Usage

The Popover wraps Radix UI's Popover primitive with design system tokens. It uses a trigger-based pattern — wrap an interactive element in PopoverTrigger and the floating content in PopoverContent.

<Popover>
	<PopoverTrigger asChild>
		<Button>Open</Button>
	</PopoverTrigger>
	<PopoverContent>
		<p>Popover content goes here.</p>
	</PopoverContent>
</Popover>

Placement

Control which side of the trigger the popover appears on using the side prop on PopoverContent. Fine-tune alignment with the align prop.

<PopoverContent side="top">     {/* Above the trigger */}
<PopoverContent side="right">   {/* To the right */}
<PopoverContent side="bottom">  {/* Below — default */}
<PopoverContent side="left">    {/* To the left */}

{/* Alignment within the side */}
<PopoverContent side="bottom" align="start">  {/* Left-aligned */}
<PopoverContent side="bottom" align="center"> {/* Centered — default */}
<PopoverContent side="bottom" align="end">    {/* Right-aligned */}
SideDescription
topAbove the trigger element
rightTo the right of the trigger
bottomBelow the trigger (default)
leftTo the left of the trigger
AlignDescription
startAlign to the start edge
centerCenter alignment (default)
endAlign to the end edge

With Arrow

Add a PopoverArrow inside PopoverContent to render a small pointing arrow toward the trigger.

Or use the PopoverArrow component directly for more control:

<PopoverContent>
	<PopoverArrow />
	<p>Content with explicit arrow component.</p>
</PopoverContent>

With Close Button

Set showClose on PopoverContent to render a close button in the top-right corner.

Combined: Arrow + Close

<Popover>
	<PopoverTrigger asChild>
		<Button>Settings</Button>
	</PopoverTrigger>
	<PopoverContent showClose arrow>
		<p>Full-featured popover with arrow and close button.</p>
	</PopoverContent>
</Popover>

With Form Content

Popovers are commonly used for inline editing, quick settings, and small forms.

<Popover>
	<PopoverTrigger asChild>
		<Button variant="secondary">Edit Name</Button>
	</PopoverTrigger>
	<PopoverContent showClose>
		<div className="flex flex-col gap-3">
			<div className="space-y-1">
				<h4 className="text-sm font-semibold">Edit display name</h4>
				<p className="text-xs text-muted-foreground">
					This name will be visible to other users.
				</p>
			</div>
			<Input placeholder="Display name" size="sm" />
			<div className="flex justify-end gap-2">
				<PopoverClose asChild>
					<Button variant="ghost" size="sm">
						Cancel
					</Button>
				</PopoverClose>
				<Button variant="primary" size="sm">
					Save
				</Button>
			</div>
		</div>
	</PopoverContent>
</Popover>

Offset and Collision

Use sideOffset to control the distance between the trigger and the popover. The popover automatically flips to avoid viewport collisions (handled by Radix UI).

{
	/* Default offset */
}
<PopoverContent sideOffset={4}>...</PopoverContent>;

{
	/* Larger offset */
}
<PopoverContent sideOffset={12}>...</PopoverContent>;

Controlled

Use open and onOpenChange for programmatic control.

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

<Popover open={open} onOpenChange={setOpen}>
	<PopoverTrigger asChild>
		<Button>Controlled</Button>
	</PopoverTrigger>
	<PopoverContent>
		<p>This popover is controlled externally.</p>
		<Button variant="primary" size="sm" onClick={() => setOpen(false)}>
			Done
		</Button>
	</PopoverContent>
</Popover>;

Color Picker Pattern

A common pattern using a popover for a color picker or swatch selector.

<Popover>
	<PopoverTrigger asChild>
		<button
			className="size-8 rounded-md border border-border"
			style={{ backgroundColor: color }}
			aria-label="Choose color"
		/>
	</PopoverTrigger>
	<PopoverContent side="bottom" align="start">
		<div className="grid grid-cols-5 gap-2">
			{colors.map((c) => (
				<button
					key={c}
					className="size-6 rounded-sm border border-border"
					style={{ backgroundColor: c }}
					onClick={() => setColor(c)}
					aria-label={c}
				/>
			))}
		</div>
	</PopoverContent>
</Popover>

Props

Popover

PropTypeDefaultDescription
openbooleanControlled open state.
defaultOpenbooleanDefault open state (uncontrolled).
onOpenChange(open: boolean) => voidCallback when the open state changes.
modalbooleanfalseWhether the popover acts as a modal (traps focus).

PopoverTrigger

Uses Radix UI's PopoverTrigger API. Use asChild to render the trigger as your own element.

PropTypeDefaultDescription
asChildbooleanfalseMerge props onto the child element instead of rendering a <button>.

PopoverContent

PropTypeDefaultDescription
side"top" | "right" | "bottom" | "left""bottom"Which side of the trigger to place the popover.
align"start" | "center" | "end""center"Alignment along the side.
sideOffsetnumber4Distance in pixels from the trigger.
showClosebooleanfalseWhether to render a close button.
arrowbooleanfalseWhether to render a pointing arrow.
classNamestringAdditional CSS classes.

PopoverClose

Uses Radix UI's PopoverClose API. Use asChild to render as your own element.

PropTypeDefaultDescription
asChildbooleanfalseMerge props onto the child element.

PopoverArrow

PropTypeDefaultDescription
classNamestringAdditional CSS classes for the arrow SVG.

Accessibility

Built on @radix-ui/react-popover which provides full ARIA compliance and keyboard management.

  • Focus management — Focus moves into the popover when opened and returns to the trigger when closed.
  • Escape to close — Pressing Escape closes the popover.
  • Click outside — Clicking outside the popover dismisses it (configurable via Radix).
  • ARIA attributes — The trigger has aria-expanded and aria-haspopup. Content has role="dialog".
  • Close button has an accessible aria-label.
  • Focus ring is visible on keyboard navigation only (focus-visible).
  • Uses z-popover z-index token to layer correctly above page content.
  • Collision detection automatically repositions the popover to stay within the viewport.

Design Tokens

TokenUsage
--surface-raisedPopover panel background
--borderPopover border color
--shadow-mdPopover elevation shadow
--radius-mdPopover border radius
--z-popoverZ-index for the popover layer
--duration-fastEnter/exit animation timing

On this page