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:

PropTypeDefaultDescription
positionerPropsBasePopover.Positioner.PropsProps forwarded to the underlying Positioner component. Includes align, side, sideOffset, alignOffset, collisionPadding, etc.

PopoverHeader

PropTypeDefaultDescription
closeButtonbooleantrueWhether to show the default close button in the header.
classNamestringAdditional CSS classes to apply to the header container.

PopoverBody

PropTypeDefaultDescription
classNamestringAdditional 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.