import { Popover, PopoverBody, PopoverContent, PopoverHeader, PopoverTitle, PopoverTrigger } from "@registry/components/ui/popover";
import { Button } from "@registry/components/ui/button";
export function PopoverExample() {
return (
<Popover>
<PopoverTrigger render={<Button variant="outline">Open Popover</Button>} />
<PopoverContent>
<PopoverHeader>
<PopoverTitle>Notifications</PopoverTitle>
</PopoverHeader>
<PopoverBody>
<p className="text-sm text-gray-600 dark:text-gray-400">
You are all caught up. Good job!
</p>
</PopoverBody>
</PopoverContent>
</Popover>
);
} Examples
Without close button
Set closeButton={false} on PopoverHeader to hide the default close button.
import { Popover, PopoverBody, PopoverClose, PopoverContent, PopoverHeader, PopoverTitle, PopoverTrigger } from "@registry/components/ui/popover";
import { Button } from "@registry/components/ui/button";
export function PopoverNoCloseButtonExample() {
return (
<Popover>
<PopoverTrigger render={<Button variant="outline">No Close Button</Button>} />
<PopoverContent>
<PopoverHeader closeButton={false}>
<PopoverTitle>Notice</PopoverTitle>
</PopoverHeader>
<PopoverBody>
<p className="text-sm text-gray-600 dark:text-gray-400">
This popover has no default close button in the header.
</p>
<PopoverClose render={<Button size="sm">Dismiss</Button>} />
</PopoverBody>
</PopoverContent>
</Popover>
);
} Custom positioning
Pass positionerProps to PopoverContent to customize the popup position.
import { Popover, PopoverBody, PopoverContent, PopoverHeader, PopoverTitle, PopoverTrigger } from "@registry/components/ui/popover";
import { Button } from "@registry/components/ui/button";
export function PopoverWithPositionerPropsExample() {
return (
<Popover>
<PopoverTrigger render={<Button variant="outline">Top Aligned</Button>} />
<PopoverContent positionerProps={{ side: "top", align: "start" }}>
<PopoverHeader>
<PopoverTitle>Positioned Popover</PopoverTitle>
</PopoverHeader>
<PopoverBody>
<p className="text-sm text-gray-600 dark:text-gray-400">
This popover is positioned above the trigger with start alignment.
</p>
</PopoverBody>
</PopoverContent>
</Popover>
);
} Installation
Copy the source code below into your project:
import { Popover as BasePopover } from "@base-ui/react/popover";
import { cn } from "@registry/lib/utils";
import { XIcon } from "@phosphor-icons/react";
import { Button } from "./button";
const Popover = (props: BasePopover.Root.Props) => (
<BasePopover.Root data-slot="popover" {...props} />
);
function PopoverTrigger(props: BasePopover.Trigger.Props) {
return <BasePopover.Trigger data-slot="popover-trigger" {...props} />;
}
const PopoverPortal = (props: BasePopover.Portal.Props) => (
<BasePopover.Portal data-slot="popover-portal" {...props} />
);
const PopoverClose = (props: BasePopover.Close.Props) => (
<BasePopover.Close data-slot="popover-close" {...props} />
);
function PopoverContent({
className,
children,
positionerProps,
...props
}: BasePopover.Popup.Props & {
positionerProps?: BasePopover.Positioner.Props;
}) {
return (
<BasePopover.Portal>
<BasePopover.Positioner
{...positionerProps}
collisionPadding={positionerProps?.collisionPadding || 8}
sideOffset={positionerProps?.sideOffset || 8}
>
<BasePopover.Popup
className={cn(
"z-50 w-80 rounded-2xl bg-popover shadow-lg outline outline-border",
"origin-(--transform-origin) transition-all duration-150",
"data-starting-style:scale-90 data-starting-style:opacity-0",
"data-ending-style:scale-90 data-ending-style:opacity-0",
className,
)}
data-slot="popover-popup"
{...props}
>
{children}
</BasePopover.Popup>
</BasePopover.Positioner>
</BasePopover.Portal>
);
}
function PopoverTitle({ className, ...props }: BasePopover.Title.Props) {
return (
<BasePopover.Title
className={cn("text-lg font-bold text-foreground", className)}
data-slot="popover-title"
{...props}
/>
);
}
function PopoverHeader({
className,
children,
closeButton = true,
}: {
className?: string;
closeButton?: boolean;
children?: React.ReactNode;
}) {
return (
<div className={cn("relative flex flex-col p-4", className)} data-slot="popover-header">
{closeButton && (
<PopoverClose
className="absolute top-3 right-3"
render={<Button className="size-5" size="icon" variant="secondary" />}
>
<XIcon className="size-3" />
</PopoverClose>
)}
{children}
</div>
);
}
function PopoverBody({ className, children }: { className?: string; children?: React.ReactNode }) {
return (
<div className={cn("flex flex-col gap-3 px-4 pb-4", className)} data-slot="popover-body">
{children}
</div>
);
}
export {
Popover,
PopoverTrigger,
PopoverPortal,
PopoverClose,
PopoverContent,
PopoverTitle,
PopoverHeader,
PopoverBody,
}; API Reference
Popover
This component does not add any props on top of Base UI Popover.Root . See the Base UI docs for the full API reference.
PopoverTrigger
This component does not add any props on top of Base UI Popover.Trigger . See the Base UI docs for the full API reference.
PopoverContent
The PopoverContent component extends the Base UI Popover.Popup
props and adds the following:
| Prop | Type | Default | Description |
|---|---|---|---|
| positionerProps | BasePopover.Positioner.Props | — | Props forwarded to the underlying Positioner component. Includes align, side, sideOffset, alignOffset, collisionPadding, etc. |
PopoverHeader
| Prop | Type | Default | Description |
|---|---|---|---|
| closeButton | boolean | true | Whether to show the default close button in the header. |
| className | string | — | Additional CSS classes to apply to the header container. |
PopoverBody
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | — | Additional CSS classes to apply to the body container. |
PopoverTitle
This component does not add any props on top of Base UI Popover.Title . See the Base UI docs for the full API reference.
PopoverClose
This component does not add any props on top of Base UI Popover.Close . See the Base UI docs for the full API reference.
PopoverPortal
This component does not add any props on top of Base UI Popover.Portal . See the Base UI docs for the full API reference.