- Accordion
- Alert
- Alert Dialog
- Autocomplete
- Avatar
- Badge
- Breadcrumb
- Button
- Card
- Checkbox
- Checkbox Group
- Collapsible
- Combobox
- CommandNew
- Dialog
- Empty
- Field
- Fieldset
- Form
- Frame
- Group
- Input
- Input Group
- Kbd
- Label
- Menu
- Meter
- Number Field
- Pagination
- Popover
- Preview Card
- Progress
- Radio Group
- Scroll Area
- Select
- Separator
- Sheet
- Skeleton
- Slider
- Spinner
- Switch
- Table
- Tabs
- Textarea
- Toast
- Toggle
- Toggle Group
- Toolbar
- Tooltip
Popover
An accessible popup anchored to a button.
"use client";
import { Button } from "@/components/ui/button";
import { Field } from "@/components/ui/field";
import { Form } from "@/components/ui/form";
import {
Popover,
PopoverDescription,
PopoverPopup,
PopoverTitle,
PopoverTrigger,
} from "@/components/ui/popover";
import { Textarea } from "@/components/ui/textarea";
export default function Particle() {
return (
<Popover>
<PopoverTrigger render={<Button variant="outline" />}>
Open Popover
</PopoverTrigger>
<PopoverPopup className="w-80">
<div className="mb-4">
<PopoverTitle className="text-base">Send us feedback</PopoverTitle>
<PopoverDescription>
Let us know how we can improve.
</PopoverDescription>
</div>
<Form>
<Field>
<Textarea
aria-label="Send feedback"
id="feedback"
placeholder="How can we improve?"
/>
</Field>
<Button type="submit">Send feedback</Button>
</Form>
</PopoverPopup>
</Popover>
);
}
Installation
pnpm dlx shadcn@latest add @coss/popover
Usage
import {
Popover,
PopoverClose,
PopoverDescription,
PopoverPopup,
PopoverTitle,
PopoverTrigger,
} from "@/components/ui/popover"<Popover>
<PopoverTrigger>Open Popover</PopoverTrigger>
<PopoverPopup>
<PopoverTitle>Popover Title</PopoverTitle>
<PopoverDescription>Popover Description</PopoverDescription>
<PopoverClose>Close</PopoverClose>
</PopoverPopup>
</Popover>API Reference
Popover
Root component. Alias for Popover.Root from Base UI.
PopoverTrigger
Trigger button that opens the popover. Alias for Popover.Trigger from Base UI.
PopoverPopup
Popup container that displays the popover content. Also exported as PopoverContent.
| Prop | Type | Default | Description |
|---|---|---|---|
tooltipStyle | boolean | false | When true, applies tooltip-like styling (smaller padding, arrow) |
side | "top" | "bottom" | "left" | "right" | "bottom" | Side of the trigger to position the popup |
align | "start" | "center" | "end" | "center" | Alignment relative to the trigger |
sideOffset | number | 4 | Distance from the trigger in pixels |
alignOffset | number | 0 | Offset along the alignment axis |
PopoverTitle
Title text for the popover. Styled wrapper for Popover.Title from Base UI.
PopoverDescription
Description text for the popover. Styled wrapper for Popover.Description from Base UI.
PopoverClose
Close button for the popover. Alias for Popover.Close from Base UI.
PopoverCreateHandle
Creates a handle for detached popover triggers. Returns a handle object to attach to multiple triggers.
Examples
With Close Button
"use client";
import { XIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Popover,
PopoverClose,
PopoverDescription,
PopoverPopup,
PopoverTitle,
PopoverTrigger,
} from "@/components/ui/popover";
export default function Particle() {
return (
<Popover>
<PopoverTrigger render={<Button variant="outline" />}>
Open Popover
</PopoverTrigger>
<PopoverPopup className="w-80">
<PopoverClose
aria-label="Close"
className="absolute end-2 top-2"
render={<Button size="icon" variant="ghost" />}
>
<XIcon />
</PopoverClose>
<div className="mb-2">
<PopoverTitle className="text-base">Notifications</PopoverTitle>
<PopoverDescription>
You are all caught up. Good job!
</PopoverDescription>
</div>
<PopoverClose render={<Button variant="outline" />}>Close</PopoverClose>
</PopoverPopup>
</Popover>
);
}
Tooltip Style
Use the tooltipStyle prop to make a popover look like a tooltip. This is recommended when you have an info icon button whose only purpose is to show additional information. See the tooltip accessibility guidelines for best practices.
"use client";
import { InfoIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group";
import {
Popover,
PopoverPopup,
PopoverTrigger,
} from "@/components/ui/popover";
export default function Particle() {
return (
<InputGroup>
<InputGroupInput
aria-label="Password"
placeholder="Password"
type="password"
/>
<InputGroupAddon align="inline-end">
<Popover>
<PopoverTrigger
openOnHover
render={
<Button
aria-label="Password requirements"
size="icon-xs"
variant="ghost"
/>
}
>
<InfoIcon />
</PopoverTrigger>
<PopoverPopup side="top" tooltipStyle>
<p>Min. 8 characters</p>
</PopoverPopup>
</Popover>
</InputGroupAddon>
</InputGroup>
);
}
Animated Popovers
You can create animated popovers that smoothly transition between different triggers using detached triggers. This pattern allows multiple triggers to share a single popover popup, with automatic animations for position, size, and content changes.
To create detached triggers:
- Create a handle using
PopoverCreateHandle - Attach the same handle to multiple
PopoverTriggercomponents - Each trigger provides a
payloadprop containing the content component - Use a single
Popovercomponent with the handle to render the popup
"use client";
import { BellIcon, UserIcon } from "lucide-react";
import type { ComponentType } from "react";
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import {
Popover,
PopoverCreateHandle,
PopoverDescription,
PopoverPopup,
PopoverTitle,
PopoverTrigger,
} from "@/components/ui/popover";
const popoverHandle = PopoverCreateHandle<ComponentType>();
const NotificationsContent = () => {
return (
<>
<PopoverTitle className="text-base">Notifications</PopoverTitle>
<PopoverDescription>
You have no new notifications at this time.
</PopoverDescription>
</>
);
};
const ProfileContent = () => {
return (
<div className="w-48">
<div className="flex items-center gap-3">
<Avatar>
<AvatarImage
alt="Mark Andersson"
src="https://images.unsplash.com/photo-1543610892-0b1f7e6d8ac1?w=128&h=128&dpr=2&q=80"
/>
<AvatarFallback>MA</AvatarFallback>
</Avatar>
<div className="min-w-0 flex-1">
<h4 className="line-clamp-1 font-medium text-sm">Mark Andersson</h4>
<div className="flex items-center gap-3 text-muted-foreground text-xs">
Product Designer
</div>
</div>
</div>
<Button className="mt-3 w-full" size="sm" variant="outline">
Log out
</Button>
</div>
);
};
export default function Particle() {
return (
<div className="flex gap-2">
<PopoverTrigger
handle={popoverHandle}
payload={NotificationsContent}
render={
<Button aria-label="Notifications" size="icon" variant="outline" />
}
>
<BellIcon aria-hidden="true" />
</PopoverTrigger>
<PopoverTrigger
handle={popoverHandle}
payload={ProfileContent}
render={<Button aria-label="Profile" size="icon" variant="outline" />}
>
<UserIcon aria-hidden="true" />
</PopoverTrigger>
<Popover handle={popoverHandle}>
{({ payload: Payload }) => (
<PopoverPopup className="min-w-none">
{Payload !== undefined && <Payload />}
</PopoverPopup>
)}
</Popover>
</div>
);
}