Popover
A floating panel anchored to a trigger that resizes smoothly with its content and follows the trigger’s direction.
Used in Pro blocks: Agent Todos, Chat Thread, Diff Review, and 2 more
pnpm dlx shadcn@latest add https://hextaui.com/r/popover.jsonAdds the component, the HextaUI theme tokens and any HextaUI components it depends on.
Add the theme tokens to your global CSS, if you haven’t yet.
Install the dependencies.
pnpm add @base-ui/react cnCopy and paste the following code into your project.
components/ui/popover.tsx Update the import paths to match your project setup.
When the content grows or shrinks, the popup animates its height instead of jumping. Continuous changes, like typing, follow the content directly so nothing lags behind.
Pass open and onOpenChange to drive it from your own state. The second argument tells you why it changed, such as trigger-press, outside-press or escape-key.
side and align set the preferred position. When there is no room, the popup flips to the other side and shifts to stay on screen, keeping 8px from the edges.
Set openOnHover on the trigger for preview cards. delay and closeDelay keep it from flickering as the pointer passes over.
Create a handle with createPopoverHandle to share one popover between several triggers anywhere in the tree. Each trigger passes a payload, and the popup renders it through a function child.
Use className="w-auto p-0" to fit content that brings its own padding. The popup follows the calendar as it changes months.
A popover inside another popover or a sheet layers above its parent. Clicks inside the child keep the parent open, and Escape closes only the topmost layer.
Unbroken text wraps inside the popup. When the content is taller than the space available, the popup scrolls inside instead of running off screen.
With modal, page scroll is locked and outside clicks only dismiss the popover. Render a <PopoverClose /> inside so focus can be trapped and touch screen readers have a way out.
A disabled trigger never opens its popover.
The popup picks up the direction of the trigger that opened it, even though it renders in a portal. Logical sides like inline-end flip with it.
| Key | Action |
|---|---|
| EnterSpace | On the trigger, opens or closes the popover. Focus moves into the popup. |
| Tab | Moves through the popup’s content. Tabbing out of a non-modal popover closes it. |
| Esc | Closes the popover and returns focus to the trigger. |
<PopoverTitle />and<PopoverDescription />label and describe the popup for screen readers. Include a title whenever the popup contains more than a sentence.- Focus moves to the first focusable element when it opens and back to the trigger when it closes. Change this with
initialFocusandfinalFocus. - With reduced motion enabled, the popup fades without scaling.
Built on the Base UI popover. Every part accepts the props of the primitive it wraps.
| Prop | Type | Default |
|---|---|---|
defaultOpen | boolean | false |
open | boolean | – |
onOpenChangedetails.reason says what caused the change. | (open: boolean, details) => void | – |
onOpenChangeCompleteCalled after the open or close animation ends. | (open: boolean) => void | – |
modaltrue locks page scroll and outside interaction. trap-focus only traps focus. | boolean | "trap-focus" | false |
handleConnects detached triggers. | PopoverHandle<Payload> | – |
children | ReactNode | ({ payload }) => ReactNode | – |
| Prop | Type | Default |
|---|---|---|
openOnHover | boolean | false |
delayMilliseconds before opening on hover. | number | 300 |
closeDelayMilliseconds before closing after hover ends. | number | 0 |
handle | PopoverHandle<Payload> | – |
payloadPassed to the popup when this trigger opens it. | Payload | – |
disabled | boolean | false |
render | ReactElement | (props, state) => ReactElement | <button> |
| Attribute | Description |
|---|---|
data-slot="popover-trigger" | Target the trigger in CSS. |
data-popup-open | Present while its popover is open. |
data-pressed | Present while the trigger is pressed. |
data-disabled | Present when the trigger is disabled. |
Renders the portal, the positioner and the popup in one part.
| Prop | Type | Default |
|---|---|---|
side | "top" | "right" | "bottom" | "left" | "inline-start" | "inline-end" | "bottom" |
align | "start" | "center" | "end" | "center" |
sideOffsetGap between the trigger and the popup. | number | (data) => number | 6 |
alignOffset | number | (data) => number | 0 |
collisionPaddingSpace kept from the edges of the viewport. | number | Rect | 8 |
collisionAvoidanceWhether to flip, shift or neither when space runs out. | CollisionAvoidance | – |
collisionBoundary | Boundary | – |
anchorPosition against something other than the trigger. | Element | RefObject | VirtualElement | () => Element | – |
sticky | boolean | false |
positionMethod | "absolute" | "fixed" | "absolute" |
initialFocusWhere focus goes when the popover opens. | boolean | RefObject | (type) => HTMLElement | boolean | – |
finalFocusWhere focus goes when the popover closes. | boolean | RefObject | (type) => HTMLElement | boolean | – |
portalPropsProps for the portal, such as container. | PortalProps | – |
classNameThe popup is w-72 by default. | string | (state) => string | – |
render | ReactElement | (props, state) => ReactElement | <div> |
| Attribute | Description |
|---|---|
data-slot="popover-content" | The popup. |
data-slot="popover-positioner" | The element that positions the popup. |
data-open | Present while the popover is open. |
data-starting-style | Present while the popup animates in. |
data-ending-style | Present while the popup animates out. |
data-side | The side the popup ended up on. |
data-align | The alignment the popup ended up with. |
data-instant | Present when the change should not animate. |
--transform-origin | The point the popup scales from, at the trigger. |
--available-width | Space between the trigger and the viewport edge. |
--available-height | Space between the trigger and the viewport edge. The popup’s max height. |
--anchor-width | The trigger’s width. |
--anchor-height | The trigger’s height. |
A plain <div> that stacks the title and description.
| Attribute | Description |
|---|---|
data-slot="popover-header" | Target the header in CSS. |
| Prop | Type | Default |
|---|---|---|
render | ReactElement | (props, state) => ReactElement | <h2> |
| Attribute | Description |
|---|---|
data-slot="popover-title" | Labels the popup. |
| Prop | Type | Default |
|---|---|---|
render | ReactElement | (props, state) => ReactElement | <p> |
| Attribute | Description |
|---|---|
data-slot="popover-description" | Describes the popup. |
| Prop | Type | Default |
|---|---|---|
render | ReactElement | (props, state) => ReactElement | <button> |
| Attribute | Description |
|---|---|
data-slot="popover-close" | Closes the popover when pressed. |
createPopoverHandle<Payload>() returns a handle that connects a <Popover /> to triggers rendered elsewhere. Create it once, outside your component.
- Alert dialogA confirmation dialog for destructive or important actions that waits for async work, and becomes a bottom sheet on phones.
- CommandA searchable list of actions, inline or as a ⌘K palette, with pages, shortcuts and highlighted matches.
- Context menuA menu of actions on right click or long press, with submenus, checkbox and radio items, and hold feedback on touch.
- DialogA window over the page for forms and focused tasks, with a pinned header and footer, nesting, and a swipeable bottom sheet on phones.
- DrawerA panel that slides in from any edge and follows your finger, with snap points, a working handle and nested drawers that stack.
- Dropdown menuA menu of actions and options behind a button, with groups, submenus, checkbox and radio items, and shortcuts.
Used in Pro blocks
Blocks that build on Popover.
- Agent TodosThe plan an agent keeps while it works, one line per step. Handles steps in progress, failed steps and plan changes mid-run.
- Chat ThreadThe conversation view of an AI chat. Handles replies that stream, edit and retry, and versions of a reply you can switch between.
- Diff ReviewA review of an agent’s file edits before they land. Handles accept or reject per change, comments back to the agent and edits still streaming.
- Voice ModeVoice conversations with an AI assistant. Handles listening, interruptions and captions, in a full-screen session or an in-chat voice pill.
- Prompt InputA chat composer that grows as you type. Handles file uploads, @ and / menus at the caret and messages typed while a reply streams.
Every block comes with HextaUI Pro, and Prompt Input is free.