Popover
Displays rich content in a portal triggered by a button or any custom element
Import
import {
Popover,
PopoverTrigger,
PopoverContent,
PopoverArrow,
PopoverDialog
} from '@heroui/react';Usage
"use client";
import {Button, Popover, PopoverContent, PopoverDialog, PopoverHeading} from "@heroui/react";
export function PopoverBasic() {
return (
<div className="flex items-center gap-4">
<Popover>
<Button>Click me</Button>
<PopoverContent>
<PopoverDialog>
<PopoverHeading>Popover Title</PopoverHeading>
<p className="text-muted mt-2 text-sm">
This is the popover content. You can put any content here.
</p>
</PopoverDialog>
</PopoverContent>
</Popover>
</div>
);
}Anatomy
Import all parts and piece them together.
import {
Popover,
PopoverTrigger,
PopoverContent,
PopoverArrow,
PopoverDialog
} from '@heroui/react';
export default () => (
<Popover>
<PopoverTrigger/>
<PopoverContent>
<PopoverArrow />
<PopoverDialog>
{/* content goes here */}
</PopoverDialog>
</PopoverContent>
</Popover>
)With Arrow
"use client";
import {
Button,
Popover,
PopoverArrow,
PopoverContent,
PopoverDialog,
PopoverHeading,
} from "@heroui/react";
import {Icon} from "@iconify/react";
export function PopoverWithArrow() {
return (
<div className="flex items-center gap-4">
<Popover>
<Button variant="secondary">With Arrow</Button>
<PopoverContent>
<PopoverDialog>
<PopoverArrow />
<PopoverHeading>Popover with Arrow</PopoverHeading>
<p className="text-muted mt-2 text-sm">
The arrow shows which element triggered the popover.
</p>
</PopoverDialog>
</PopoverContent>
</Popover>
<Popover>
<Button isIconOnly variant="tertiary">
<Icon icon="gravity-ui:ellipsis" />
</Button>
<PopoverContent offset={10}>
<PopoverDialog>
<PopoverArrow />
<PopoverHeading>Popover with Arrow</PopoverHeading>
<p className="text-muted mt-2 text-sm">
The arrow shows which element triggered the popover.
</p>
</PopoverDialog>
</PopoverContent>
</Popover>
</div>
);
}Placement
Click buttons
"use client";
import {Button, Popover, PopoverArrow, PopoverContent, PopoverDialog} from "@heroui/react";
export function PopoverPlacement() {
return (
<div className="grid grid-cols-3 gap-4">
<div />
<Popover>
<Button className="w-full" variant="tertiary">
Top
</Button>
<PopoverContent placement="top">
<PopoverDialog>
<PopoverArrow />
<p className="text-sm">Top placement</p>
</PopoverDialog>
</PopoverContent>
</Popover>
<div />
<Popover>
<Button className="w-full" variant="tertiary">
Left
</Button>
<PopoverContent placement="left">
<PopoverDialog>
<PopoverArrow />
<p className="text-sm">Left placement</p>
</PopoverDialog>
</PopoverContent>
</Popover>
<div className="flex items-center justify-center">
<span className="text-muted text-sm">Click buttons</span>
</div>
<Popover>
<Button className="w-full" variant="tertiary">
Right
</Button>
<PopoverContent placement="right">
<PopoverDialog>
<PopoverArrow />
<p className="text-sm">Right placement</p>
</PopoverDialog>
</PopoverContent>
</Popover>
<div />
<Popover>
<Button className="w-full" variant="tertiary">
Bottom
</Button>
<PopoverContent placement="bottom">
<PopoverDialog>
<PopoverArrow />
<p className="text-sm">Bottom placement</p>
</PopoverDialog>
</PopoverContent>
</Popover>
<div />
</div>
);
}Interactive Content
SJ
Sarah Johnson
@sarahj
"use client";
import {
Avatar,
AvatarFallback,
AvatarImage,
Button,
Popover,
PopoverContent,
PopoverDialog,
PopoverHeading,
PopoverTrigger,
} from "@heroui/react";
import {useState} from "react";
export function PopoverInteractive() {
const [isFollowing, setIsFollowing] = useState(false);
return (
<div className="flex items-center gap-6">
<Popover>
<PopoverTrigger aria-label="User profile">
<div className="flex items-center gap-2">
<Avatar size="sm">
<AvatarImage
alt="Sarah Johnson"
src="https://img.heroui.chat/image/avatar?w=400&h=400&u=3"
/>
<AvatarFallback>SJ</AvatarFallback>
</Avatar>
<div className="flex flex-col">
<p className="text-sm font-medium">Sarah Johnson</p>
<p className="text-muted text-xs">@sarahj</p>
</div>
</div>
</PopoverTrigger>
<PopoverContent className="w-[320px]">
<PopoverDialog>
<PopoverHeading>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<Avatar size="md">
<AvatarImage
alt="Sarah Johnson"
src="https://img.heroui.chat/image/avatar?w=400&h=400&u=3"
/>
<AvatarFallback>SJ</AvatarFallback>
</Avatar>
<div>
<p className="font-semibold">Sarah Johnson</p>
<p className="text-muted text-sm">@sarahj</p>
</div>
</div>
<Button
className="rounded-full"
size="sm"
variant={isFollowing ? "tertiary" : "primary"}
onPress={() => setIsFollowing(!isFollowing)}
>
{isFollowing ? "Following" : "Follow"}
</Button>
</div>
</PopoverHeading>
<p className="text-muted mt-3 text-sm">
Product designer and creative director. Building beautiful experiences that matter.
</p>
<div className="mt-3 flex gap-4">
<div>
<span className="font-semibold">892</span>
<span className="text-muted ml-1 text-sm">Following</span>
</div>
<div>
<span className="font-semibold">12.5K</span>
<span className="text-muted ml-1 text-sm">Followers</span>
</div>
</div>
</PopoverDialog>
</PopoverContent>
</Popover>
</div>
);
}Styling
Passing Tailwind CSS classes
import {
Popover,
PopoverTrigger,
PopoverContent,
PopoverDialog,
Button
} from '@heroui/react';
function CustomPopover() {
return (
<Popover>
<PopoverTrigger>
<Button>Open</Button>
</PopoverTrigger>
<PopoverContent className="bg-accent text-accent-foreground">
<PopoverDialog>
<h3>Custom Styled</h3>
<p>This popover has custom styling</p>
</PopoverDialog>
</PopoverContent>
</Popover>
);
}Customizing the component classes
To customize the Popover component classes, you can use the @layer components directive.
Learn more.
@layer components {
.popover {
@apply rounded-xl shadow-2xl;
}
.popover__dialog {
@apply p-4;
}
.popover__heading {
@apply text-lg font-bold;
}
}HeroUI follows the BEM methodology to ensure component variants and states are reusable and easy to customize.
CSS Classes
The Popover component uses these CSS classes (View source styles):
Base Classes
.popover- Base popover container styles.popover__dialog- Dialog content wrapper.popover__heading- Heading text styles.popover__trigger- Trigger element styles
Interactive States
The component supports animation states:
- Entering:
[data-entering]- Applied during popover appearance - Exiting:
[data-exiting]- Applied during popover disappearance - Placement:
[data-placement="*"]- Applied based on popover position - Focus:
:focus-visibleor[data-focus-visible="true"]
API Reference
Popover Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | - | Trigger and content elements |
isOpen | boolean | - | Controls popover visibility (controlled) |
defaultOpen | boolean | false | Initial open state (uncontrolled) |
onOpenChange | (isOpen: boolean) => void | - | Called when open state changes |
PopoverContent Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | - | Content to display in the popover |
placement | "top" | "bottom" | "left" | "right" (and variants) | "bottom" | Placement of the popover |
offset | number | 8 | Distance from the trigger element |
shouldFlip | boolean | true | Whether popover can change orientation to fit |
className | string | - | Additional CSS classes |
PopoverDialog Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | - | Dialog content |
className | string | - | Additional CSS classes |
PopoverTrigger Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | - | Element that triggers the popover |
className | string | - | Additional CSS classes |
PopoverArrow Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | - | Custom arrow element |
className | string | - | Additional CSS classes |