---
title: Popover
description: Migration guide for Popover from HeroUI v2 to v3
---

<Callout type="info">
  Refer to the [v3 Popover documentation](/docs/react/components/popover) for complete API reference, styling guide, and advanced examples. This guide only focuses on migrating from HeroUI v2.
</Callout>

## Structure Changes

In v2, Popover used separate components:

```tsx
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:

```tsx
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

<Tabs items={["v2", "v3"]}>
  <Tab value="v2">
    ```tsx
    {/* 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>
    ```
  </Tab>
  <Tab value="v3">
    ```tsx
    {/* 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>
    ```
  </Tab>
</Tabs>

### With Heading

<Tabs items={["v2", "v3"]}>
  <Tab value="v2">
    ```tsx
    <PopoverContent>
      <div className="px-1 py-2">
        <div className="text-small font-bold">Title</div>
        <div className="text-tiny">Content</div>
      </div>
    </PopoverContent>
    ```
  </Tab>
  <Tab value="v3">
    ```tsx
    <Popover.Content>
      <Popover.Dialog>
        <Popover.Heading>Title</Popover.Heading>
        <p className="text-muted mt-2 text-sm">Content</p>
      </Popover.Dialog>
    </Popover.Content>
    ```
  </Tab>
</Tabs>

### Controlled

<Tabs items={["v2", "v3"]}>
  <Tab value="v2">
    ```tsx
    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>
    ```
  </Tab>
  <Tab value="v3">
    ```tsx
    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>
    ```
  </Tab>
</Tabs>

### Custom Trigger

<Tabs items={["v2", "v3"]}>
  <Tab value="v2">
    ```tsx
    <PopoverTrigger>
      <CustomButton>Custom</CustomButton>
    </PopoverTrigger>
    ```
  </Tab>
  <Tab value="v3">
    ```tsx
    <Popover.Trigger>
      <CustomButton>Custom</CustomButton>
    </Popover.Trigger>
    ```
  </Tab>
</Tabs>

## 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)
          └── Content
```

## New 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:

```tsx
<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

1. **Component Structure**: Must use compound components (`Popover.Content`, `Popover.Dialog`, etc.)
2. **Trigger**: Can use `Popover.Trigger` or Button directly
3. **Content Wrapper**: Content must be wrapped in `Popover.Dialog`
4. **Arrow**: `showArrow` prop removed - use `Popover.Arrow` component
5. **Heading**: Use `Popover.Heading` component for titles
6. **Props Moved**: `placement`, `offset`, `shouldFlip` moved to `Popover.Content`
7. **Styling Props Removed**: `size`, `color`, `radius`, `shadow` - use Tailwind CSS
8. **Backdrop Removed**: `backdrop` prop removed
9. **Motion Removed**: `motionProps` removed, animations handled differently
10. **ClassNames Removed**: Use `className` props on individual components
