A popover is a floating container that displays rich content anchored to a trigger element. It’s useful for displaying additional information, menus, or interactive controls without cluttering the main interface.
Installation
npx shadcn@latest add @eo-n/popover
Install dependencies
npm install @base-ui/react
Copy component code
Copy and paste the following code into components/ui/popover.tsx:"use client";
import * as React from "react";
import { Popover as PopoverPrimitive } from "@base-ui/react";
import { cn } from "@/lib/utils";
const PopoverCreateHandle = PopoverPrimitive.createHandle;
const Popover = PopoverPrimitive.Root;
function PopoverTrigger({
...props
}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;
}
function PopoverBackdrop({
...props
}: React.ComponentProps<typeof PopoverPrimitive.Backdrop>) {
return <PopoverPrimitive.Backdrop data-slot="popover-backdrop" {...props} />;
}
function PopoverPortal({
...props
}: React.ComponentProps<typeof PopoverPrimitive.Portal>) {
return <PopoverPrimitive.Portal data-slot="popover-portal" {...props} />;
}
interface PopoverContentProps
extends Omit<
React.ComponentProps<typeof PopoverPrimitive.Positioner>,
"render"
> {}
function PopoverContent({
className,
sideOffset = 4,
children,
...props
}: PopoverContentProps) {
return (
<PopoverPortal>
<PopoverBackdrop />
<PopoverPrimitive.Positioner
data-slot="popover-positioner"
sideOffset={sideOffset}
className="z-50 transition-[top,left,right,bottom,transform] duration-150 ease-out"
{...props}
>
<PopoverPrimitive.Popup
data-slot="popover-content"
className={cn(
"group bg-popover text-popover-foreground relative overflow-x-hidden overflow-y-auto overscroll-contain rounded-md border p-4 shadow-md transition-[width,height,opacity,scale] duration-150 ease-out",
"origin-[var(--transform-origin)] data-[ending-style]:scale-95 data-[ending-style]:opacity-0 data-[starting-style]:scale-95 data-[starting-style]:opacity-0",
className
)}
>
{children}
</PopoverPrimitive.Popup>
</PopoverPrimitive.Positioner>
</PopoverPortal>
);
}
function PopoverHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="popover-header"
className={cn("flex flex-col gap-2", className)}
{...props}
/>
);
}
function PopoverTitle({
className,
...props
}: React.ComponentProps<typeof PopoverPrimitive.Title>) {
return (
<PopoverPrimitive.Title
data-slot="popover-title"
className={cn("text-base leading-none font-medium", className)}
{...props}
/>
);
}
function PopoverDescription({
className,
...props
}: React.ComponentProps<typeof PopoverPrimitive.Description>) {
return (
<PopoverPrimitive.Description
data-slot="popover-description"
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
);
}
function PopoverClose({
...props
}: React.ComponentProps<typeof PopoverPrimitive.Close>) {
return <PopoverPrimitive.Close data-slot="popover-close" {...props} />;
}
export {
PopoverCreateHandle,
Popover,
PopoverTrigger,
PopoverBackdrop,
PopoverPortal,
PopoverContent,
PopoverHeader,
PopoverTitle,
PopoverDescription,
PopoverClose,
};
Update imports
Update the import paths to match your project setup.
Import all parts and piece them together:
import {
Popover,
PopoverContent,
PopoverDescription,
PopoverTitle,
PopoverTrigger,
} from "@/components/ui/popover";
<Popover>
<PopoverTrigger>Open</PopoverTrigger>
<PopoverContent>
<PopoverTitle>Popover Title</PopoverTitle>
<PopoverDescription>
This is some content inside the popover.
</PopoverDescription>
</PopoverContent>
</Popover>
Examples
Basic Popover
import {
Popover,
PopoverContent,
PopoverDescription,
PopoverTitle,
PopoverTrigger,
} from "@/components/ui/popover";
import { Button } from "@/components/ui/button";
export default function PopoverDemo() {
return (
<Popover>
<PopoverTrigger asChild>
<Button>Open Popover</Button>
</PopoverTrigger>
<PopoverContent>
<PopoverTitle>Popover Title</PopoverTitle>
<PopoverDescription>
This is some content inside the popover.
</PopoverDescription>
</PopoverContent>
</Popover>
);
}
With Form
Use a popover to contain a form:
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
export default function PopoverForm() {
return (
<Popover>
<PopoverTrigger asChild>
<Button>Settings</Button>
</PopoverTrigger>
<PopoverContent className="w-80">
<div className="grid gap-4">
<div className="space-y-2">
<h4 className="font-medium leading-none">Dimensions</h4>
<p className="text-sm text-muted-foreground">
Set the dimensions for the layer.
</p>
</div>
<div className="grid gap-2">
<div className="grid grid-cols-3 items-center gap-4">
<Label htmlFor="width">Width</Label>
<Input
id="width"
defaultValue="100%"
className="col-span-2 h-8"
/>
</div>
<div className="grid grid-cols-3 items-center gap-4">
<Label htmlFor="maxWidth">Max. width</Label>
<Input
id="maxWidth"
defaultValue="300px"
className="col-span-2 h-8"
/>
</div>
<div className="grid grid-cols-3 items-center gap-4">
<Label htmlFor="height">Height</Label>
<Input
id="height"
defaultValue="25px"
className="col-span-2 h-8"
/>
</div>
</div>
</div>
</PopoverContent>
</Popover>
);
}
Open on Hover
Trigger the popover on hover instead of click:
import {
Popover,
PopoverContent,
PopoverDescription,
PopoverTitle,
PopoverTrigger,
} from "@/components/ui/popover";
import { Button } from "@/components/ui/button";
export default function PopoverHover() {
return (
<Popover openOnHover>
<PopoverTrigger asChild>
<Button variant="outline">Hover me</Button>
</PopoverTrigger>
<PopoverContent>
<PopoverTitle>Hover Popover</PopoverTitle>
<PopoverDescription>
This popover appears when you hover over the trigger.
</PopoverDescription>
</PopoverContent>
</Popover>
);
}
Controlled Popover
Control the popover’s open state:
import * as React from "react";
import {
Popover,
PopoverContent,
PopoverDescription,
PopoverTitle,
PopoverTrigger,
} from "@/components/ui/popover";
import { Button } from "@/components/ui/button";
export default function ControlledPopover() {
const [open, setOpen] = React.useState(false);
return (
<div className="space-y-4">
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button>Open Popover</Button>
</PopoverTrigger>
<PopoverContent>
<PopoverTitle>Controlled Popover</PopoverTitle>
<PopoverDescription>
This popover's state is controlled by React state.
</PopoverDescription>
<Button className="mt-4" onClick={() => setOpen(false)}>
Close
</Button>
</PopoverContent>
</Popover>
<p className="text-sm text-muted-foreground">
Popover is {open ? "open" : "closed"}
</p>
</div>
);
}
Different Sides
Position the popover on different sides:
import {
Popover,
PopoverContent,
PopoverDescription,
PopoverTitle,
PopoverTrigger,
} from "@/components/ui/popover";
import { Button } from "@/components/ui/button";
export default function PopoverSides() {
return (
<div className="flex gap-4">
<Popover>
<PopoverTrigger asChild>
<Button>Top</Button>
</PopoverTrigger>
<PopoverContent side="top">
<PopoverTitle>Top Popover</PopoverTitle>
<PopoverDescription>Positioned on top</PopoverDescription>
</PopoverContent>
</Popover>
<Popover>
<PopoverTrigger asChild>
<Button>Bottom</Button>
</PopoverTrigger>
<PopoverContent side="bottom">
<PopoverTitle>Bottom Popover</PopoverTitle>
<PopoverDescription>Positioned on bottom</PopoverDescription>
</PopoverContent>
</Popover>
<Popover>
<PopoverTrigger asChild>
<Button>Left</Button>
</PopoverTrigger>
<PopoverContent side="left">
<PopoverTitle>Left Popover</PopoverTitle>
<PopoverDescription>Positioned on left</PopoverDescription>
</PopoverContent>
</Popover>
<Popover>
<PopoverTrigger asChild>
<Button>Right</Button>
</PopoverTrigger>
<PopoverContent side="right">
<PopoverTitle>Right Popover</PopoverTitle>
<PopoverDescription>Positioned on right</PopoverDescription>
</PopoverContent>
</Popover>
</div>
);
}
API Reference
Popover
The root component that manages the popover state.
Controls the open state of the popover.
The initial open state for uncontrolled usage.
Callback fired when the open state changes.
Opens the popover when hovering over the trigger.
PopoverTrigger
The button that toggles the popover.
Merge props onto the child element instead of wrapping it.
PopoverContent
The content to be rendered in the popover.
side
'top' | 'right' | 'bottom' | 'left'
default:"'bottom'"
The preferred side of the trigger to render against when open.
The distance in pixels from the trigger.
align
'start' | 'center' | 'end'
default:"'center'"
The preferred alignment against the trigger.
An offset in pixels from the alignment.
Additional CSS classes to apply.
Wrapper for the popover title and description.
Additional CSS classes to apply.
PopoverTitle
The accessible title of the popover.
Additional CSS classes to apply.
PopoverDescription
The accessible description of the popover.
Additional CSS classes to apply.
PopoverClose
A button that closes the popover.
Merge props onto the child element instead of wrapping it.
PopoverCreateHandle
Creates a handle for imperative popover control.
const handleRef = PopoverCreateHandle();
// Later, use the handle to control the popover
handleRef.current?.open();
handleRef.current?.close();