Aura Design System

Context Menu

Displays a menu to the user triggered by right-click or long-press.

Preview

Right click here
import { useState } from "react";
import {
  ContextMenu,
  ContextMenuTrigger,
  ContextMenuContent,
  ContextMenuItem,
  ContextMenuCheckboxItem,
  ContextMenuRadioItem,
  ContextMenuLabel,
  ContextMenuSeparator,
  ContextMenuShortcut,
  ContextMenuSub,
  ContextMenuSubContent,
  ContextMenuSubTrigger,
  ContextMenuRadioGroup,
} from "@/components/ui/ContextMenu";

export function ContextMenuDemo() {
  return (
  <ContextMenu>
    <ContextMenuTrigger className="flex h-[150px] w-[300px] items-center justify-center rounded-md border border-dashed border-gray-6 text-sm">
      Right click here
    </ContextMenuTrigger>
    <ContextMenuContent className="w-18">
      <ContextMenuItem>
        Back
        <ContextMenuShortcut>⌘[</ContextMenuShortcut>
      </ContextMenuItem>
      <ContextMenuItem disabled>
        Forward
        <ContextMenuShortcut>⌘]</ContextMenuShortcut>
      </ContextMenuItem>
      <ContextMenuItem>
        Reload
        <ContextMenuShortcut>⌘R</ContextMenuShortcut>
      </ContextMenuItem>
      <ContextMenuSeparator />
      <ContextMenuItem>
        Save As...
        <ContextMenuShortcut>⇧⌘S</ContextMenuShortcut>
      </ContextMenuItem>
      <ContextMenuItem>Print...</ContextMenuItem>
    </ContextMenuContent>
  </ContextMenu>
)
}

Installation

Make sure that namespace is set in your component.json file. Namespace docs: Learn more about namespaces

pnpm dlx shadcn@latest add @aura/context-menu

Manual

Install the following dependencies:

pnpm install @radix-ui/react-icons radix-ui

Copy and paste the class names utility into your utils/class-names.ts file.

import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

Copy and paste the ContextMenu component into your components/ui/ContextMenu.tsx file.

"use client";
/**
 * @description Displays a menu to the user triggered by right-click or long-press.
 */
import * as React from "react";
import { ContextMenu as ContextMenuRadix } from "radix-ui";
import {
  CheckIcon,
  ChevronRightIcon,
  DotFilledIcon,
} from "@radix-ui/react-icons";

import { cn } from "@/utils/class-names";

function ContextMenu({
  ...props
}: React.ComponentProps<typeof ContextMenuRadix.Root>) {
  return <ContextMenuRadix.Root data-slot="context-menu" {...props} />;
}

function ContextMenuTrigger({
  className,
  ...props
}: React.ComponentProps<typeof ContextMenuRadix.Trigger>) {
  return (
    <ContextMenuRadix.Trigger
      data-slot="context-menu-trigger"
      className={cn(className)}
      {...props}
    />
  );
}

function ContextMenuGroup({
  ...props
}: React.ComponentProps<typeof ContextMenuRadix.Group>) {
  return <ContextMenuRadix.Group data-slot="context-menu-group" {...props} />;
}

function ContextMenuPortal({
  ...props
}: React.ComponentProps<typeof ContextMenuRadix.Portal>) {
  return <ContextMenuRadix.Portal data-slot="context-menu-portal" {...props} />;
}

function ContextMenuSub({
  ...props
}: React.ComponentProps<typeof ContextMenuRadix.Sub>) {
  return <ContextMenuRadix.Sub data-slot="context-menu-sub" {...props} />;
}

function ContextMenuRadioGroup({
  className,
  ...props
}: React.ComponentProps<typeof ContextMenuRadix.RadioGroup>) {
  return (
    <ContextMenuRadix.RadioGroup
      data-slot="context-menu-radio-group"
      className={cn(className)}
      {...props}
    />
  );
}

function ContextMenuSubTrigger({
  children,
  className,
  ...props
}: React.ComponentProps<typeof ContextMenuRadix.SubTrigger>) {
  return (
    <ContextMenuRadix.SubTrigger
      data-slot="context-menu-sub-trigger"
      className={cn(
        className,
        "p-0.5 px-2 hover:bg-accent-3 flex relative cursor-pointer"
      )}
      {...props}
    >
      {children}
      <div className="absolute right-0.5 top-0 bottom-0 items-center flex justify-center">
        <ChevronRightIcon />
      </div>
    </ContextMenuRadix.SubTrigger>
  );
}

function ContextMenuSubContent({
  className,
  ...props
}: React.ComponentProps<typeof ContextMenuRadix.SubContent>) {
  return (
    <ContextMenuRadix.SubContent
      data-slot="context-menu-sub-content"
      className={cn(
        className,
        "bg-gray-1 border border-gray-a6 rounded-sm relative shadow-md"
      )}
      {...props}
    />
  );
}

function ContextMenuContent({
  className,
  ...props
}: React.ComponentProps<typeof ContextMenuRadix.Content>) {
  return (
    <ContextMenuRadix.Portal>
      <ContextMenuRadix.Content
        data-slot="context-menu-content"
        collisionPadding={8}
        className={cn(
          className,
          "bg-gray-1 border border-gray-a6 rounded-sm relative shadow-md"
        )}
        {...props}
      />
    </ContextMenuRadix.Portal>
  );
}

function ContextMenuItem({
  className,
  ...props
}: React.ComponentProps<typeof ContextMenuRadix.Item> & {}) {
  return (
    <ContextMenuRadix.Item
      data-slot="context-menu-item"
      className={cn(
        className,
        "p-0.5 px-2 hover:bg-accent-3 flex relative cursor-pointer justify-between"
      )}
      {...props}
    />
  );
}

function ContextMenuCheckboxItem({
  children,
  checked,
  className,
  ...props
}: React.ComponentProps<typeof ContextMenuRadix.CheckboxItem>) {
  return (
    <ContextMenuRadix.CheckboxItem
      data-slot="context-menu-checkbox-item"
      className={cn(
        className,
        "p-0.5 px-2 hover:bg-accent-3 flex relative cursor-pointer"
      )}
      checked={checked}
      {...props}
    >
      <span className="absolute left-0.5 top-0 bottom-0 items-center flex justify-center">
        <ContextMenuRadix.ItemIndicator>
          <CheckIcon className="text-accent-8" />
        </ContextMenuRadix.ItemIndicator>
      </span>
      {children}
    </ContextMenuRadix.CheckboxItem>
  );
}

function ContextMenuRadioItem({
  children,
  className,
  ...props
}: React.ComponentProps<typeof ContextMenuRadix.RadioItem>) {
  return (
    <ContextMenuRadix.RadioItem
      data-slot="context-menu-radio-item"
      className={cn(
        className,
        "p-0.5 px-2 hover:bg-accent-3 flex relative cursor-pointer"
      )}
      {...props}
    >
      <span className="absolute left-0.5 top-0 bottom-0 items-center flex justify-center">
        <ContextMenuRadix.ItemIndicator>
          <DotFilledIcon className="text-accent-8" />
        </ContextMenuRadix.ItemIndicator>
      </span>
      {children}
    </ContextMenuRadix.RadioItem>
  );
}

function ContextMenuLabel({
  className,
  ...props
}: React.ComponentProps<typeof ContextMenuRadix.Label>) {
  return (
    <ContextMenuRadix.Label
      data-slot="context-menu-label"
      className={cn(className, "p-0.5 px-2 text-gray-12")}
      {...props}
    />
  );
}

function ContextMenuSeparator({
  className,
  ...props
}: React.ComponentProps<typeof ContextMenuRadix.Separator>) {
  return (
    <ContextMenuRadix.Separator
      data-slot="context-menu-separator"
      className={cn(className, "m-0.6 h-px bg-gray-a6")}
      {...props}
    />
  );
}

function ContextMenuShortcut({ ...props }: React.ComponentProps<"span">) {
  return <span data-slot="context-menu-shortcut" {...props} />;
}

export {
  ContextMenu,
  ContextMenuTrigger,
  ContextMenuContent,
  ContextMenuItem,
  ContextMenuCheckboxItem,
  ContextMenuRadioItem,
  ContextMenuLabel,
  ContextMenuSeparator,
  ContextMenuShortcut,
  ContextMenuGroup,
  ContextMenuPortal,
  ContextMenuSub,
  ContextMenuSubContent,
  ContextMenuSubTrigger,
  ContextMenuRadioGroup,
};

Usage

WithSubmenu

export const WithSubmenu = () => (
  <ContextMenu>
    <ContextMenuTrigger className="flex h-[150px] w-[300px] items-center justify-center rounded-md border border-dashed border-gray-6 text-sm">
      Right click here
    </ContextMenuTrigger>
    <ContextMenuContent className="w-18">
      <ContextMenuItem>Back</ContextMenuItem>
      <ContextMenuItem>Forward</ContextMenuItem>
      <ContextMenuItem>Reload</ContextMenuItem>
      <ContextMenuSeparator />
      <ContextMenuSub>
        <ContextMenuSubTrigger>More Tools</ContextMenuSubTrigger>
        <ContextMenuSubContent className="w-18">
          <ContextMenuItem>
            Save Page As...
            <ContextMenuShortcut>⇧⌘S</ContextMenuShortcut>
          </ContextMenuItem>
          <ContextMenuItem>Create Shortcut...</ContextMenuItem>
          <ContextMenuItem>Name Window...</ContextMenuItem>
          <ContextMenuSeparator />
          <ContextMenuItem>Developer Tools</ContextMenuItem>
        </ContextMenuSubContent>
      </ContextMenuSub>
      <ContextMenuSeparator />
      <ContextMenuItem>
        Inspect
        <ContextMenuShortcut>⌥⌘I</ContextMenuShortcut>
      </ContextMenuItem>
    </ContextMenuContent>
  </ContextMenu>
)

WithCheckboxes

export const WithCheckboxes = () => {
  const [showBookmarksBar, setShowBookmarksBar] = useState(true);
  const [showFullUrls, setShowFullUrls] = useState(false);

  return (
    <ContextMenu>
      <ContextMenuTrigger className="flex h-[150px] w-[300px] items-center justify-center rounded-md border border-dashed border-gray-6 text-sm">
        Right click here
      </ContextMenuTrigger>
      <ContextMenuContent className="w-18">
        <ContextMenuCheckboxItem
          checked={showBookmarksBar}
          onCheckedChange={setShowBookmarksBar}
        >
          Show Bookmarks Bar
          <ContextMenuShortcut>⌘⇧B</ContextMenuShortcut>
        </ContextMenuCheckboxItem>
        <ContextMenuCheckboxItem
          checked={showFullUrls}
          onCheckedChange={setShowFullUrls}
        >
          Show Full URLs
        </ContextMenuCheckboxItem>
      </ContextMenuContent>
    </ContextMenu>
  );
};

WithRadioGroup

export const WithRadioGroup = () => {
  const [person, setPerson] = useState("pedro");

  return (
    <ContextMenu>
      <ContextMenuTrigger className="flex h-[150px] w-[300px] items-center justify-center rounded-md border border-dashed border-gray-6 text-sm">
        Right click here
      </ContextMenuTrigger>
      <ContextMenuContent className="w-18">
        <ContextMenuRadioGroup value={person} onValueChange={setPerson}>
          <ContextMenuLabel>People</ContextMenuLabel>
          <ContextMenuSeparator />
          <ContextMenuRadioItem value="pedro">
            Pedro Duarte
          </ContextMenuRadioItem>
          <ContextMenuRadioItem value="colm">Colm Tuite</ContextMenuRadioItem>
        </ContextMenuRadioGroup>
      </ContextMenuContent>
    </ContextMenu>
  );
};

ComplexMenu

export const ComplexMenu = () => {
  const [showBookmarksBar, setShowBookmarksBar] = useState(true);
  const [showFullUrls, setShowFullUrls] = useState(false);
  const [person, setPerson] = useState("pedro");

  return (
    <ContextMenu>
      <ContextMenuTrigger className="flex h-[150px] w-[300px] items-center justify-center rounded-md border border-dashed border-gray-6 text-sm">
        Right click here
      </ContextMenuTrigger>
      <ContextMenuContent className="w-20">
        <ContextMenuItem>
          Back
          <ContextMenuShortcut>⌘[</ContextMenuShortcut>
        </ContextMenuItem>
        <ContextMenuItem disabled>
          Forward
          <ContextMenuShortcut>⌘]</ContextMenuShortcut>
        </ContextMenuItem>
        <ContextMenuItem>
          Reload
          <ContextMenuShortcut>⌘R</ContextMenuShortcut>
        </ContextMenuItem>
        <ContextMenuSeparator />
        <ContextMenuCheckboxItem
          checked={showBookmarksBar}
          onCheckedChange={setShowBookmarksBar}
        >
          Show Bookmarks Bar
          <ContextMenuShortcut>⌘⇧B</ContextMenuShortcut>
        </ContextMenuCheckboxItem>
        <ContextMenuCheckboxItem
          checked={showFullUrls}
          onCheckedChange={setShowFullUrls}
        >
          Show Full URLs
        </ContextMenuCheckboxItem>
        <ContextMenuSeparator />
        <ContextMenuRadioGroup value={person} onValueChange={setPerson}>
          <ContextMenuLabel>People</ContextMenuLabel>
          <ContextMenuSeparator />
          <ContextMenuRadioItem value="pedro">
            Pedro Duarte
          </ContextMenuRadioItem>
          <ContextMenuRadioItem value="colm">Colm Tuite</ContextMenuRadioItem>
        </ContextMenuRadioGroup>
      </ContextMenuContent>
    </ContextMenu>
  );
};