Popover
A popover is a small floating panel that opens from a button. It holds a bit more than a tooltip can, such as a short explanation, a few settings or a form.
import { Button } from "@/components/ui/button";
import {
Popover,
PopoverClose,
PopoverContent,
PopoverDescription,
PopoverTitle,
PopoverTrigger,
} from "@/components/ui/popover";
export default function PopoverDemo() {
return (
<Popover>
<PopoverTrigger render={<Button variant="secondary" />}>Notifications</PopoverTrigger>
<PopoverContent>
<PopoverTitle>Notifications</PopoverTitle>
<PopoverDescription>You are all caught up. Good job!</PopoverDescription>
<PopoverClose render={<Button variant="secondary" className="mt-2 self-start" />}>Dismiss</PopoverClose>
</PopoverContent>
</Popover>
);
}The code
The component is a single file, read from the repository when the site is built. Press Show code to see all of it.
import type { ComponentProps } from "react";
import { Popover as BasePopover } from "@base-ui/react/popover";
import { cx } from "@/lib/cx";
import { popupMotion, popupSurface } from "@/lib/styles";
import type { WithClass } from "@/lib/types";
export const Popover = BasePopover.Root;
// Renders a button by default. To open it from your own element, such as a Button, pass it as
// render: <PopoverTrigger render={<Button />}>.
export const PopoverTrigger = BasePopover.Trigger;
// side, sideOffset and align place the popover against its trigger; every other prop goes to the
// popup itself.
export function PopoverContent({
side = "bottom",
sideOffset = 8,
align = "center",
className,
...props
}: WithClass<ComponentProps<typeof BasePopover.Popup>> &
Pick<ComponentProps<typeof BasePopover.Positioner>, "side" | "sideOffset" | "align">) {
return (
<BasePopover.Portal>
<BasePopover.Positioner side={side} sideOffset={sideOffset} align={align} className="z-50">
<BasePopover.Popup className={cx(popupSurface, popupMotion, "flex w-72 flex-col gap-2 p-4", className)} {...props} />
</BasePopover.Positioner>
</BasePopover.Portal>
);
}
export function PopoverTitle({ className, ...props }: WithClass<ComponentProps<typeof BasePopover.Title>>) {
return <BasePopover.Title className={cx("text-base font-semibold text-text", className)} {...props} />;
}
export function PopoverDescription({ className, ...props }: WithClass<ComponentProps<typeof BasePopover.Description>>) {
return <BasePopover.Description className={cx("text-sm text-text/70", className)} {...props} />;
}
export const PopoverClose = BasePopover.Close;Props
| Prop | Type | Default | What it does |
|---|---|---|---|
side | "top" | "right" | "bottom" | "left" | "bottom" | On PopoverContent. Which side of the trigger it opens on. It flips to the other side if there is no room. |
align | "start" | "center" | "end" | "center" | On PopoverContent. How it lines up along that side. |
sideOffset | number | 8 | On PopoverContent. The gap in pixels between the trigger and the popover. |
defaultOpen | boolean | false | Starts the popover open, when it manages its own state. |
open | boolean | — | Whether it is open, when you control it yourself. |
onOpenChange | function | — | Called with the new open state. |
render | element | — | On PopoverTrigger and PopoverClose. Uses your own element, such as a Button, instead of a plain button. |
Every other prop goes to the Base UI Popover underneath. PopoverTitle and PopoverDescription name the panel for screen readers.
The thinking
A popover is for something small that belongs to one control and shouldn't take over the page. It stays out of the way until asked.
- Built on Base UI: Base UI places the panel next to the trigger, flips it when there is no room, and handles the open state, the focus and the dismissal.
- Popover or Dialog: A popover sits beside its trigger and leaves the page usable. A Dialog covers the page and asks for full attention.
- Popover or Tooltip: A tooltip is a label that appears on hover. A popover opens on click and can hold buttons, links and fields.
Interface design
- Surface: The shared overlay surface: the surface fill, a strong hairline, the lg radius of 8px and a soft shadow. No arrow.
- Width: Fixed at 18rem, so every popover on the site feels like the same object.
- Text: A semibold title and a dimmer description, in the same rhythm as a Dialog.
Interaction
- Open: Click the trigger, or press Enter or Space when it has focus.
- Close: Escape, a click outside, or a PopoverClose button.
- Focus: Focus moves into the panel when it opens and returns to the trigger when it closes.
- Motion: A 150ms fade and a small scale from the side it opens from. With reduced motion turned on it appears at once.
Empathy
- State is announced: The trigger reports whether the popover is open.
- Named: The panel is named by its title and described by its description. Always include a PopoverTitle.
- Don't hide required steps: Anything people must do should be on the page, not behind a click.