Skip to main content

Popover

Popover displays rich content in a floating panel triggered by a click.

Import

import { Popover, PopoverHeader, PopoverBody } from "@tosui/react";

Basic Usage

<Popover
content={
<PopoverBody>
This is the popover content.
</PopoverBody>
}
>
<Button>Click me</Button>
</Popover>

With Header

<Popover
content={
<>
<PopoverHeader>Popover Title</PopoverHeader>
<PopoverBody>
This popover has a header and body.
</PopoverBody>
</>
}
>
<Button>With Header</Button>
</Popover>

Placement

<HStack gap={4}>
<Popover placement="top" content={<PopoverBody>Top</PopoverBody>}>
<Button>Top</Button>
</Popover>
<Popover placement="bottom" content={<PopoverBody>Bottom</PopoverBody>}>
<Button>Bottom</Button>
</Popover>
<Popover placement="left" content={<PopoverBody>Left</PopoverBody>}>
<Button>Left</Button>
</Popover>
<Popover placement="right" content={<PopoverBody>Right</PopoverBody>}>
<Button>Right</Button>
</Popover>
</HStack>

Close on Blur

By default, popover closes when clicking outside. Disable with closeOnBlur={false}.

<Popover
closeOnBlur={false}
content={
<PopoverBody>
Click outside - I won't close!
</PopoverBody>
}
>
<Button>Stays Open</Button>
</Popover>

Controlled Mode

Use isOpen, onOpen, and onClose for full control.

function ControlledPopover() {
const [isOpen, setIsOpen] = useState(false);

return (
<Popover
isOpen={isOpen}
onOpen={() => setIsOpen(true)}
onClose={() => setIsOpen(false)}
content={
<PopoverBody>
<Button size="sm" onClick={() => setIsOpen(false)}>
Close
</Button>
</PopoverBody>
}
>
<Button>Controlled</Button>
</Popover>
);
}

Common Patterns

Confirmation Popover

<Popover
content={
<>
<PopoverHeader>Delete item?</PopoverHeader>
<PopoverBody>
<Text mb={3}>This action cannot be undone.</Text>
<HStack gap={2} justify="flex-end">
<Button size="sm" variant="ghost">Cancel</Button>
<Button size="sm" color="error">Delete</Button>
</HStack>
</PopoverBody>
</>
}
>
<Button variant="ghost" color="error">Delete</Button>
</Popover>

User Profile Popover

<Popover
content={
<PopoverBody>
<VStack gap={2} align="flex-start">
<HStack gap={3}>
<Avatar name="John Doe" />
<Box>
<Text fontWeight="medium">John Doe</Text>
<Text fontSize="sm" color="foreground-muted">john@example.com</Text>
</Box>
</HStack>
<Divider />
<Button variant="ghost" size="sm" fullWidth>View Profile</Button>
<Button variant="ghost" size="sm" fullWidth>Settings</Button>
<Button variant="ghost" size="sm" fullWidth color="error">Sign Out</Button>
</VStack>
</PopoverBody>
}
>
<Avatar name="John Doe" />
</Popover>

Form Popover

<Popover
closeOnBlur={false}
content={
<>
<PopoverHeader>Quick Add</PopoverHeader>
<PopoverBody>
<VStack gap={3}>
<Input placeholder="Item name" />
<HStack gap={2} justify="flex-end">
<Button size="sm" variant="ghost">Cancel</Button>
<Button size="sm">Add</Button>
</HStack>
</VStack>
</PopoverBody>
</>
}
>
<Button>Quick Add</Button>
</Popover>

Props Reference

Popover

PropTypeDefaultDescription
contentReactNode-Popover content (required)
placement"top" | "bottom" | "left" | "right""bottom"Popover position
closeOnBlurbooleantrueClose on outside click
isOpenboolean-Controlled open state
onOpen() => void-Callback when popover opens
onClose() => void-Callback when popover closes
childrenReactNode-Trigger element (required)

PopoverHeader, PopoverBody

PropTypeDefaultDescription
classNamestring-Additional CSS class
childrenReactNode-Section content

Accessibility

  • Uses role="dialog" on popover
  • Trigger has aria-expanded and aria-haspopup="dialog"
  • Closes on Escape key
  • Renders via portal to avoid clipping

TypeScript

import { Popover, PopoverHeader, PopoverBody, type PopoverPlacement, type PopoverProps, type PopoverHeaderProps, type PopoverBodyProps } from "@tosui/react";