Components
An accessible popover that anchors a floating panel to a trigger, auto-flips and clamps to stay in view, with a pointing arrow and spring-based open/close animation.

"use client";
import { Popover } from "@/components/ui/popover";
import { useRef, useState } from "react";
const people = [
{
id: "tl",
first: "Ada",
name: "Ada Lovelace",
role: "Layout engine",
at: "left-3 top-3",
},
{
id: "tr",
first: "Grace",
name: "Grace Hopper",
role: "Compilers",
at: "right-3 top-3",
},
{
id: "bl",
first: "Alan",
name: "Alan Kay",
role: "Research",
at: "left-3 bottom-3",
},
{
id: "br",
first: "Barbara",
name: "Barbara Liskov",
role: "Platform",
at: "right-3 bottom-3",
},
];
export default function PopoverDemo() {
const field = useRef<HTMLDivElement>(null);
const [open, setOpen] = useState<string | null>(null);
return (
<div className="mx-auto h-[340px] w-full max-w-[440px]">
<div ref={field} className="relative h-full w-full">
{people.map((person) => (
<div key={person.id} className={`absolute ${person.at}`}>
<Popover
label={`${person.name}, profile`}
side="top"
boundary={field}
open={open === person.id}
onOpenChange={(next) => setOpen(next ? person.id : null)}
trigger={person.first}
>
<div className="w-[196px]">
<p className="text-[13px] font-medium text-stone-700 dark:text-stone-100">
{person.name}
</p>
<p className="mt-0.5 text-[11.5px] text-stone-400 dark:text-stone-500">
{person.role} · joined 2019
</p>
<p className="mt-2 text-[12.5px] leading-relaxed text-stone-500 dark:text-stone-400">
Two reviews open. Last shipped the docs shell on Tuesday.
</p>
</div>
</Popover>
</div>
))}
</div>
</div>
);
}
Part of interior.dev — browse the full library on 21st.dev.