Popover
Migration guide for Popover from HeroUI v2 to v3
Refer to the v3 Popover documentation for complete API reference, styling guide, and advanced examples. This guide only focuses on migrating from HeroUI v2.
Structure Changes
In v2, Popover used separate components:
import { Popover, PopoverTrigger, PopoverContent, Button } from "@heroui/react";
export default function App() {
return (
<Popover placement="right">
<PopoverTrigger>
<Button>Open</Button>
</PopoverTrigger>
<PopoverContent>
<div>Content</div>
</PopoverContent>
</Popover>
);
}In v3, Popover uses compound components:
import { Popover, Button } from "@heroui/react";
export default function App() {
return (
<Popover>
<Button>Open</Button>
<Popover.Content>
<Popover.Dialog>
<Popover.Heading>Title</Popover.Heading>
<div>Content</div>
</Popover.Dialog>
</Popover.Content>
</Popover>
);
}Key Changes
1. Component Structure
v2: Separate components (Popover, PopoverTrigger, PopoverContent)
v3: Compound components (Popover, Popover.Trigger, Popover.Content, Popover.Dialog, Popover.Heading, Popover.Arrow)
2. Prop Changes
| v2 Prop | v3 Location | Notes |
|---|---|---|
placement | placement (on Content) | Moved to Popover.Content |
offset | offset (on Content) | Moved to Popover.Content |
shouldFlip | shouldFlip (on Content) | Moved to Popover.Content |
isOpen / defaultOpen / onOpenChange | Same (on root) | Controlled state stays on root Popover |
showArrow | — | Use Popover.Arrow component |
size | — | Removed (use Tailwind CSS) |
color | — | Removed (use Tailwind CSS) |
radius | — | Removed (use Tailwind CSS) |
shadow | — | Removed (use Tailwind CSS) |
backdrop | — | Removed |
motionProps | — | Removed (animations handled differently) |
classNames | — | Use className on each part |
onClose | — | Use onOpenChange((open) => { if (!open) { ... } }) instead |
Migration Examples
Content Configuration
{/* With arrow */}
<Popover showArrow>
<PopoverTrigger><Button>Open</Button></PopoverTrigger>
<PopoverContent><div>Content</div></PopoverContent>
</Popover>
{/* With placement */}
<Popover placement="top">
<PopoverTrigger><Button>Open</Button></PopoverTrigger>
<PopoverContent><div>Content</div></PopoverContent>
</Popover>
{/* With offset */}
<Popover offset={10}>
<PopoverTrigger><Button>Open</Button></PopoverTrigger>
<PopoverContent><div>Content</div></PopoverContent>
</Popover>{/* With arrow - use component */}
<Popover>
<Button>Open</Button>
<Popover.Content>
<Popover.Dialog>
<Popover.Arrow />
<div>Content</div>
</Popover.Dialog>
</Popover.Content>
</Popover>
{/* With placement - moved to Content */}
<Popover>
<Button>Open</Button>
<Popover.Content placement="top">
<Popover.Dialog>
<Popover.Arrow />
<div>Content</div>
</Popover.Dialog>
</Popover.Content>
</Popover>
{/* With offset - moved to Content */}
<Popover>
<Button>Open</Button>
<Popover.Content offset={10}>
<Popover.Dialog>
<div>Content</div>
</Popover.Dialog>
</Popover.Content>
</Popover>With Heading
<PopoverContent>
<div className="px-1 py-2">
<div className="text-small font-bold">Title</div>
<div className="text-tiny">Content</div>
</div>
</PopoverContent><Popover.Content>
<Popover.Dialog>
<Popover.Heading>Title</Popover.Heading>
<p className="text-muted mt-2 text-sm">Content</p>
</Popover.Dialog>
</Popover.Content>Controlled
import { useState } from "react";
const [isOpen, setIsOpen] = useState(false);
<Popover isOpen={isOpen} onOpenChange={setIsOpen}>
<PopoverTrigger>
<Button>Open</Button>
</PopoverTrigger>
<PopoverContent>
<div>Content</div>
</PopoverContent>
</Popover>import { useState } from "react";
const [isOpen, setIsOpen] = useState(false);
<Popover isOpen={isOpen} onOpenChange={setIsOpen}>
<Button>Open</Button>
<Popover.Content>
<Popover.Dialog>
<div>Content</div>
</Popover.Dialog>
</Popover.Content>
</Popover>Custom Trigger
<PopoverTrigger>
<CustomButton>Custom</CustomButton>
</PopoverTrigger><Popover.Trigger>
<CustomButton>Custom</CustomButton>
</Popover.Trigger>Component Anatomy
The v3 Popover follows this structure:
Popover (Root)
├── Popover.Trigger (optional, or use Button directly)
└── Popover.Content
└── Popover.Dialog
├── Popover.Arrow (optional)
├── Popover.Heading (optional)
└── ContentNew Capabilities in v3
Popover.Arrow Component
In v2, the arrow was controlled by the showArrow boolean prop on the root Popover. In v3, Popover.Arrow is a dedicated component placed inside Popover.Content, giving you full control over its rendering:
<Popover>
<Button>Open</Button>
<Popover.Content>
<Popover.Arrow className="custom-arrow" />
<Popover.Dialog>
<div>Content</div>
</Popover.Dialog>
</Popover.Content>
</Popover>Popover.Arrow also accepts a render prop for fully custom arrow rendering.
Controlled Open State
Controlled open state remains on the root Popover component, using the same prop names as v2:
| Prop | Type | Default | Description |
|---|---|---|---|
isOpen | boolean | - | Controls popover visibility (controlled) |
defaultOpen | boolean | false | Initial open state (uncontrolled) |
onOpenChange | (isOpen: boolean) => void | - | Called when open state changes |
Custom Render Function
Popover.Content and Popover.Arrow both support a render prop that allows you to override the default DOM element with a custom render function for advanced use cases.
Summary
- Component Structure: Must use compound components (
Popover.Content,Popover.Dialog, etc.) - Trigger: Can use
Popover.Triggeror Button directly - Content Wrapper: Content must be wrapped in
Popover.Dialog - Arrow:
showArrowprop removed - usePopover.Arrowcomponent - Heading: Use
Popover.Headingcomponent for titles - Props Moved:
placement,offset,shouldFlipmoved toPopover.Content - Styling Props Removed:
size,color,radius,shadow- use Tailwind CSS - Backdrop Removed:
backdropprop removed - Motion Removed:
motionPropsremoved, animations handled differently - ClassNames Removed: Use
classNameprops on individual components