import { CopyIcon, ShareIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
Popover,
PopoverContent,
PopoverDescription,
PopoverHeader,
PopoverTitle,
PopoverTrigger,
} from "@/components/ui/popover";
import { toast } from "@/components/ui/toast";
export default function PopoverBasic() {
return (
<Popover>
<PopoverTrigger render={<Button variant="secondary" />}>
<ShareIcon />
Share
</PopoverTrigger>
<PopoverContent align="start">
<PopoverHeader>
<PopoverTitle>Share “Mobile app”</PopoverTitle>
<PopoverDescription>Anyone with the link can view this project.</PopoverDescription>
</PopoverHeader>
<div className="flex gap-2">
<Input readOnly defaultValue="https://example.com/p/mobile-app" aria-label="Share link" />
<Button
variant="secondary"
size="icon"
aria-label="Copy link"
onClick={() => toast.add({ title: "Link copied" })}
>
<CopyIcon />
</Button>
</div>
</PopoverContent>
</Popover>
);
}- Add the registryOnce per project
pnpm dlx shadcn@latest registry add @jarvis=https://ui.jarv.is/r/{name}.json - Add popover
pnpm dlx shadcn@latest add @jarvis/popover
Usage
import { Button } from "@/components/ui/button";
import {
Popover,
PopoverContent,
PopoverDescription,
PopoverHeader,
PopoverTitle,
PopoverTrigger,
} from "@/components/ui/popover";
export function DetailsPopover() {
return (
<Popover>
<PopoverTrigger render={<Button variant="secondary" />}>Details</PopoverTrigger>
<PopoverContent>
<PopoverHeader>
<PopoverTitle>Website redesign</PopoverTitle>
<PopoverDescription>18 of 28 tasks done.</PopoverDescription>
</PopoverHeader>
</PopoverContent>
</Popover>
);
}Unlike a Dialog, a popover leaves the rest of the page interactive. side and
align on PopoverContent set its position.
Composition
Examples
Form controls
Set a w-* class on PopoverContent to fit its content.
import { ListFilterIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import { Field, FieldItem, FieldLabel, FieldSeparator } from "@/components/ui/field";
import {
Popover,
PopoverContent,
PopoverHeader,
PopoverTitle,
PopoverTrigger,
} from "@/components/ui/popover";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
const filters = [
{ value: "all", label: "All tasks" },
{ value: "mine", label: "Assigned to me" },
{ value: "unassigned", label: "Unassigned" },
];
export default function PopoverFormControls() {
return (
<Popover>
<PopoverTrigger render={<Button variant="secondary" />}>
<ListFilterIcon />
Filter
</PopoverTrigger>
<PopoverContent align="start" className="w-64">
<PopoverHeader>
<PopoverTitle>Filter tasks</PopoverTitle>
</PopoverHeader>
<RadioGroup defaultValue="all" aria-label="Tasks">
{filters.map(({ value, label }) => (
<FieldItem key={value}>
<RadioGroupItem value={value} />
<FieldLabel>{label}</FieldLabel>
</FieldItem>
))}
</RadioGroup>
<FieldSeparator />
<Field orientation="horizontal">
<Checkbox />
<FieldLabel>Include completed</FieldLabel>
</Field>
</PopoverContent>
</Popover>
);
}Infotip
openOnHover on PopoverTrigger opens the popover on hover as well as click. Unlike a
Tooltip, it works on touch screens and with screen readers, and can hold a link.
Give the icon button an aria-label.
import { InfoIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import {
Popover,
PopoverContent,
PopoverDescription,
PopoverTrigger,
} from "@/components/ui/popover";
export default function PopoverInfotip() {
return (
<Field className="w-64">
<div className="flex items-center gap-1">
<FieldLabel htmlFor="rate-limit">Rate limit</FieldLabel>
<Popover>
<PopoverTrigger
openOnHover
render={
<Button
variant="ghost"
size="icon"
aria-label="About rate limits"
className="size-5 text-muted-foreground"
/>
}
>
<InfoIcon className="size-3.5" />
</PopoverTrigger>
<PopoverContent side="top" className="w-60 p-2.5">
<PopoverDescription className="text-xs">
Requests each API key can make per minute. Requests over the limit get a 429 response.{" "}
<a
href="#rate-limits"
className="font-medium text-foreground underline decoration-border-strong decoration-[0.5px] underline-offset-2"
>
Learn more
</a>
</PopoverDescription>
</PopoverContent>
</Popover>
</div>
<Input id="rate-limit" type="number" defaultValue={600} />
</Field>
);
}Props
| Part | Extends |
|---|---|
| Popover | Base UI's Popover.Root |
| PopoverTrigger | Base UI's Popover.Trigger |
| PopoverContent | Base UI's Popover.Popup |
| PopoverHeader | <div> |
| PopoverTitle | Base UI's Popover.Title |
| PopoverDescription | Base UI's Popover.Description |
The tables leave out className, style and render. Parts built on Base UI take them as its styling and composition guides describe.
Popover
| Prop | Type | Default |
|---|---|---|
defaultOpenWhether the popover is initially open. To render a controlled popover, use the | boolean | false |
openWhether the popover is currently open. | boolean | — |
onOpenChangeEvent handler called when the popover is opened or closed. | ((open: boolean, eventDetails: Popover.Root.ChangeEventDetails) => void) | — |
onOpenChangeCompleteEvent handler called after any animations complete when the popover is opened or closed. | ((open: boolean) => void) | — |
actionsRefA ref to imperative actions.
| React.RefObject<PopoverRootActions | null> | — |
modalDetermines if the popover enters a modal state when open.
On touch devices, a When When | "trap-focus" | boolean | false |
triggerIdID of the trigger that the popover is associated with.
This is useful in conjunction with the | string | null | — |
defaultTriggerIdID of the trigger that the popover is associated with.
This is useful in conjunction with the | string | null | — |
handleA handle to associate the popover with a trigger. If specified, allows external triggers to control the popover's open state. | Popover.Handle<unknown> | — |
childrenThe content of the popover.
This can be a regular React node or a render function that receives the | PayloadChildRenderFunction<unknown> | React.ReactNode | — |
PopoverTrigger
| Prop | Type | Default |
|---|---|---|
nativeButtonWhether the component renders a native | boolean | true |
idID of the trigger. In addition to being forwarded to the rendered element,
it is also used to specify the active trigger for the popover in controlled mode (with the PopoverRoot | string | — |
handleA handle to associate the trigger with a popover. | Popover.Handle<unknown> | — |
payloadA payload to pass to the popover when it is opened. | unknown | — |
openOnHoverWhether the popover should also open when the trigger is hovered. | boolean | false |
delayHow long to wait before the popover may be opened on hover. Specified in milliseconds. Requires the | number | 300 |
closeDelayHow long to wait before closing the popover that was opened on hover. Specified in milliseconds. Requires the | number | 0 |
PopoverContent
| Prop | Type | Default |
|---|---|---|
initialFocusDetermines the element to focus when the popover is opened. By default, focus moves to the first tabbable element inside the popup, except when the popover is opened by touch — then the popup itself is focused to avoid opening the virtual keyboard.
| boolean | React.RefObject<HTMLElement | null> | ((openType: InteractionType) => void | boolean | HTMLElement | null) | — |
finalFocusDetermines the element to focus when the popover is closed.
| boolean | React.RefObject<HTMLElement | null> | ((closeType: InteractionType) => void | boolean | HTMLElement | null) | — |
anchorAn element to position the popup against. By default, the popup will be positioned against the trigger. | Element | React.RefObject<Element | null> | VirtualElement | (() => Element | VirtualElement | null) | null | — |
sideWhich side of the anchor element to align the popup against. May automatically change to avoid collisions. | "bottom" | "inline-end" | "inline-start" | "left" | "right" | "top" | "bottom" |
sideOffsetDistance between the anchor and the popup in pixels. Also accepts a function that returns the distance to read the dimensions of the anchor and positioner elements, along with its side and alignment. The function takes a
| number | OffsetFunction | 4 |
alignHow to align the popup relative to the specified side. | "center" | "end" | "start" | "center" |
alignOffsetAdditional offset along the alignment axis in pixels. Also accepts a function that returns the offset to read the dimensions of the anchor and positioner elements, along with its side and alignment. The function takes a
| number | OffsetFunction | 0 |