Skip to main content

Overview

The Context Menu component displays a contextual menu when right-clicking an element. Supports items with icons, avatars, keyboard shortcuts, checkboxes, and nested submenus.

Basic Usage

<template>
  <UContextMenu :items="menuItems">
    <template #default>
      <div class="p-8 border rounded">
        Right-click here
      </div>
    </template>
  </UContextMenu>
</template>

<script setup>
const menuItems = [
  {
    label: 'Edit',
    icon: 'i-lucide-pencil',
    onSelect: () => console.log('Edit clicked')
  },
  {
    label: 'Delete',
    icon: 'i-lucide-trash',
    color: 'error',
    onSelect: () => console.log('Delete clicked')
  }
]
</script>

Props

items
ContextMenuItem[]
Array of menu items to display. See Item Properties below.
size
'xs' | 'sm' | 'md' | 'lg' | 'xl'
default:"'md'"
The size of the menu and its items.
checkedIcon
string
default:"appConfig.ui.icons.check"
The icon displayed when an item is checked. Accepts Iconify icon names.
loadingIcon
string
default:"appConfig.ui.icons.loading"
The icon displayed when an item is loading. Accepts Iconify icon names.
externalIcon
boolean | string
default:"appConfig.ui.icons.external"
The icon displayed when the item is an external link. Set to false to hide.
content
ContextMenuContentProps
Additional props passed to the menu content element.
portal
boolean | string | HTMLElement
default:"true"
Render the menu in a portal.
modal
boolean
default:"true"
The modality of the menu.
labelKey
string
default:"'label'"
The key used to get the label from the item.
descriptionKey
string
default:"'description'"
The key used to get the description from the item.
disabled
boolean
Disable the entire context menu.
class
any
CSS class for styling the trigger element.
ui
object
Theme customization object for component slots.

Events

update:open
(open: boolean) => void
Emitted when the open state changes.

Slots

default
{}
The trigger element that opens the context menu on right-click.
item
{ item: ContextMenuItem, active: boolean, index: number, ui: object }
Customize the entire item rendering.
item-leading
{ item: ContextMenuItem, active: boolean, index: number, ui: object }
Customize the leading section (icon/avatar) of items.
item-label
{ item: ContextMenuItem, active: boolean, index: number }
Customize the label section of items.
item-description
{ item: ContextMenuItem, active: boolean, index: number }
Customize the description section of items.
item-trailing
{ item: ContextMenuItem, active: boolean, index: number, ui: object }
Customize the trailing section (keyboard shortcuts/icons) of items.
content-top
{ sub: boolean }
Content rendered at the top of the menu.
content-bottom
{ sub: boolean }
Content rendered at the bottom of the menu.

Item Properties

Each item in the items array can have the following properties:
label
string
The text label for the item.
description
string
Optional description text shown below the label.
icon
string
Iconify icon name to display before the label.
avatar
AvatarProps
Avatar to display instead of an icon.
color
string
Color variant for the item.
kbds
KbdProps['value'][] | KbdProps[]
Keyboard shortcuts to display on the right.
type
'label' | 'separator' | 'link' | 'checkbox'
default:"'link'"
The item type.
slot
string
Custom slot name for this item.
loading
boolean
Show loading state for the item.
disabled
boolean
Disable the item.
checked
boolean
Checked state for checkbox items.
children
ContextMenuItem[]
Nested submenu items.
onSelect
(e: Event) => void
Callback fired when the item is selected.
onUpdateChecked
(checked: boolean) => void
Callback fired when checkbox state changes.

Examples

With Nested Submenus

<template>
  <UContextMenu :items="menuItems">
    <template #default>
      <div class="p-8 border rounded">
        Right-click for nested menu
      </div>
    </template>
  </UContextMenu>
</template>

<script setup>
const menuItems = [
  {
    label: 'File',
    icon: 'i-lucide-file',
    children: [
      { label: 'New', icon: 'i-lucide-file-plus' },
      { label: 'Open', icon: 'i-lucide-folder-open' },
      { type: 'separator' },
      { label: 'Save', icon: 'i-lucide-save', kbds: ['meta', 'S'] }
    ]
  },
  {
    label: 'Edit',
    icon: 'i-lucide-pencil',
    children: [
      { label: 'Cut', icon: 'i-lucide-scissors', kbds: ['meta', 'X'] },
      { label: 'Copy', icon: 'i-lucide-copy', kbds: ['meta', 'C'] },
      { label: 'Paste', icon: 'i-lucide-clipboard', kbds: ['meta', 'V'] }
    ]
  }
]
</script>

With Checkboxes

<template>
  <UContextMenu :items="menuItems">
    <template #default>
      <div class="p-8 border rounded">
        Right-click for options
      </div>
    </template>
  </UContextMenu>
</template>

<script setup>
const showToolbar = ref(true)
const showSidebar = ref(false)

const menuItems = computed(() => [
  {
    label: 'Show Toolbar',
    type: 'checkbox',
    checked: showToolbar.value,
    onUpdateChecked: (val) => showToolbar.value = val
  },
  {
    label: 'Show Sidebar',
    type: 'checkbox',
    checked: showSidebar.value,
    onUpdateChecked: (val) => showSidebar.value = val
  }
])
</script>

With Separators and Labels

<template>
  <UContextMenu :items="menuItems">
    <template #default>
      <div class="p-8 border rounded">
        Right-click here
      </div>
    </template>
  </UContextMenu>
</template>

<script setup>
const menuItems = [
  { type: 'label', label: 'Actions' },
  { label: 'Edit', icon: 'i-lucide-pencil' },
  { label: 'Duplicate', icon: 'i-lucide-copy' },
  { type: 'separator' },
  { type: 'label', label: 'Danger Zone' },
  { label: 'Delete', icon: 'i-lucide-trash', color: 'error' }
]
</script>

With User Avatar

<template>
  <UContextMenu :items="menuItems">
    <template #default>
      <div class="p-8 border rounded">
        Right-click for user menu
      </div>
    </template>
  </UContextMenu>
</template>

<script setup>
const menuItems = [
  {
    label: 'John Doe',
    description: '[email protected]',
    avatar: { src: '/avatar.jpg' }
  },
  { type: 'separator' },
  { label: 'Profile', icon: 'i-lucide-user' },
  { label: 'Settings', icon: 'i-lucide-settings' },
  { type: 'separator' },
  { label: 'Logout', icon: 'i-lucide-log-out', color: 'error' }
]
</script>

With Loading State

<template>
  <UContextMenu :items="menuItems">
    <template #default>
      <div class="p-8 border rounded">
        Right-click to see loading
      </div>
    </template>
  </UContextMenu>
</template>

<script setup>
const isLoading = ref(false)

const menuItems = computed(() => [
  { 
    label: 'Refresh', 
    icon: 'i-lucide-refresh-cw',
    loading: isLoading.value,
    onSelect: async () => {
      isLoading.value = true
      await new Promise(resolve => setTimeout(resolve, 2000))
      isLoading.value = false
    }
  }
])
</script>
<template>
  <UContextMenu :items="menuItems">
    <template #default>
      <div class="p-8 border rounded">
        Right-click for links
      </div>
    </template>
  </UContextMenu>
</template>

<script setup>
const menuItems = [
  { 
    label: 'Documentation', 
    icon: 'i-lucide-book',
    to: 'https://nuxt.com',
    target: '_blank'
  },
  { 
    label: 'GitHub', 
    icon: 'i-lucide-github',
    to: 'https://github.com/nuxt/ui',
    target: '_blank'
  }
]
</script>

Build docs developers (and LLMs) love