Installation
pnpm dlx shadcn@latest add https://rdf.dev.360online.com/r/popover.jsonInstalls to @/components/ui/popover.
Manual installation
Install the dependencies:
pnpm add @base-ui/react@^1.8.0Copy the source into your project:
'use client'
import { Popover as PopoverPrimitive } from '@base-ui/react/popover'
import type * as React from 'react'
import { cn } from '@/lib/utils'
function Popover({ ...props }: PopoverPrimitive.Root.Props) {
return <PopoverPrimitive.Root data-slot="popover" {...props} />
}
function PopoverTrigger({ ...props }: PopoverPrimitive.Trigger.Props) {
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />
}
function PopoverContent({
className,
align = 'center',
alignOffset = 0,
side = 'bottom',
sideOffset = 4,
anchor,
...props
}: PopoverPrimitive.Popup.Props &
Pick<PopoverPrimitive.Positioner.Props, 'align' | 'alignOffset' | 'side' | 'sideOffset' | 'anchor'>) {
return (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Positioner
align={align}
alignOffset={alignOffset}
side={side}
sideOffset={sideOffset}
anchor={anchor}
className="isolate z-50"
>
<PopoverPrimitive.Popup
data-slot="popover-content"
className={cn(
'z-50 flex w-72 origin-(--transform-origin) flex-col gap-2.5 rounded-lg bg-popover p-2.5 text-sm text-popover-foreground shadow-md ring-1 ring-border outline-hidden duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95',
className,
)}
{...props}
/>
</PopoverPrimitive.Positioner>
</PopoverPrimitive.Portal>
)
}
function PopoverHeader({ className, ...props }: React.ComponentProps<'div'>) {
return <div data-slot="popover-header" className={cn('flex flex-col gap-0.5 text-sm', className)} {...props} />
}
function PopoverTitle({ className, ...props }: PopoverPrimitive.Title.Props) {
return <PopoverPrimitive.Title data-slot="popover-title" className={cn('font-medium', className)} {...props} />
}
function PopoverDescription({ className, ...props }: PopoverPrimitive.Description.Props) {
return (
<PopoverPrimitive.Description
data-slot="popover-description"
className={cn('text-muted-foreground', className)}
{...props}
/>
)
}
export { Popover, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger }A popover component built on top of Base UI Popover, providing a floating element that displays additional content when triggered.
Features
- Built on the Base UI Popover primitive
- Custom styling with Tailwind CSS
- Accessible by default
- Multiple positioning options
- Controlled and uncontrolled modes
- Custom trigger support
- Animated transitions
- Header, title and description building blocks (
PopoverHeader,PopoverTitle,PopoverDescription) - Focus management
- Screen reader support
Usage
import {
Popover,
PopoverTrigger,
PopoverContent,
PopoverHeader,
PopoverTitle,
PopoverDescription,
} from '@/components/ui/popover'
import { Button } from '@/components/ui/button'
// Basic usage
export function BasicPopover() {
return (
<Popover>
<PopoverTrigger render={<Button variant="outline" />}>Opening hours</PopoverTrigger>
<PopoverContent className="w-72">
<PopoverHeader>
<PopoverTitle>Citizen Service Centre</PopoverTitle>
<PopoverDescription>City Hall, Kungsgatan 3</PopoverDescription>
</PopoverHeader>
</PopoverContent>
</Popover>
)
}
// Controlled usage
export function ControlledPopover() {
const [open, setOpen] = React.useState(false)
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger render={<Button variant="outline" />}>Add note</PopoverTrigger>
<PopoverContent>
{/* Form fields */}
<Button onClick={() => setOpen(false)}>Save note</Button>
</PopoverContent>
</Popover>
)
}Examples
Default
Opening hours for the citizen service centre, with today in bold.
With Form
A controlled popover for adding an internal note to a case. Cancel and Save close it through state.
With Icon
An icon-only filter button with a checkbox list and a close button.
Contact Card
Contact details for the case officer, opened from an inline link.
Custom Positioning
Popovers with different side values.
Props
Popover
defaultOpen- The open state when initially renderedopen- The controlled open stateonOpenChange- Event handler for open state changesmodal- Whether the popover is modal
PopoverTrigger
render- Element to render instead of the default<button>, e.g.render={<Button variant="outline" />}openOnHover- Also open the popover when the trigger is hoveredchildren- The trigger's content
PopoverContent
align- The alignment of the popover (default'center')alignOffset- Offset along the alignment axis (default0)side- The preferred side of the trigger to render (default'bottom')sideOffset- The distance from the trigger (default4)anchor- Element (or ref) to position against instead of the trigger. ReplacesPopoverAnchor.className- Additional CSS classeschildren- Content to display in the popover
PopoverHeader
- Stacks a
PopoverTitleandPopoverDescription className- Additional CSS classes
PopoverTitle
- Renders an
<h2>and labels the popover (aria-labelledby), so screen readers announce its name render- Render a different heading level, e.g.render={<h3 />}className- Additional CSS classeschildren- Title text
PopoverDescription
- Describes the popover (
aria-describedby) className- Additional CSS classeschildren- Muted description text
PopoverClose and PopoverArrow are no longer exported. Close the popover with controlled open state (as in the With Form and With Icon examples) and style the content without an arrow.
Accessibility
The Popover component follows WAI-ARIA guidelines:
- Uses proper ARIA roles and attributes
- Manages focus trap within popover
- Supports keyboard navigation:
Esc: Close popoverTab: Navigate focusable elementsShift+Tab: Navigate focusable elements (reverse)
- Announces popover state changes
- Provides clear action buttons
- Maintains focus on trigger after closing
Best Practices
-
Content:
- Keep content concise and focused
- Use clear headings
- Group related information
- Consider mobile viewports
-
Positioning:
- Choose appropriate placement
- Handle overflow gracefully
- Consider screen edges
- Maintain visibility
-
Interaction:
- Provide clear triggers
- Handle click outside
- Consider touch devices
- Manage focus properly
-
Accessibility:
- Write descriptive labels
- Use semantic markup
- Test with screen readers
- Verify keyboard navigation
-
Implementation:
- Handle state changes
- Consider animations
- Test edge cases
Related Components
- Tooltip
- Hover Card
- Dialog
- Dropdown Menu
- HoverCard