Skip to content
EMBOSS
Docs menu

Popover

A floating panel anchored to its trigger, with title and description wired for assistive tech.

@emboss/popover

Installation

pnpm dlx shadcn@latest add @emboss/popover

Usage

example.tsx
import { Button } from "@/components/ui/button";
import {
  Popover,
  PopoverContent,
  PopoverDescription,
  PopoverTitle,
  PopoverTrigger,
} from "@/components/ui/popover";

export function Example() {
  return (
    <Popover>
      <PopoverTrigger render={<Button>Calibrate</Button>} />
      <PopoverContent>
        <PopoverTitle>Calibration</PopoverTitle>
        <PopoverDescription>Adjust the reference level.</PopoverDescription>
      </PopoverContent>
    </Popover>
  );
}

API reference

Popover / PopoverTrigger / PopoverContent

Root state, anchored trigger, and the floating panel. Title and description parts label the panel.

PropTypeDefaultDescription
open / defaultOpen / onOpenChangeboolean / boolean / (open) => voidControlled and uncontrolled open state.
sideOffsetnumber8Gap between trigger and panel (PopoverContent).

Keyboard

KeysAction
EscCloses the popover and returns focus.

Source

View source — popover.tsx
popover.tsx
"use client";

import { Popover as BasePopover } from "@base-ui/react/popover";
import { cn } from "@/lib/utils";

/**
 * A floating work surface anchored to its trigger, with title and
 * description parts wired to the popup for assistive tech.
 *
 * @example
 * <Popover>
 *   <PopoverTrigger render={<Button>Calibrate</Button>} />
 *   <PopoverContent>
 *     <PopoverTitle>Calibration</PopoverTitle>
 *     <PopoverDescription>Adjust the reference level.</PopoverDescription>
 *   </PopoverContent>
 * </Popover>
 */
function Popover(props: React.ComponentProps<typeof BasePopover.Root>) {
  return <BasePopover.Root {...props} />;
}

function PopoverTrigger(
  props: React.ComponentProps<typeof BasePopover.Trigger>,
) {
  return <BasePopover.Trigger data-slot="popover-trigger" {...props} />;
}

function PopoverContent({
  className,
  sideOffset = 8,
  children,
  ...props
}: React.ComponentProps<typeof BasePopover.Popup> & {
  sideOffset?: number;
}) {
  return (
    <BasePopover.Portal>
      <BasePopover.Positioner sideOffset={sideOffset} className="z-50">
        <BasePopover.Popup
          data-slot="popover-content"
          className={cn(
            "w-72 origin-(--transform-origin) rounded-md bg-surface-2 p-4 text-ink shadow-float-1 transition-[opacity,scale] duration-(--duration-press) ease-(--ease-press) data-ending-style:scale-95 data-ending-style:opacity-0 data-starting-style:scale-95 data-starting-style:opacity-0",
            className,
          )}
          {...props}
        >
          {children}
        </BasePopover.Popup>
      </BasePopover.Positioner>
    </BasePopover.Portal>
  );
}

function PopoverTitle({
  className,
  ...props
}: React.ComponentProps<typeof BasePopover.Title>) {
  return (
    <BasePopover.Title
      data-slot="popover-title"
      className={cn("font-medium text-ink", className)}
      {...props}
    />
  );
}

function PopoverDescription({
  className,
  ...props
}: React.ComponentProps<typeof BasePopover.Description>) {
  return (
    <BasePopover.Description
      data-slot="popover-description"
      className={cn("mt-1 text-sm text-ink-muted", className)}
      {...props}
    />
  );
}

function PopoverClose(props: React.ComponentProps<typeof BasePopover.Close>) {
  return <BasePopover.Close data-slot="popover-close" {...props} />;
}

export {
  Popover,
  PopoverTrigger,
  PopoverContent,
  PopoverTitle,
  PopoverDescription,
  PopoverClose,
};