FluxUI Pro is live - modern UI, powerful animations, zero hassle.
Components
Popover

Popover

Animated popover component with customizable positioning and alignment options.

Installation

1

Install the packages

npm i motion clsx tailwind-merge
2

Add util file

lib/util.ts
import { ClassValue, clsx } from "clsx"; import { twMerge } from "tailwind-merge"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); }
3

Copy and paste the following code into your project

popover.tsx
"use client"; import { cn } from "@/lib/utils"; import { motion, AnimatePresence } from "motion/react"; import React, { useState, useRef, useEffect } from "react"; import { FiChevronDown } from "react-icons/fi"; interface PopoverProps { trigger?: React.ReactNode; content?: React.ReactNode; position?: "top" | "bottom" | "left" | "right"; align?: "start" | "center" | "end"; } const Popover: React.FC<PopoverProps> = ({ trigger, content = "Popover content", position = "bottom", align = "center" }) => { const [isOpen, setIsOpen] = useState(false); const popoverRef = useRef<HTMLDivElement>(null); useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (popoverRef.current && !popoverRef.current.contains(event.target as Node)) { setIsOpen(false); } }; document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, []); const positionClasses = { top: "bottom-full mb-2", bottom: "top-full mt-2", left: "right-full mr-2", right: "left-full ml-2", }; const alignClasses = { start: position.includes("top") || position.includes("bottom") ? "left-0" : "top-0", center: position.includes("top") || position.includes("bottom") ? "left-1/2 transform -translate-x-1/2" : "top-1/2 transform -translate-y-1/2", end: position.includes("top") || position.includes("bottom") ? "right-0" : "bottom-0", }; return ( <div ref={popoverRef} className="relative"> {/* Trigger */} <button onClick={() => setIsOpen(!isOpen)} className="flex items-center gap-2 px-4 py-2 bg-neutral-800 hover:bg-neutral-700 rounded-lg text-white transition-colors" > {trigger || "Click me"} <motion.div animate={{ rotate: isOpen ? 180 : 0 }} transition={{ duration: 0.2 }} > <FiChevronDown className="h-4 w-4" /> </motion.div> </button> {/* Popover Content */} <AnimatePresence> {isOpen && ( <motion.div className={cn( "absolute z-50 w-64 p-4", "bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg shadow-lg", positionClasses[position], alignClasses[align] )} initial={{ opacity: 0, scale: 0.95, y: position === "top" ? 10 : position === "bottom" ? -10 : 0, x: position === "left" ? 10 : position === "right" ? -10 : 0 }} animate={{ opacity: 1, scale: 1, y: 0, x: 0 }} exit={{ opacity: 0, scale: 0.95, y: position === "top" ? 10 : position === "bottom" ? -10 : 0, x: position === "left" ? 10 : position === "right" ? -10 : 0 }} transition={{ duration: 0.2, ease: "easeOut" }} > {content} </motion.div> )} </AnimatePresence> </div> ); }; export default Popover;
4

Update the import paths to match your project setup

Props

PropTypeDefaultDescription
triggerReactNodeundefinedElement that triggers the popover.
contentReactNodePopover contentContent to display in the popover.
positionstringbottomPosition of the popover (top, bottom, left, right).
alignstringcenterAlignment of the popover (start, center, end).