- Docs
- Components
- Popover
Popover
Displays rich content in a portal, triggered by a button.
Preview
Code
Installation
CLI
Manual
Copy and paste the following code into your project.
export * from "./popover";
export * as Popover from "./namespace";
export * from "./styles";
export {
PopoverRoot as Root,
PopoverContent as Content,
PopoverArrow as Arrow,
} from "./popover";
"use client";
import {
DialogTrigger,
OverlayArrow,
Popover,
PopoverContext,
useSlottedContext,
} from "react-aria-components";
import { PopoverStyles } from "./styles";
export interface PopoverRootProps
extends React.ComponentProps<typeof DialogTrigger> {}
export const PopoverRoot = DialogTrigger;
export interface PopoverContentProps
extends React.ComponentProps<typeof Popover> {}
export function PopoverContent({
className,
placement,
...props
}: PopoverContentProps) {
const popoverContext = useSlottedContext(PopoverContext);
const isMenuTrigger = popoverContext?.trigger === "MenuTrigger";
const isSubmenuTrigger = popoverContext?.trigger === "SubmenuTrigger";
const isSelectTrigger = popoverContext?.trigger === "Select";
const isMenu = isMenuTrigger || isSubmenuTrigger || isSelectTrigger;
const _placement = placement ?? (isSubmenuTrigger ? "right" : "bottom");
return (
<Popover
{...props}
placement={_placement}
className={(values) =>
PopoverStyles.Content({
isMenu,
className:
typeof className === "function" ? className(values) : className,
})
}
/>
);
}
export interface PopoverArrowProps extends React.ComponentProps<"svg"> {}
export function PopoverArrow({ className, ...props }: PopoverArrowProps) {
return (
<OverlayArrow className="group">
<svg
aria-hidden="true"
width={12}
height={12}
viewBox="0 0 12 12"
{...props}
className={PopoverStyles.Arrow({ className })}
>
<path d="M0 0 L6 6 L12 0" />
</svg>
</OverlayArrow>
);
}
import { cva } from "~/lib/cva";
export const PopoverStyles = {
Content: cva({
base: [
"z-50 min-w-(--trigger-width) rounded-md border bg-popover bg-clip-padding p-4 text-popover-fg shadow-xs",
"[scrollbar-width:thin] dark:backdrop-blur-2xl dark:backdrop-saturate-200 [&::-webkit-scrollbar]:size-0.5",
"motion-duration-150",
"entering:motion-opacity-in entering:motion-ease-out",
"entering:placement-left:motion-translate-x-in-[0.25rem]",
"entering:placement-right:motion-translate-x-in-[-0.25rem]",
"entering:placement-top:motion-translate-y-in-[0.25rem]",
"entering:placement-bottom:motion-translate-y-in-[-0.25rem]",
"exiting:motion-opacity-out exiting:motion-ease-in",
"exiting:placement-left:motion-translate-x-out-[0.25rem]",
"exiting:placement-right:motion-translate-x-out-[-0.25rem]",
"exiting:placement-top:motion-translate-y-out-[0.25rem]",
"exiting:placement-bottom:motion-translate-y-out-[-0.25rem]",
],
variants: {
isMenu: {
true: ["overflow-y-auto p-0.5"],
},
},
}),
Arrow: cva({
base: [
"block fill-popover stroke-border",
"group-placement-left:-rotate-90 group-placement-bottom:rotate-180 group-placement-right:rotate-90",
],
}),
};
Update the import paths to match your project setup.
Usage
Single import
import { Button } from "~/components/ui/button";
import { Popover } from "~/components/ui/popover";
<Popover.Root>
<Button>...</Button>
<Popover.Content>
<Popover.Arrow />
...
</Popover.Content>
</Popover.Root>
Multiple imports
import { Button } from "~/components/ui/button";
import {
PopoverArrow,
PopoverContent,
PopoverRoot,
} from "~/components/ui/popover";
<PopoverRoot>
<Button>...</Button>
<PopoverContent>
<PopoverArrow />
...
</PopoverContent>
</PopoverRoot>
Examples
With Arrow
Preview
Code