Popover
Floating panel with controlled open state and auto-close support.
<Popover side="top" ...>...</Popover>
<Popover side="bottom" ...>...</Popover>
<Popover side="left" ...>...</Popover>
<Popover side="right" ...>...</Popover><Popover
open={open}
onOpenChange={setOpen}
content="Closes in 2s"
autoClose={2000}
>
<IconButton
variant="ghost"
aria-label="Auto close"
onClick={() => setOpen(true)}
>
<InfoIcon />
</IconButton>
</Popover>- This is a pre-styled wrapper around Base UI Popover. Refer to their docs for the full API.
- The trigger wraps
childrenin aspan— pass any interactive element as the child.
import { Popover, Button } from "@/ui";
<Popover content={<p>Popover content</p>}>
<Button>Open</Button>
</Popover>src/ui/Popover.tsx
"use client";
import { useEffect } from "react";
import { Popover as BasePopover } from "@base-ui/react/popover";
type Side = "top" | "bottom" | "left" | "right";
interface PopoverProps {
open: boolean;
onOpenChange: (open: boolean) => void;
content: React.ReactNode;
children: React.ReactElement;
side?: Side;
sideOffset?: number;
autoClose?: number;
}
export function Popover({
open,
onOpenChange,
content,
children,
side = "top",
sideOffset = 8,
autoClose,
}: PopoverProps) {
useEffect(() => {
if (!open || !autoClose) return;
const id = setTimeout(() => onOpenChange(false), autoClose);
return () => clearTimeout(id);
}, [open, autoClose, onOpenChange]);
return (
<BasePopover.Root open={open} onOpenChange={onOpenChange}>
<BasePopover.Trigger
render={<span className="inline-flex" />}
nativeButton={false}
>
{children}
</BasePopover.Trigger>
<BasePopover.Portal>
<BasePopover.Positioner
side={side}
sideOffset={sideOffset}
className="z-60"
>
<BasePopover.Popup className="rounded-md bg-root-contrast px-3 py-1.5 text-xs text-root opacity-0 transition-[opacity,transform] duration-150 data-exiting:opacity-0 data-open:opacity-100 data-[side=bottom]:data-exiting:translate-y-1 data-[side=bottom]:data-open:translate-y-0 data-[side=top]:data-exiting:-translate-y-1 data-[side=top]:data-open:translate-y-0">
{content}
</BasePopover.Popup>
</BasePopover.Positioner>
</BasePopover.Portal>
</BasePopover.Root>
);
}