User Menu

Auth & User

User profile dropdown menu.

Preview

Usage

example.jsx
import { UserMenu } from "@/components/ui/user-menu";

export default function Example() {
  return <UserMenu />;
}

Source Code

Copy this file into components/ui/user-menu.jsx in your project.

user-menu.jsx
"use client";

import { forwardRef, useState, useRef, useEffect } from "react";
import { cn } from "@/lib/utils";

const UserMenu = forwardRef(({ className, user = {}, menuItems = [], ...props }, ref) => {
  const [open, setOpen] = useState(false);
  const menuRef = useRef(null);

  useEffect(() => {
    const handler = (e) => { if (menuRef.current && !menuRef.current.contains(e.target)) setOpen(false); };
    document.addEventListener("mousedown", handler);
    return () => document.removeEventListener("mousedown", handler);
  }, []);

  return (
    <div ref={(el) => { menuRef.current = el; if (typeof ref === "function") ref(el); else if (ref) ref.current = el; }} className={cn("relative", className)} {...props}>
      <button onClick={() => setOpen(!open)} className="flex items-center gap-2 rounded-full p-1 hover:bg-accent">
        <div className="flex h-8 w-8 items-center justify-center rounded-full bg-primary text-sm font-medium text-primary-foreground">
          {user.avatar ? <img src={user.avatar} alt="" className="h-full w-full rounded-full object-cover" /> : (user.name?.[0] || "U")}
        </div>
        <span className="hidden text-sm font-medium sm:block">{user.name}</span>
      </button>
      {open && (
        <div className="absolute right-0 mt-2 w-56 rounded-md border bg-popover p-1 shadow-lg">
          <div className="border-b px-3 py-2">
            <div className="text-sm font-medium">{user.name}</div>
            <div className="text-xs text-muted-foreground">{user.email}</div>
          </div>
          <div className="py-1">
            {menuItems.map((item, i) => (
              item.separator ? <div key={i} className="my-1 h-px bg-border" /> :
              <button key={i} onClick={() => { item.onClick?.(); setOpen(false); }}
                className="flex w-full items-center gap-2 rounded-sm px-3 py-1.5 text-sm hover:bg-accent"
              >
                {item.icon && <span>{item.icon}</span>}{item.label}
              </button>
            ))}
          </div>
        </div>
      )}
    </div>
  );
});
UserMenu.displayName = "UserMenu";

export { UserMenu };

Quick Install

Make sure you have the cn() utility set up. It requires clsx and tailwind-merge.

npm install clsx tailwind-merge