Skip to content

shadcn.io is an independent project built on shadcn/ui

Components

Free

Popover for Radix UI

Open a note or a few fields beside the trigger while the page stays usable. For a quick edit in React with Radix UI, when nothing should block the page.

The panel sits next to the button that opened it. You can still use the page around it.

Use it for a small edit or a short explanation. A task that should block the page until it is finished is a Dialog. In React, Radix UI moves focus into the panel and returns it on Escape.

Preview width

Installation

Sign in to get your install command
Free with an account. It carries your token, so the CLI installs everything in one go.
Use email insteadNo credit card

Anatomy

Popover anatomy: trigger, title, positioner, arrow, description, content
PopoverArrow
โ””โ”€โ”€ PopoverArrowTip
PopoverAnchor
PopoverTrigger
PopoverIndicator
PopoverPositioner
PopoverContent
PopoverTitle
PopoverDescription
PopoverClose

Usage

import { Button } from "@/components/ui/button";
import { Field, FieldGroup, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import {
  Popover, PopoverContent, PopoverDescription, PopoverHeader,
  PopoverTitle, PopoverTrigger,
} from "@/components/ui/popover";
<Popover>
  <PopoverTrigger asChild>
    <Button variant="outline">Open popover</Button>
  </PopoverTrigger>
  <PopoverContent className="w-64 max-w-full">
    <PopoverHeader>
      <PopoverTitle>Dimensions</PopoverTitle>
      <PopoverDescription>
        Set the dimensions for the layer.
      </PopoverDescription>
    </PopoverHeader>
    <FieldGroup className="min-w-0 gap-3">
      <Field className="min-w-0" orientation="horizontal">
        <FieldLabel className="w-1/2">Width</FieldLabel>
        <Input className="min-w-0" defaultValue="100%" />
      </Field>
      <Field className="min-w-0" orientation="horizontal">
        <FieldLabel className="w-1/2">Height</FieldLabel>
        <Input className="min-w-0" defaultValue="25px" />
      </Field>
    </FieldGroup>
  </PopoverContent>
</Popover>

Accessibility

Space
Opens/closes the popover.
Enter
Opens/closes the popover.
Tab
Moves focus to the next focusable element within the content. Note: If there are no focusable elements, focus is moved to the next focusable element after the trigger.
Shift+Tab
Moves focus to the previous focusable element within the content. Note: If there are no focusable elements, focus is moved to the trigger.
Escape
Closes the popover and moves focus to the trigger.

Demos

Basic

A title and one sentence, with no inputs. The panel lines up with the start of the button rather than its center, so it reads as a note attached to that control. Use this for a definition that should stay open while someone reads it. A Tooltip is the lighter choice when a single line on hover or focus is enough and does not need a dismiss.

Preview width

Alignments

Three triggers place the panel at the start, the center, or the end of the button. Prefer start or end near a corner, and keep one alignment on a toolbar so the panels do not jump. Ark-based stacks use positioning.placement on Popover, Base UI and Radix use align on PopoverContent, and React Aria uses placement with a space, as in bottom start.

Preview width

Form

Width and height live in the panel, under a title that says what you are editing. Focus moves into the fields when it opens, and Escape or a click outside closes it. Use this for a small edit that should stay next to its button. More than a couple of fields, or a page that must wait, belongs in a Dialog.

Preview width

Rtl

The row sets dir to rtl, and each button opens the panel toward a physical side: left, top, bottom, or right. Those edges do not mirror just because the language does. The trigger text is Arabic so it runs with the page. If the panel should swap sides with the writing direction, use a logical placement instead of left or right.

Preview width

FAQ

How do I open the Popover from code in React?

Pass open and onOpenChange on Popover; the callback gets a boolean.

It still needs its trigger, or an anchor, to know where to sit.

How do I align the Radix UI Popover to the start or end of its trigger?

Pass align on PopoverContent: start, center (the default) or end. side and sideOffset move it around the trigger.

The Alignments demo above shows all three.

How do I position the Popover next to another element?

Wrap that element in PopoverAnchor: the trigger still opens the Popover, and the content positions against the anchor.

How do I make the Popover as wide as its trigger?

Give PopoverContent a width from the CSS variable the positioner sets: w-(--radix-popover-trigger-width). It updates as the trigger resizes, which suits pickers and menus under a field.

How do I put a form inside a Popover?

Put the fields in PopoverContent, as in the Form demo: focus moves into the panel when it opens, and Escape or a click outside closes it. To close it after saving, control its open state and set it to closed in your submit handler.

When should I use a Popover instead of a Dialog, a Tooltip or a Hover Card?

Use a Popover for a short form or extra controls next to what opened it, while the rest of the page stays usable. A Dialog blocks the page for a task that needs full attention, a Tooltip gives a one-line hint on hover or focus, and a Hover Card previews content while the pointer rests on a link.

Is the Radix UI Popover accessible?

Yes. The trigger is a button that tells assistive tech whether the panel is open, focus moves into the content and back to the trigger when it closes, and Escape closes it. Add a PopoverTitle so the panel has a name screen readers can announce.

How do I install the Popover in React?

Run the command from the Installation section above: it adds the Popover and the parts it uses to your project's ui folder. The shadcn CLI writes components/ui/popover.tsx, a file you own and can edit.

API Reference

Popover

data-slot="popover"

PropType
open?boolean

Whether it's open. Pair with onOpenChange to control it.

defaultOpen?boolean

Whether it starts open, when you don't control open.

onOpenChange?{ bivarianceHack(open: boolean): void; }["bivarianceHack"]

Called when it opens or closes.

Type
{ bivarianceHack(open: boolean): void; }["bivarianceHack"]
modal?boolean

Whether it's modal: the rest of the page can't be reached and focus stays inside while it's open.

Default
false

PopoverAnchor

<div> data-slot="popover-anchor"

PropType
virtualRef?React.RefObject<Measurable | null>

A virtual element to position against: anything with getBoundingClientRect().

Type
React.RefObject<Measurable | null>
asChild?boolean

Merge props onto the child element instead of rendering a host.

PopoverTrigger

<button> data-slot="popover-trigger"

PropType
asChild?boolean

Merge props onto the child element instead of rendering a host.

PopoverContent

<div> data-slot="popover-content"

PropType
align?(typeof ALIGN_OPTIONS)[number]

How it lines up with the trigger: start, center or end.

Default
"center"
alignOffset?number

How far it shifts from its align position, in px.

arrowPadding?number

The space kept between the arrow and the content's corners, in px.

avoidCollisions?boolean

Whether it flips and shifts to stay in view.

collisionBoundary?Boundary | Boundary[]

The element or elements it stays inside. The viewport when unset.

Type
Boundary | Boundary[]
collisionPadding?number | Partial<Record<Side, number>>

The distance it keeps from the boundary's edges, in px, or one per side.

Type
number | Partial<Record<Side, number>>
deferPointerDownOutside?boolean

When true, a pointerdown event outside of the layered element will wait for the interaction's click event before dispatching, allowing third-party code to stop propagation of later events and cancel dismissal.

forceMount?true

Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries.

hideWhenDetached?boolean

Whether it hides when its trigger is entirely out of view.

onCloseAutoFocus?{ bivarianceHack(event: Event): void; }["bivarianceHack"]

Event handler called when auto-focusing on close. Can be prevented.

Type
{ bivarianceHack(event: Event): void; }["bivarianceHack"]
onEscapeKeyDown?{ bivarianceHack(event: KeyboardEvent): void; }["bivarianceHack"]

Event handler called when the escape key is down. Can be prevented.

Type
{ bivarianceHack(event: KeyboardEvent): void; }["bivarianceHack"]
onFocusOutside?{ bivarianceHack(event: FocusOutsideEvent): void; }["bivarianceHack"]

Event handler called when the focus moves outside of the DismissableLayer. Can be prevented.

Type
{ bivarianceHack(event: FocusOutsideEvent): void; }["bivarianceHack"]
onInteractOutside?{ bivarianceHack(event: PointerDownOutsideEvent | FocusOutsideEvent): void; }["bivarianceHack"]

Event handler called when an interaction happens outside the DismissableLayer. Specifically, when a pointerdown event happens outside or focus moves outside of it. Can be prevented.

Type
{ bivarianceHack(event: PointerDownOutsideEvent | FocusOutsideEvent): void; }["bivarianceHack"]
onOpenAutoFocus?{ bivarianceHack(event: Event): void; }["bivarianceHack"]

Event handler called when auto-focusing on open. Can be prevented.

Type
{ bivarianceHack(event: Event): void; }["bivarianceHack"]
onPointerDownOutside?{ bivarianceHack(event: PointerDownOutsideEvent): void; }["bivarianceHack"]

Event handler called when the a pointerdown event happens outside of the DismissableLayer. Can be prevented.

Type
{ bivarianceHack(event: PointerDownOutsideEvent): void; }["bivarianceHack"]
side?(typeof SIDE_OPTIONS)[number]

The side of the trigger it opens on. It flips when there's no room, unless avoidCollisions is off.

sideOffset?number

The distance from the trigger, in px.

Default
4
sticky?(typeof Sticky)[keyof typeof Sticky]

How it stays in view along its trigger: partial while the trigger is partly visible, always whatever the trigger does.

Type
(typeof Sticky)[keyof typeof Sticky]
updatePositionStrategy?(typeof UpdatePositionStrategy)[keyof typeof UpdatePositionStrategy]

How often it updates its position: optimized, on layout changes, or always, every animation frame.

Type
(typeof UpdatePositionStrategy)[keyof typeof UpdatePositionStrategy]
asChild?boolean

Merge props onto the child element instead of rendering a host.

className?string

Merged with the part's classes.

AttributeType
data-expanded?string

Present when open is true.

data-placementstring

The content's placement on its trigger, such as bottom-start.

data-side"top" | "right" | "bottom" | "left"

The side of the trigger the content sits on.

Type
"top" | "right" | "bottom" | "left"
data-state"open" | "closed"

"open" or "closed".

Type
"open" | "closed"
CSS variableType
--radix-popover-content-transform-originstring

PopoverHeader

<div> data-slot="popover-header"

PropType
className?string

Merged with the part's classes.

PopoverTitle

<div> data-slot="popover-title"

PropType
asChild?boolean

Merge props onto the child element instead of rendering a host.

className?string

Merged with the part's classes.

PopoverDescription

<p> data-slot="popover-description"

PropType
asChild?boolean

Merge props onto the child element instead of rendering a host.

className?string

Merged with the part's classes.