Pro--% off in--d : --h : --m : --s
HeroUI

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 Propv3 LocationNotes
placementplacement (on Content)Moved to Popover.Content
offsetoffset (on Content)Moved to Popover.Content
shouldFlipshouldFlip (on Content)Moved to Popover.Content
isOpen / defaultOpen / onOpenChangeSame (on root)Controlled state stays on root Popover
showArrowUse Popover.Arrow component
sizeRemoved (use Tailwind CSS)
colorRemoved (use Tailwind CSS)
radiusRemoved (use Tailwind CSS)
shadowRemoved (use Tailwind CSS)
backdropRemoved
motionPropsRemoved (animations handled differently)
classNamesUse className on each part
onCloseUse 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)
          └── 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:

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

PropTypeDefaultDescription
isOpenboolean-Controls popover visibility (controlled)
defaultOpenbooleanfalseInitial 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

On this page