ComponentsPopover
Popover
Click-toggled floating panel anchored to a trigger button. Compound sub-components: Popover + Popover.Trigger + Popover.Content. Click outside to dismiss.
Design Language Comparison
Neobrutalism
Shadcn-inspired
Flowbite-inspired
Glassmorphism
Material Design 3
Neumorphism
Neobrutalism
White panel, thick black border, flat offset shadow, yellow trigger hover.
const NeobrutalismPopover = ({ children }) => {
const [open, setOpen] = useState(false);
const ref = useRef(null);
useEffect(() => {
const handleClick = (e) => {
if (ref.current && !ref.current.contains(e.target)) setOpen(false);
};
document.addEventListener("mousedown", handleClick);
return () => document.removeEventListener("mousedown", handleClick);
}, []);
return (
<PopoverContext.Provider value={{ open, setOpen }}>
<div ref={ref} className="relative inline-block">{children}</div>
</PopoverContext.Provider>
);
};
NeobrutalismPopover.Trigger = ({ children }) => {
const { open, setOpen } = usePopover();
return (
<button
type="button"
onClick={() => setOpen(!open)}
aria-expanded={open}
className="inline-flex items-center px-4 py-2 border-[3px] border-black shadow-[4px_4px_0_#000] rounded-[2px] font-extrabold bg-white hover:bg-yellow-400 transition-colors cursor-pointer font-sans text-sm"
>
{children}
</button>
);
};
NeobrutalismPopover.Content = ({ children }) => {
const { open } = usePopover();
if (!open) return null;
return (
<div className="absolute z-50 top-full left-0 mt-2 min-w-[200px] bg-white border-[3px] border-black shadow-[4px_4px_0_#000] rounded-[2px] p-4 font-sans">
{children}
</div>
);
};
// Usage:
// <NeobrutalismPopover>
// <NeobrutalismPopover.Trigger>Open</NeobrutalismPopover.Trigger>
// <NeobrutalismPopover.Content>
// <p className="text-sm font-bold">Popover content here</p>
// </NeobrutalismPopover.Content>
// </NeobrutalismPopover>Shadcn-inspired
White panel, zinc border, shadow-md, 6 px radius.
const ShadcnPopover = ({ children }) => {
const [open, setOpen] = useState(false);
const ref = useRef(null);
useEffect(() => {
const handleClick = (e) => {
if (ref.current && !ref.current.contains(e.target)) setOpen(false);
};
document.addEventListener("mousedown", handleClick);
return () => document.removeEventListener("mousedown", handleClick);
}, []);
return (
<PopoverContext.Provider value={{ open, setOpen }}>
<div ref={ref} className="relative inline-block">{children}</div>
</PopoverContext.Provider>
);
};
ShadcnPopover.Trigger = ({ children }) => {
const { open, setOpen } = usePopover();
return (
<button
type="button"
onClick={() => setOpen(!open)}
aria-expanded={open}
className="inline-flex items-center px-3 py-2 border border-zinc-200 rounded-[6px] shadow-sm font-medium bg-white hover:bg-zinc-50 transition-colors duration-150 cursor-pointer font-sans text-sm text-zinc-900"
>
{children}
</button>
);
};
ShadcnPopover.Content = ({ children }) => {
const { open } = usePopover();
if (!open) return null;
return (
<div className="absolute z-50 top-full left-0 mt-2 min-w-[200px] bg-white border border-zinc-200 shadow-md rounded-[6px] p-4 font-sans">
{children}
</div>
);
};Flowbite-inspired
White panel, gray border, shadow-lg, rounded-lg, blue trigger button.
const FlowbitePopover = ({ children }) => {
const [open, setOpen] = useState(false);
const ref = useRef(null);
useEffect(() => {
const handleClick = (e) => {
if (ref.current && !ref.current.contains(e.target)) setOpen(false);
};
document.addEventListener("mousedown", handleClick);
return () => document.removeEventListener("mousedown", handleClick);
}, []);
return (
<PopoverContext.Provider value={{ open, setOpen }}>
<div ref={ref} className="relative inline-block">{children}</div>
</PopoverContext.Provider>
);
};
FlowbitePopover.Trigger = ({ children }) => {
const { open, setOpen } = usePopover();
return (
<button
type="button"
onClick={() => setOpen(!open)}
aria-expanded={open}
className="inline-flex items-center px-4 py-2 bg-[#1c64f2] hover:bg-[#1a56db] text-white border border-[#1c64f2] rounded-lg font-medium transition-all duration-200 cursor-pointer font-sans text-sm"
>
{children}
</button>
);
};
FlowbitePopover.Content = ({ children }) => {
const { open } = usePopover();
if (!open) return null;
return (
<div className="absolute z-50 top-full left-0 mt-2 min-w-[200px] bg-white border border-gray-200 shadow-lg rounded-lg p-4 font-sans">
{children}
</div>
);
};Glassmorphism
Frosted glass panel with backdrop blur and translucent trigger button.
const GlassmorphismPopover = ({ trigger, content, placement = "bottom" }) => {
const [open, setOpen] = useState(false);
const ref = useRef(null);
useEffect(() => {
const handler = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
document.addEventListener("mousedown", handler);
return () => document.removeEventListener("mousedown", handler);
}, []);
const pos = { bottom: "top-full left-0 mt-2", top: "bottom-full left-0 mb-2", left: "right-full top-0 mr-2", right: "left-full top-0 ml-2" };
return (
<div ref={ref} className="relative inline-block font-sans">
<div onClick={() => setOpen(!open)} className="cursor-pointer">{trigger}</div>
{open && (
<div className={`absolute z-10 min-w-[200px] bg-white/15 backdrop-blur-md border border-white/20 rounded-xl shadow-[0_8px_32px_rgba(31,38,135,0.2)] p-4 ${pos[placement]}`}>
<div className="text-sm text-white/90">{content}</div>
</div>
)}
</div>
);
};Material Design 3
Surface panel with 12 px radius, MD3 soft elevation, tonal trigger.
const Md3Popover = ({ trigger, content, placement = "bottom" }) => {
const [open, setOpen] = useState(false);
const ref = useRef(null);
useEffect(() => {
const handler = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
document.addEventListener("mousedown", handler);
return () => document.removeEventListener("mousedown", handler);
}, []);
const pos = { bottom: "top-full left-0 mt-1", top: "bottom-full left-0 mb-1", left: "right-full top-0 mr-1", right: "left-full top-0 ml-1" };
return (
<div ref={ref} className="relative inline-block font-sans">
<div onClick={() => setOpen(!open)} className="cursor-pointer">{trigger}</div>
{open && (
<div className={`absolute z-10 min-w-[200px] bg-[#fffbfe] border border-[#e7e0ec] rounded-[12px] shadow-[0_2px_6px_rgba(0,0,0,0.15),0_4px_8px_rgba(0,0,0,0.1)] p-4 ${pos[placement]}`}>
<div className="text-sm text-[#1c1b1f]">{content}</div>
</div>
)}
</div>
);
};Neumorphism
Raised panel matching base background with dual-tone outset shadow.
const NmPopover = ({ trigger, content, placement = "bottom" }) => {
const [open, setOpen] = useState(false);
const ref = useRef(null);
useEffect(() => {
const handler = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
document.addEventListener("mousedown", handler);
return () => document.removeEventListener("mousedown", handler);
}, []);
const pos = { bottom: "top-full left-0 mt-2", top: "bottom-full left-0 mb-2", left: "right-full top-0 mr-2", right: "left-full top-0 ml-2" };
return (
<div ref={ref} className="relative inline-block font-sans">
<div onClick={() => setOpen(!open)} className="cursor-pointer">{trigger}</div>
{open && (
<div className={`absolute z-10 min-w-[200px] bg-[#e0e5ec] rounded-xl shadow-[-5px_-5px_10px_#ffffff,_5px_5px_10px_rgba(163,177,198,0.6)] p-4 ${pos[placement]}`}>
<div className="text-sm text-[#6c7a9c]">{content}</div>
</div>
)}
</div>
);
};Last updated on