Select UI framework
Get Started
React Aria
AlertApp Store ButtonAspect RatioBadgeBannerBreadcrumbButton GroupButtonCheckboxCommand MenuCompactButtonDialogDrawerEmptyFancy ButtonFile UploadHintInput OTPInputItemKbdLabelLinkButtonLoaderPopoverProgress BarProgress CircleRadio GroupRatingSelectSeparatorSkeletonSliderSocial ButtonSwitchTagTextareaToggle GroupToggleTooltip
Component popover-basic not found in registry.
Component popover-close-button not found in registry.
Component popover-with-icon not found in registry.
Component popover-no-arrow not found in registry.
Component popover-positions not found in registry.
Installation
Install the required dependencies:
// @Diar Muradi Popover v0.0
"use client"
import * as React from "react"
import { tv, type VariantProps } from "@diarmuradi/ui/lib/index"
import {
Dialog as AriaDialog,
DialogProps as AriaDialogProps,
DialogTrigger as AriaDialogTrigger,
Popover as AriaPopover,
PopoverProps as AriaPopoverProps,
composeRenderProps,
OverlayArrow,
} from "react-aria-components"
// Component identifier names
const POPOVER_CONTENT_NAME = "PopoverContent"
const POPOVER_DIALOG_NAME = "PopoverDialog"
const POPOVER_CLOSE_NAME = "PopoverClose"
export const popoverVariants = tv({
slots: {
content: [
// base
"w-max rounded-2xl bg-background p-5 shadow-xs ring-1 ring-inset ring-border",
"z-50",
// animation
"data-[entering]:animate-in data-[exiting]:animate-out",
"data-[exiting]:fade-out-0 data-[entering]:fade-in-0 data-[exiting]:zoom-out-95 data-[entering]:zoom-in-95",
"data-[placement=bottom]:slide-in-from-top-2 data-[placement=left]:slide-in-from-right-2 data-[placement=right]:slide-in-from-left-2 data-[placement=top]:slide-in-from-bottom-2",
],
arrow: ["group", "size-[11px]"],
close: ["absolute right-4 top-4"],
},
})
type PopoverSharedProps = VariantProps<typeof popoverVariants>
// Direct primitive assignments for components that don't need custom styling
const PopoverRoot = AriaDialogTrigger
// Content component
const PopoverContent = React.forwardRef<
React.ComponentRef<typeof AriaPopover>,
AriaPopoverProps & {
showArrow?: boolean
unstyled?: boolean
}
>(
(
{ children, className, offset = 12, showArrow = true, unstyled, ...rest },
forwardedRef
) => {
const { content, arrow } = popoverVariants()
return (
<AriaPopover
ref={forwardedRef}
offset={offset}
className={composeRenderProps(className, (className) =>
!unstyled ? content({ class: className }) : className || ""
)}
{...rest}
>
{(values) => (
<>
{showArrow && (
<OverlayArrow className={arrow()}>
{/* TODO: Fix the arrow */}
<svg
width={12}
height={12}
viewBox="0 0 12 12"
className="group-placement-left:-rotate-90 fill-background stroke-border group-placement-bottom:rotate-180 group-placement-right:rotate-90 block"
>
<path d="M0 0 L6 6 L12 0" />
</svg>
</OverlayArrow>
)}
{typeof children === "function" ? children(values) : children}
</>
)}
</AriaPopover>
)
}
)
PopoverContent.displayName = POPOVER_CONTENT_NAME
// Dialog component for popover content
const PopoverDialog = React.forwardRef<
React.ComponentRef<typeof AriaDialog>,
AriaDialogProps & {
unstyled?: boolean
}
>(({ className, unstyled, ...rest }, forwardedRef) => {
return (
<AriaDialog
ref={forwardedRef}
className={!unstyled ? "p-4 outline-0" : className}
{...rest}
/>
)
})
PopoverDialog.displayName = POPOVER_DIALOG_NAME
// Close component
const PopoverClose = React.forwardRef<
HTMLButtonElement,
React.ComponentPropsWithoutRef<"button"> & {
unstyled?: boolean
}
>(({ className, unstyled, ...rest }, forwardedRef) => {
const { close } = popoverVariants()
return (
<button
ref={forwardedRef}
className={!unstyled ? close({ class: className }) : className}
{...rest}
/>
)
})
PopoverClose.displayName = POPOVER_CLOSE_NAME
export type { PopoverSharedProps }
export {
PopoverRoot as Root,
PopoverContent as Content,
PopoverDialog as Dialog,
PopoverClose as Close,
}
Usage
import * as Popover from "@/registry/react-aria/ui/popover"
export function PopoverExample() {
return (
<Popover.Root>
<Button.Root>Open Popover</Button.Root>
<Popover.Content>
<Popover.Dialog>
<div className="space-y-2">
<h4 className="font-medium">Popover Title</h4>
<p className="text-muted-foreground text-sm">
This is the popover content.
</p>
</div>
</Popover.Dialog>
</Popover.Content>
</Popover.Root>
)
}Features
- Accessible: Built on React Aria Components with full keyboard and screen reader support
- Positioning: Smart positioning with collision detection and automatic repositioning
- Animations: Smooth enter/exit animations with proper timing
- Customizable: Full control over styling and behavior
- Arrow Support: Optional arrow pointing to the trigger element
- Close Button: Built-in close button support
Variants
Basic Popover
A simple popover with content.
<Popover.Root>
<Button.Root>Open Popover</Button.Root>
<Popover.Content>
<Popover.Dialog>
<div className="space-y-2">
<h4 className="font-medium">Basic Popover</h4>
<p className="text-muted-foreground text-sm">
This is a basic popover example.
</p>
</div>
</Popover.Dialog>
</Popover.Content>
</Popover.Root>Popover with Close Button
A popover with a close button in the top right corner.
<Popover.Root>
<Button.Root>With Close Button</Button.Root>
<Popover.Content>
<Popover.Dialog>
<div className="space-y-2">
<h4 className="font-medium">Popover with Close</h4>
<p className="text-muted-foreground text-sm">
This popover has a close button.
</p>
</div>
<Popover.Close>
<Button.Root variant="secondary" size="sm" className="h-6 w-6 p-0">
×
</Button.Root>
</Popover.Close>
</Popover.Dialog>
</Popover.Content>
</Popover.Root>Popover without Arrow
A popover without the pointing arrow.
<Popover.Root>
<Button.Root>No Arrow</Button.Root>
<Popover.Content showArrow={false}>
<Popover.Dialog>
<div className="space-y-2">
<h4 className="font-medium">No Arrow</h4>
<p className="text-muted-foreground text-sm">
This popover doesn't have an arrow.
</p>
</div>
</Popover.Dialog>
</Popover.Content>
</Popover.Root>Positions
Basic Positions
Popovers can be positioned on different sides of the trigger element.
// Top position
<Popover.Root>
<Button.Root>Top</Button.Root>
<Popover.Content placement="top">
<Popover.Dialog>
<div className="space-y-2">
<h4 className="font-medium">Top Position</h4>
<p className="text-muted-foreground text-sm">
This popover appears above the trigger.
</p>
</div>
</Popover.Dialog>
</Popover.Content>
</Popover.Root>
// Bottom position
<Popover.Root>
<Button.Root>Bottom</Button.Root>
<Popover.Content placement="bottom">
<Popover.Dialog>
<div className="space-y-2">
<h4 className="font-medium">Bottom Position</h4>
<p className="text-muted-foreground text-sm">
This popover appears below the trigger.
</p>
</div>
</Popover.Dialog>
</Popover.Content>
</Popover.Root>
// Left position
<Popover.Root>
<Button.Root>Left</Button.Root>
<Popover.Content placement="left">
<Popover.Dialog>
<div className="space-y-2">
<h4 className="font-medium">Left Position</h4>
<p className="text-muted-foreground text-sm">
This popover appears to the left of the trigger.
</p>
</div>
</Popover.Dialog>
</Popover.Content>
</Popover.Root>
// Right position
<Popover.Root>
<Button.Root>Right</Button.Root>
<Popover.Content placement="right">
<Popover.Dialog>
<div className="space-y-2">
<h4 className="font-medium">Right Position</h4>
<p className="text-muted-foreground text-sm">
This popover appears to the right of the trigger.
</p>
</div>
</Popover.Dialog>
</Popover.Content>
</Popover.Root>Alignment Options
You can also control the alignment of the popover relative to the trigger.
// Start alignment
<Popover.Root>
<Button.Root>Top Start</Button.Root>
<Popover.Content placement="top start">
<Popover.Dialog>
<div className="space-y-2">
<h4 className="font-medium">Top Start</h4>
<p className="text-muted-foreground text-sm">
Aligned to the start of the trigger.
</p>
</div>
</Popover.Dialog>
</Popover.Content>
</Popover.Root>
// End alignment
<Popover.Root>
<Button.Root>Top End</Button.Root>
<Popover.Content placement="top end">
<Popover.Dialog>
<div className="space-y-2">
<h4 className="font-medium">Top End</h4>
<p className="text-muted-foreground text-sm">
Aligned to the end of the trigger.
</p>
</div>
</Popover.Dialog>
</Popover.Content>
</Popover.Root>
// Center alignment (default)
<Popover.Root>
<Button.Root>Top Center</Button.Root>
<Popover.Content placement="top">
<Popover.Dialog>
<div className="space-y-2">
<h4 className="font-medium">Top Center</h4>
<p className="text-muted-foreground text-sm">
Centered on the trigger (default).
</p>
</div>
</Popover.Dialog>
</Popover.Content>
</Popover.Root>Examples
Settings Menu
A popover with a settings menu layout.
<Popover.Root>
<Button.Root>
<Settings className="mr-2 h-4 w-4" />
Settings
</Button.Root>
<Popover.Content>
<Popover.Dialog>
<div className="space-y-3">
<div className="flex items-center gap-2">
<Settings className="text-muted-foreground h-4 w-4" />
<h4 className="font-medium">Settings</h4>
</div>
<div className="space-y-2">
<div className="flex items-center gap-2 text-sm">
<User className="h-3 w-3" />
<span>Profile Settings</span>
</div>
<div className="flex items-center gap-2 text-sm">
<Calendar className="h-3 w-3" />
<span>Calendar Settings</span>
</div>
</div>
</div>
</Popover.Dialog>
</Popover.Content>
</Popover.Root>Form in Popover
A popover containing a form.
<Popover.Root>
<Button.Root>Add Item</Button.Root>
<Popover.Content>
<Popover.Dialog>
<div className="space-y-4">
<h4 className="font-medium">Add New Item</h4>
<div className="space-y-3">
<input
type="text"
placeholder="Item name"
className="border-input bg-background w-full rounded-md border px-3 py-2 text-sm"
/>
<textarea
placeholder="Description"
className="border-input bg-background w-full rounded-md border px-3 py-2 text-sm"
rows={3}
/>
<div className="flex justify-end gap-2">
<Button.Root variant="secondary" size="sm">
Cancel
</Button.Root>
<Button.Root size="sm">Add Item</Button.Root>
</div>
</div>
</div>
</Popover.Dialog>
</Popover.Content>
</Popover.Root>Accessibility
The Popover component is built on React Aria Components and includes:
- Keyboard Navigation: Full keyboard support with Escape to close
- Focus Management: Automatic focus trapping and restoration
- Screen Reader Support: Proper ARIA attributes and announcements
- Portal Rendering: Renders outside the DOM hierarchy to avoid z-index issues
- Collision Detection: Smart positioning to keep content visible