Hanzo GUI

Context Menu

A menu component triggered by right-click on web and long press on touch devices

  • Full keyboard navigation.
  • Supports items, icons, images, checkboxes, groups, and more.
  • Supports submenus.
  • Supports modal and non-modal modes.
  • Supports native menus on native platforms.
  • Customizable alignment, offsets, and positioning.
  • Support for native iOS and Android icons.
  • Supports previews on iOS. ContextMenu displays a menu triggered by right-click on web or long-press on mobile. It supports submenus, native platform menus with iOS previews, and automatically stacks above other content.

Installation

ContextMenu is already installed in @hanzo/gui, or you can install it independently:

yarn add @hanzogui/context-menu

If you want to use native menus, add these dependencies:

yarn add @react-native-menu/menu
yarn add react-native-ios-context-menu
yarn add react-native-ios-utilities
yarn add zeego
yarn add sf-symbols-typescript

Then add the setup import at your app entry point:

import '@hanzogui/native/setup-zeego'

Expo Router users: This import must run before expo-router/entry. Create an index.js at your project root that imports the setup first, then expo-router, and update your package.json main field to "index.js". See the upgrade guide for details.

Anatomy

Import all parts and piece them together.

import { ContextMenu } from '@hanzo/gui' // or '@hanzogui/context-menu'

export default () => (
  <ContextMenu>
    <ContextMenu.Trigger asChild>
      <YStack>
        <Text>Right Click or longPress</Text>
      </YStack>
    </ContextMenu.Trigger>

    <ContextMenu.Portal zIndex={100}>
      <ContextMenu.Content>
        <ContextMenu.Item>
          <ContextMenu.ItemTitle>About Notes</ContextMenu.ItemTitle>
        </ContextMenu.Item>
        <ContextMenu.Item>
          <ContextMenu.ItemTitle>Settings</ContextMenu.ItemTitle>
        </ContextMenu.Item>
        {/* when title is nested inside a React element then you need to use `textValue` */}
        <ContextMenu.Item textValue="Calendar">
          <ContextMenu.ItemTitle>
            <Text>Calendar</Text>
          </ContextMenu.ItemTitle>
          <ContextMenu.ItemIcon>
            <Calendar color="gray" size="$1" />
          </ContextMenu.ItemIcon>
        </ContextMenu.Item>
        <ContextMenu.Separator />
        <ContextMenu.Sub>
          <ContextMenu.SubTrigger>
            <ContextMenu.ItemTitle>Actions</ContextMenu.ItemTitle>
          </ContextMenu.SubTrigger>
          <ContextMenu.Portal zIndex={200}>
            <ContextMenu.SubContent>
              <ContextMenu.Label fontSize={'$1'}>Note settings</ContextMenu.Label>
              <ContextMenu.Item onSelect={onSelect} key="create-note">
                <ContextMenu.ItemTitle>Create note</ContextMenu.ItemTitle>
              </ContextMenu.Item>
              <ContextMenu.Item onSelect={onSelect} key="delete-all">
                <ContextMenu.ItemTitle>Delete all notes</ContextMenu.ItemTitle>
              </ContextMenu.Item>
              <ContextMenu.Item onSelect={onSelect} key="sync-all">
                <ContextMenu.ItemTitle>Sync notes</ContextMenu.ItemTitle>
              </ContextMenu.Item>
            </ContextMenu.SubContent>
          </ContextMenu.Portal>
        </ContextMenu.Sub>
      </ContextMenu.Content>
    </ContextMenu.Portal>
  </ContextMenu>
)

API Reference

ContextMenu

Contains every component for the ContextMenu.

PropTypeDefaultRequired
childrenReact.ReactNode-
placementPlacement--
openboolean--
defaultOpenboolean--
onOpenChange(open: boolean, event?: { preventDefault: () => void }) => void--
onOpenWillChange(open: boolean) => void--
modalbooleantrue-
stayInFrameShiftProps | boolean{ padding: 10 }-
allowFlipFlipProps | boolean--
offsetOffsetOptions--
unstyledboolean--

Allowing Native Context Menu

You can call event.preventDefault() in onOpenChange to prevent the Hanzo GUI menu from opening and allow the native browser context menu to appear instead:

<ContextMenu
  onOpenChange={(open, event) => {
    if (someCondition) {
      // prevent Hanzo GUI menu, let native context menu show
      event?.preventDefault()
    }
  }}
>
  {/* ... */}
</ContextMenu>

ContextMenu.Portal

This is necessary for the ContextMenu.

PropTypeDefaultRequired
zIndexnumber--
childrenReact.ReactNode-
forceMounttrue--

ContextMenu.Trigger

The ContextMenu will only be triggered when the user right-clicks or long-presses within the Trigger area.

PropTypeDefaultRequired
actionpress|longPresslongPress-

ContextMenu.Content

Contains the content of the ContextMenu.

PropTypeDefaultRequired
childrenReact.ReactNode-
loopbooleanfalse-
forceMounttrue--
onCloseAutoFocus(event: Event) => void--
onEscapeKeyDown(event: KeyboardEvent) => void--
onPointerDownOutside(event: PointerEvent) => void--
onInteractOutside(event: Event) => void--

ContextMenu.Item

A selectable menu item that triggers an action when selected.

PropTypeDefaultRequired
keystring-
disabledbooleanfalse-
destructiveboolean--
hiddenboolean--
onSelect(event?: Event) => void--
onFocus() => void--
onBlur() => void--
textValuestring--

ContextMenu.ItemTitle

Renders the title of the menu item.

PropTypeDefaultRequired
childrenstring | React.ReactNode-

You can directly pass a text node to the ItemTitle. However, if you use a nested React node like <Text>, you need to pass textValue to the <Item> so that it works with native menus.

ContextMenu.ItemIcon

A component to render an icon. For non-native menus, you can pass an icon component. For native menus, you can pass platform-specific icons to the android/ios props.

On iOS, it renders the native SF Symbols icons.

PropTypeDefaultRequired
childrenReact.ReactNode--
iosobject--
androidobject--
<ContextMenu.ItemIcon
  ios={{
    name: '0.circle.fill', // required
    pointSize: 5,
    weight: 'semibold',
    scale: 'medium',
    // can also be a color string. Requires iOS 15+
    hierarchicalColor: {
      dark: 'blue',
      light: 'green',
    },
    // alternative to hierarchical color. Requires iOS 15+
    paletteColors: [
      {
        dark: 'blue',
        light: 'green',
      },
    ],
  }}
>
  <CircleIcon />
</ContextMenu.ItemIcon>

ContextMenu.ItemImage

A component to render an item image. For native menus, it only works on iOS. It takes the same properties as @hanzogui/image.

ContextMenu.ItemSubtitle

A component to render a subtitle for the menu item. For native menus, it only works on iOS.

PropTypeDefaultRequired
childrenstring-

ContextMenu.Group

A component that groups multiple menu items together.

PropTypeDefaultRequired
childrenReact.ReactNode-

ContextMenu.CheckboxItem

A menu item with a checkbox that can be toggled on/off.

PropTypeDefaultRequired
keystring-
disabledbooleanfalse-
destructiveboolean--
hiddenboolean--
onFocus() => void--
onBlur() => void--
textValuestring--
value'on' | 'off' | 'mixed'--
onValueChange(state, prevState) => void--
checkedboolean--
onCheckedChange(checked: boolean) => void--

ContextMenu.ItemIndicator

Use inside CheckboxItem or RadioItem to indicate when an item is checked. This allows you to conditionally render a checkmark.

<ContextMenu.ItemIndicator>
  <CheckmarkIcon /> {/* This does not work with the native prop. */}
</ContextMenu.ItemIndicator>
PropTypeDefaultRequired
childrenReact.ReactNode--
forceMounttrue--

ContextMenu.Label

Renders a non-focusable label for a group of items. On native menus, only one label is supported per menu and submenu.

PropTypeDefaultRequired
childrenstring-
textValuestring--

ContextMenu.Arrow

Renders an arrow pointing to the trigger.

PropTypeDefaultRequired
sizenumber | SizeToken--
unstyledboolean--

ContextMenu.Separator

Renders a visual divider between menu items. Web only.

ContextMenu.Sub

A container for nested sub-menu components.

PropTypeDefaultRequired
childrenReact.ReactNode-
openboolean--
onOpenChange(open: boolean) => void--

ContextMenu.SubContent

Renders the content of a sub-menu. Same props as Content, excluding side and align.

ContextMenu.SubTrigger

A menu item that opens a sub-menu on hover/focus. Accepts the same props as Item.

ContextMenu.Preview

When the ContextMenu is visible, this renders a custom preview component. Only works with native iOS menus.

Should be rendered as a child of ContextMenu.Content.
PropTypeDefaultRequired
childrenReact.ReactNode | (() => React.ReactNode)-
size{ width?: number, height?: number }--
onPress() => void--
backgroundColorstring | { dark: string, light: string }--
borderRadiusnumber--
preferredCommitStyle'pop' | 'dismiss''dismiss'-
<ContextMenu.Preview
  // optional props:
  preferredCommitStyle="pop" // or "dismiss"
  backgroundColor={{
    // or a color string directly
    dark: 'black',
    light: 'white',
  }}
>
  {() => <Preview />}
</ContextMenu.Preview>

Last updated on

On this page