macbetter

Menus and pickers

Context Menu

Offer actions for the thing under the pointer or finger.

Weekend tripPack the tent, book the ferry, and check the tide times.Right-click or long-press the note
"use client"

import { ContextMenu, ContextMenuContent, ContextMenuTrigger } from "macbetter/context-menu"
import {
  MenuItem,
  MenuItemIcon,
  MenuSeparator,
  MenuSub,
  MenuSubContent,
  MenuSubTrigger,
} from "macbetter/menu"
import { CopyIcon, LinkIcon, MailIcon, MessageCircleIcon, PinIcon, ShareIcon, TrashIcon } from "lucide-react"

export default function Example() {
  return (
    <ContextMenu onAction={(value) => console.log(value)}>
      <ContextMenuTrigger
        tabIndex={0}
        aria-label="Weekend trip note"
        style={{
          width: 300,
          padding: 16,
          background: "var(--mb-bg-elevated)",
          boxShadow: "var(--mb-shadow-2)",
          display: "grid",
          gap: 4,
        }}
      >
        <strong style={{ font: "var(--mb-text-headline)" }}>Weekend trip</strong>
        <span style={{ color: "var(--mb-label-secondary)" }}>Pack the tent, book the ferry, and check the tide times.</span>
        <span style={{ color: "var(--mb-label-tertiary)", font: "var(--mb-text-footnote)" }}>
          Right-click or long-press the note
        </span>
      </ContextMenuTrigger>
      <ContextMenuContent>
        <MenuItem value="pin">
          <MenuItemIcon>
            <PinIcon />
          </MenuItemIcon>
          Pin Note
        </MenuItem>
        <MenuItem value="copy">
          <MenuItemIcon>
            <CopyIcon />
          </MenuItemIcon>
          Copy
        </MenuItem>
        <MenuSub>
          <MenuSubTrigger>
            <MenuItemIcon>
              <ShareIcon />
            </MenuItemIcon>
            Share
          </MenuSubTrigger>
          <MenuSubContent>
            <MenuItem value="share-messages">
              <MenuItemIcon>
                <MessageCircleIcon />
              </MenuItemIcon>
              Messages
            </MenuItem>
            <MenuItem value="share-mail">
              <MenuItemIcon>
                <MailIcon />
              </MenuItemIcon>
              Mail
            </MenuItem>
            <MenuItem value="share-link">
              <MenuItemIcon>
                <LinkIcon />
              </MenuItemIcon>
              Copy Link
            </MenuItem>
          </MenuSubContent>
        </MenuSub>
        <MenuSeparator />
        <MenuItem value="delete" destructive>
          <MenuItemIcon>
            <TrashIcon />
          </MenuItemIcon>
          Delete
        </MenuItem>
      </ContextMenuContent>
    </ContextMenu>
  )
}

When to use it

A context menu holds the most useful commands for one object: a file, a message, a photo. People open it with a right-click or Control-click on the Mac and a long press on iPhone and iPad.

  • Show only commands that apply to what was clicked, and keep them short.
  • Never make a context menu the only way to reach a command. It's a shortcut; the same commands belong in a toolbar, a menu bar menu or a pull-down button.
  • Put destructive commands last, separated from the rest.

Usage

import { ContextMenu, ContextMenuContent, ContextMenuTrigger } from "macbetter/context-menu"
import { MenuItem, MenuSeparator } from "macbetter/menu"

ContextMenu is a Menu whose trigger is an area instead of a button. Fill ContextMenuContent with the menu's own items, icons, checkboxes and submenus. Give the area tabIndex={0} and an accessible name so the keyboard can reach it.

Platform behavior

  • macOS: the menu opens at the pointer, at full size, with no scale. While it's open, the area gets an accent ring, as Finder rings the file you right-clicked.
  • iOS and iPadOS: a long press first presses the area down a little, then lifts it with a shadow while the menu springs open beside it, in the roomy iOS menu style.

Accessibility

The context-menu key opens the menu for the focused area, with focus on its first item. Shift+F10 does too in browsers that turn it into a context-menu event, as on Windows and Linux. Arrow keys, typeahead, Enter and Escape work as in any menu, and Escape returns focus to the area. Set disabled on the trigger to let the browser's own menu through.

API

PropTypeDefaultDescription
ContextMenuMenuProps—The root. Takes the Menu's props, like onAction. Always presents as a popover.
ContextMenuTriggerdisabled, payload—The area. Classes: mb-menu-context-trigger mb-context-menu-trigger.
ContextMenuContentMenuContent props—The menu. Opens at the pointer.
--mb-context-menu-enter-scalenumber1 · 0.6How small the menu starts, on the Mac · on touch.