FairGarden Design

Context Menu

A shortcut menu on power-user app surfaces (table rows, editors, canvases), opened by right-click, Shift+F10 or the Menu key.

import {
  ContextMenu,
  ContextMenuItem,
  ContextMenuPopup,
  ContextMenuSeparator,
  ContextMenuTrigger,
} from '@fairgarden-private/design/components/ContextMenu'

The popup is the Menu's build: a portaled white scope that follows the page mode, a --border-size-2 --primary12 edge, 44 px rows and the highlight with its start-edge bar. It anchors to the pointer, or to the focused object when opened from the keyboard. The trigger area has no visual part of its own. Its items are the Menu's parts, re-exported as ContextMenuItem, ContextMenuCheckboxItem, ContextMenuRadioItem, ContextMenuSubmenu and so on. It is hidden in print.

Every item must also exist in a visible "…" Menu on the same row or selection: the context menu is a shortcut, never the only route. Keep it off editorial and marketing pages.

Rows with a context menu

Rows with a context menu

Each row carries the same actions in its "…" Menu, which is also the route on touch screens.

  • Ridge Loop
  • Alder Creek
  • Meadow Spur

Right-click a row, press Shift+F10, or use its “…” menu.

ContextMenuBasic.tsx
'use client'

import * as React from 'react'
import {
  ContextMenu,
  ContextMenuItem,
  ContextMenuPopup,
  ContextMenuSeparator,
  ContextMenuTrigger,
} from '@fairgarden-private/design/components/ContextMenu'
import {
  Menu,
  MenuItem,
  MenuPopup,
  MenuSeparator,
  MenuTrigger,
} from '@fairgarden-private/design/components/Menu'
import styles from './basic.module.css'

const trails = ['Ridge Loop', 'Alder Creek', 'Meadow Spur'] as const

/** One set of actions, shown in both the context menu and the visible "…" Menu. */
function RowActions({
  trail,
  onAction,
  Item,
  Separator,
}: {
  trail: string
  onAction: (message: string) => void
  Item: typeof MenuItem
  Separator: typeof MenuSeparator
}) {
  return (
    <>
      <Item icon="zoom_in" onClick={() => onAction(`Opened ${trail}.`)}>
        Open trail
      </Item>
      <Item icon="download" onClick={() => onAction(`Downloaded the ${trail} map.`)}>
        Download map
      </Item>
      <Separator />
      <Item destructive onClick={() => onAction(`Removed ${trail} from the list.`)}>
        Remove from list
      </Item>
    </>
  )
}

export function ContextMenuBasic() {
  const [last, setLast] = React.useState('Right-click a row, press Shift+F10, or use its “…” menu.')

  return (
    <div className={styles.stack}>
      <ul className={styles.list} role="list">
        {trails.map((trail) => (
          <li key={trail} className={styles.row}>
            <ContextMenu>
              <ContextMenuTrigger className={styles.area} tabIndex={0}>
                <span className={styles.name}>{trail}</span>
              </ContextMenuTrigger>
              <ContextMenuPopup>
                <RowActions
                  trail={trail}
                  onAction={setLast}
                  Item={ContextMenuItem}
                  Separator={ContextMenuSeparator}
                />
              </ContextMenuPopup>
            </ContextMenu>
            <Menu>
              <MenuTrigger iconOnly icon="more_horiz" size="sm">
                {`${trail} actions`}
              </MenuTrigger>
              <MenuPopup align="end">
                <RowActions trail={trail} onAction={setLast} Item={MenuItem} Separator={MenuSeparator} />
              </MenuPopup>
            </Menu>
          </li>
        ))}
      </ul>
      <p className={styles.status} aria-live="polite">
        {last}
      </p>
    </div>
  )
}

API Reference

ContextMenu and ContextMenuTrigger

Groups the parts of a context menu (Base UI ContextMenu.Root). It opens by right-click, Shift+F10 or the Menu key on the trigger area.

The area that opens the menu: a row, an editor or a canvas. It has no visual part of its own; it writes its scope attributes like every root.

ContextMenuPopup

The popup: the Menu’s build (a white scope, --border-size-2 --primary12 edge, --ds-size-hit rows, the highlight with its start-edge bar), anchored to the pointer, or to the focused object for keyboard invocation. Compose the Menu item parts inside it, re-exported here as ContextMenuItem, ContextMenuCheckboxItem and so on.

PropTypeDescription
primary
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'ruby'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| null
| undefined

Primary Radix scale inside the popup’s white scope. Omitted, the white preset’s default: the popup never takes the trigger’s scales [D133].

secondary
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'ruby'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| 'amber'
| 'blue'
| 'cyan'
| 'grass'
| 'green'
| 'jade'
| 'lime'
| 'mint'
| 'orange'
| 'sky'
| 'teal'
| 'yellow'
| null
| undefined

Secondary Radix scale inside the popup. It drives nothing but a destructive item’s fallback.

container
| HTMLElement
| ShadowRoot
| React.RefObject<HTMLElement | ShadowRoot | null>
| null
| undefined

The element the portal renders into. Default: document.body.

align
Align | undefined

Alignment to the trigger. Default start.

alignOffset
number | OffsetFunction | undefined

Offset along the alignment axis in px.

side
Side | undefined

Side of the trigger. Default bottom for a menu, the end side for a submenu.

sideOffset
number | OffsetFunction | undefined

Distance from the trigger in px. Default 8 (--size-px-2).

collisionPadding
Padding | undefined

Clearance from the viewport edge in px before the popup shifts or flips. Default 16.

keepMounted
boolean | undefined

Keeps the portal mounted while closed.

Additional types

ContextMenuPopupProps

Props for ContextMenuPopup: the Menu popup’s props.

type ContextMenuPopupProps = MenuPopupProps
ContextMenuProps

Props for ContextMenu: Base UI ContextMenu.Root props.

type ContextMenuProps = ContextMenuRoot.ContextMenuRootProps
ContextMenuTriggerProps

Props for ContextMenuTrigger: Base UI ContextMenu.Trigger props.

type ContextMenuTriggerProps = ContextMenuTrigger.ContextMenuTriggerProps

Specification: DESIGN-SYSTEM.md §9.7 (menu, and its Context Menu sub-spec).