Context Menu

Overlay & Feedback

Right-click context menu with items.

Preview

Right-click here

Usage

example.jsx
import { ContextMenu, ContextMenuItem } from "@/components/ui/context-menu";

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

Source Code

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

context-menu.jsx
"use client";

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

const ContextMenu = forwardRef(({ className, trigger, children, ...props }, ref) => {
  const [open, setOpen] = useState(false);
  const [pos, setPos] = useState({ x: 0, y: 0 });
  const menuRef = useRef(null);

  useEffect(() => {
    const handler = () => setOpen(false);
    document.addEventListener("click", handler);
    return () => document.removeEventListener("click", handler);
  }, []);

  const onContextMenu = (e) => {
    e.preventDefault();
    setPos({ x: e.clientX, y: e.clientY });
    setOpen(true);
  };

  return (
    <>
      <div onContextMenu={onContextMenu}>{trigger}</div>
      {open && (
        <div ref={(el) => { menuRef.current = el; if (typeof ref === "function") ref(el); else if (ref) ref.current = el; }}
          className={cn("fixed z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md", className)}
          style={{ top: pos.y, left: pos.x }}
          {...props}
        >
          {children}
        </div>
      )}
    </>
  );
});
ContextMenu.displayName = "ContextMenu";

const ContextMenuItem = forwardRef(({ className, ...props }, ref) => (
  <button ref={ref} className={cn("relative flex w-full cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground", className)} {...props} />
));
ContextMenuItem.displayName = "ContextMenuItem";

export { ContextMenu, ContextMenuItem };

Quick Install

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

npm install clsx tailwind-merge