ContextMenu

A menu that opens at the pointer on right-click or long press.

Right-click here

<script setup lang="ts">
  import { ref } from 'vue'
  import { Copy, FolderInput, Pencil, Trash2 } from '@lucide/vue'
  import {
    Center,
    ContextMenu,
    ContextMenuItem,
    ContextMenuSeparator,
    ContextMenuSub,
    Stack,
    Text,
  } from '@hina-ui/vue'

  const last = ref('')
</script>

<template>
  <Stack gap="sm" align="stretch" class="w-96">
    <ContextMenu label="File actions">
      <Center class="bg-inset h-32 rounded-lg border border-dashed select-none">
        <Text tone="muted" size="sm">Right-click here</Text>
      </Center>
      <template #content>
        <ContextMenuItem @select="last = 'Rename'">
          <template #icon><Pencil /></template>
          Rename
        </ContextMenuItem>
        <ContextMenuItem @select="last = 'Copy'">
          <template #icon><Copy /></template>
          Copy
        </ContextMenuItem>
        <ContextMenuSub label="Move to">
          <template #icon><FolderInput /></template>
          <ContextMenuItem @select="last = 'Move to Favorites'">Favorites</ContextMenuItem>
          <ContextMenuItem @select="last = 'Move to Archive'">Archive</ContextMenuItem>
        </ContextMenuSub>
        <ContextMenuSeparator />
        <ContextMenuItem tone="danger" @select="last = 'Delete'">
          <template #icon><Trash2 /></template>
          Delete
        </ContextMenuItem>
      </template>
    </ContextMenu>
    <Text v-if="last" tone="muted" size="sm">Picked: {{ last }}</Text>
  </Stack>
</template>
vue

Usage

import { ContextMenu, ContextMenuItem } from '@hina-ui/vue'
ts

The default slot is the area that answers a right-click, and the content slot holds the items. Right-click inside the area, or long-press on a touch screen, and the menu opens at the pointer; picking an item closes it. Items, checkbox items, radio items, groups, labels, separators and submenus are the same set as in DropdownMenu, only named with the ContextMenu prefix.

Right-click here

<script setup lang="ts">
  import { Center, ContextMenu, ContextMenuItem, Text } from '@hina-ui/vue'
</script>

<template>
  <ContextMenu label="Image actions">
    <Center class="bg-inset h-32 w-96 rounded-lg border border-dashed select-none">
      <Text tone="muted" size="sm">Right-click here</Text>
    </Center>
    <template #content>
      <ContextMenuItem>Open in new tab</ContextMenuItem>
      <ContextMenuItem>Copy image address</ContextMenuItem>
      <ContextMenuItem>Save image</ContextMenuItem>
    </template>
  </ContextMenu>
</template>
vue

Examples

Items

The icon slot of an item holds a leading icon and trailing the trailing content; give irreversible actions such as delete tone="danger". ContextMenuLabel is a title that cannot be selected, and ContextMenuSeparator draws a line.

Right-click here

<script setup lang="ts">
  import { Copy, Pencil, Trash2 } from '@lucide/vue'
  import {
    Center,
    ContextMenu,
    ContextMenuItem,
    ContextMenuLabel,
    ContextMenuSeparator,
    Kbd,
    Text,
  } from '@hina-ui/vue'
</script>

<template>
  <ContextMenu label="Note actions">
    <Center class="bg-inset h-32 w-96 rounded-lg border border-dashed select-none">
      <Text tone="muted" size="sm">Right-click here</Text>
    </Center>
    <template #content>
      <ContextMenuLabel>This note</ContextMenuLabel>
      <ContextMenuItem>
        <template #icon><Pencil /></template>
        Edit
        <template #trailing><Kbd>E</Kbd></template>
      </ContextMenuItem>
      <ContextMenuItem>
        <template #icon><Copy /></template>
        Copy
        <template #trailing><Kbd>⌘C</Kbd></template>
      </ContextMenuItem>
      <ContextMenuSeparator />
      <ContextMenuItem tone="danger">
        <template #icon><Trash2 /></template>
        Delete
      </ContextMenuItem>
    </template>
  </ContextMenu>
</template>
vue

Checkbox items

ContextMenuCheckboxItem is a toggle where several can be on at once; checked supports two-way binding, and the menu stays open after toggling.

Grid on, ruler off

<script setup lang="ts">
  import { ref } from 'vue'
  import { Center, ContextMenu, ContextMenuCheckboxItem, Text } from '@hina-ui/vue'

  const grid = ref(true)
  const ruler = ref(false)
</script>

<template>
  <ContextMenu label="View">
    <Center class="bg-inset h-32 w-96 rounded-lg border border-dashed select-none">
      <Text tone="muted" size="sm">
        Grid {{ grid ? 'on' : 'off' }}, ruler {{ ruler ? 'on' : 'off' }}
      </Text>
    </Center>
    <template #content>
      <ContextMenuCheckboxItem v-model:checked="grid">Show grid</ContextMenuCheckboxItem>
      <ContextMenuCheckboxItem v-model:checked="ruler">Show ruler</ContextMenuCheckboxItem>
    </template>
  </ContextMenu>
</template>
vue

Radio items

Wrap a set of mutually exclusive options in ContextMenuRadioGroup; the current one carries the check mark.

Sorted by name

<script setup lang="ts">
  import { ref } from 'vue'
  import {
    Center,
    ContextMenu,
    ContextMenuLabel,
    ContextMenuRadioGroup,
    ContextMenuRadioItem,
    Text,
  } from '@hina-ui/vue'

  const sort = ref('name')
  const names: Record<string, string> = { name: 'name', date: 'date modified', size: 'size' }
</script>

<template>
  <ContextMenu label="Sort">
    <Center class="bg-inset h-32 w-96 rounded-lg border border-dashed select-none">
      <Text tone="muted" size="sm">Sorted by {{ names[sort] }}</Text>
    </Center>
    <template #content>
      <ContextMenuLabel>Sort by</ContextMenuLabel>
      <ContextMenuRadioGroup v-model="sort">
        <ContextMenuRadioItem value="name">Name</ContextMenuRadioItem>
        <ContextMenuRadioItem value="date">Date modified</ContextMenuRadioItem>
        <ContextMenuRadioItem value="size">Size</ContextMenuRadioItem>
      </ContextMenuRadioGroup>
    </template>
  </ContextMenu>
</template>
vue

Disabled

disabled stops the whole area from answering a right-click, so the browser’s own menu shows as usual; disabled on an item only makes that item unselectable.

Right-click here

<script setup lang="ts">
  import { ref } from 'vue'
  import { Center, ContextMenu, ContextMenuItem, Stack, Switch, Text } from '@hina-ui/vue'

  const locked = ref(false)
</script>

<template>
  <Stack gap="md" align="start">
    <Switch v-model="locked">Lock the area</Switch>
    <ContextMenu label="Item actions" :disabled="locked">
      <Center class="bg-inset h-32 w-96 rounded-lg border border-dashed select-none">
        <Text tone="muted" size="sm">
          {{ locked ? "Locked; right-click shows the browser's own menu" : 'Right-click here' }}
        </Text>
      </Center>
      <template #content>
        <ContextMenuItem>Edit</ContextMenuItem>
        <ContextMenuItem disabled>Move</ContextMenuItem>
        <ContextMenuItem tone="danger">Delete</ContextMenuItem>
      </template>
    </ContextMenu>
  </Stack>
</template>
vue

Behavior

  • The menu opens at the pointer and flips inward near the viewport edge.
  • The page stops scrolling while the menu is open; a click outside, Esc or picking an item closes it.
  • Arrow keys move between items, Enter or Space picks one, and typing a letter jumps to a matching item.
  • A long press opens the menu on touch screens.

Accessibility

  • The menu is role="menu", named through label; items are menuitem, menuitemcheckbox and menuitemradio.
  • Actions in a context menu should also be reachable elsewhere on the page, since keyboard and assistive-technology users may not be able to trigger it.

API

ContextMenu

Prop
Type
Default
Description
label
string
—
Accessible name of the menu
disabled
boolean
false
Whether the area stops answering a right-click
open
boolean
—
Whether it is open, supports two-way binding
class
string
—
Classes appended to the menu panel
Slot
Description
default
The area that answers right-click
content
The items of the menu

Items and other parts

ContextMenuItem, ContextMenuCheckboxItem, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuGroup, ContextMenuLabel, ContextMenuSeparator and ContextMenuSub take exactly the props, slots and events of their DropdownMenu counterparts; see DropdownMenu.