Overview
The Dropdown Menu component displays a menu when clicking a trigger element. Supports items with icons, avatars, keyboard shortcuts, checkboxes, and nested submenus.
Basic Usage
<template>
<UDropdownMenu :items="menuItems">
<template #default="{ open }">
<UButton>Menu {{ open ? '▲' : '▼' }}</UButton>
</template>
</UDropdownMenu>
</template>
<script setup>
const menuItems = [
{
label: 'Profile',
icon: 'i-lucide-user',
onSelect: () => console.log('Profile clicked')
},
{
label: 'Settings',
icon: 'i-lucide-settings',
onSelect: () => console.log('Settings clicked')
},
{ type: 'separator' },
{
label: 'Logout',
icon: 'i-lucide-log-out',
color: 'error',
onSelect: () => console.log('Logout clicked')
}
]
</script>
Props
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.
Configuration object for menu positioning and behavior.
arrow
boolean | DropdownMenuArrowProps
default:"false"
Display an arrow pointing to the trigger element.
portal
boolean | string | HTMLElement
default:"true"
Render the menu in a portal.
The modality of the menu.
Controls the open state of the menu (v-model:open).
The default open state when uncontrolled.
The key used to get the label from the item.
descriptionKey
string
default:"'description'"
The key used to get the description from the item.
Disable the entire dropdown menu.
CSS class for styling the trigger element.
Theme customization object for component slots.
Events
Emitted when the open state changes.
Slots
The trigger element that opens the dropdown menu. Receives the current open state.
item
{ item: DropdownMenuItem, active: boolean, index: number, ui: object }
Customize the entire item rendering.
item-leading
{ item: DropdownMenuItem, active: boolean, index: number, ui: object }
Customize the leading section (icon/avatar) of items.
item-label
{ item: DropdownMenuItem, active: boolean, index: number }
Customize the label section of items.
item-description
{ item: DropdownMenuItem, active: boolean, index: number }
Customize the description section of items.
item-trailing
{ item: DropdownMenuItem, active: boolean, index: number, ui: object }
Customize the trailing section (keyboard shortcuts/icons) of items.
Content rendered at the top of the menu.
Content rendered at the bottom of the menu.
Item Properties
Each item in the items array can have the following properties:
The text label for the item.
Optional description text shown below the label.
Iconify icon name to display before the label.
Avatar to display instead of an icon.
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.
Custom slot name for this item.
Show loading state for the item.
Checked state for checkbox items.
Callback fired when the item is selected.
onUpdateChecked
(checked: boolean) => void
Callback fired when checkbox state changes.
Examples
<template>
<UDropdownMenu :items="userMenu">
<template #default>
<UButton>
<UAvatar src="/avatar.jpg" size="sm" />
</UButton>
</template>
</UDropdownMenu>
</template>
<script setup>
const userMenu = [
{
label: 'John Doe',
description: '[email protected]',
slot: 'user'
},
{ type: 'separator' },
{ label: 'Profile', icon: 'i-lucide-user', to: '/profile' },
{ label: 'Settings', icon: 'i-lucide-settings', to: '/settings' },
{ label: 'Billing', icon: 'i-lucide-credit-card', to: '/billing' },
{ type: 'separator' },
{ label: 'Logout', icon: 'i-lucide-log-out', color: 'error' }
]
</script>
<template>
<UDropdownMenu :items="menuItems">
<template #default>
<UButton>Actions</UButton>
</template>
</UDropdownMenu>
</template>
<script setup>
const menuItems = [
{
label: 'Share',
icon: 'i-lucide-share',
children: [
{ label: 'Email', icon: 'i-lucide-mail' },
{ label: 'Copy Link', icon: 'i-lucide-link' },
{ label: 'Social Media', icon: 'i-lucide-share-2' }
]
},
{
label: 'Export',
icon: 'i-lucide-download',
children: [
{ label: 'Export as PDF', icon: 'i-lucide-file-text' },
{ label: 'Export as CSV', icon: 'i-lucide-table' },
{ label: 'Export as JSON', icon: 'i-lucide-braces' }
]
},
{ type: 'separator' },
{ label: 'Delete', icon: 'i-lucide-trash', color: 'error' }
]
</script>
With Keyboard Shortcuts
<template>
<UDropdownMenu :items="menuItems">
<template #default>
<UButton>File</UButton>
</template>
</UDropdownMenu>
</template>
<script setup>
const menuItems = [
{ label: 'New', icon: 'i-lucide-file-plus', kbds: ['meta', 'N'] },
{ label: 'Open', icon: 'i-lucide-folder-open', kbds: ['meta', 'O'] },
{ type: 'separator' },
{ label: 'Save', icon: 'i-lucide-save', kbds: ['meta', 'S'] },
{ label: 'Save As', icon: 'i-lucide-save', kbds: ['meta', 'shift', 'S'] },
{ type: 'separator' },
{ label: 'Close', icon: 'i-lucide-x', kbds: ['meta', 'W'] }
]
</script>
With Checkboxes
<template>
<UDropdownMenu :items="viewOptions">
<template #default>
<UButton>View Options</UButton>
</template>
</UDropdownMenu>
</template>
<script setup>
const showLineNumbers = ref(true)
const showMinimap = ref(false)
const wordWrap = ref(true)
const viewOptions = computed(() => [
{
label: 'Show Line Numbers',
type: 'checkbox',
checked: showLineNumbers.value,
onUpdateChecked: (val) => showLineNumbers.value = val
},
{
label: 'Show Minimap',
type: 'checkbox',
checked: showMinimap.value,
onUpdateChecked: (val) => showMinimap.value = val
},
{
label: 'Word Wrap',
type: 'checkbox',
checked: wordWrap.value,
onUpdateChecked: (val) => wordWrap.value = val
}
])
</script>
With Arrow
<template>
<UDropdownMenu :items="menuItems" :arrow="true">
<template #default>
<UButton>Menu with Arrow</UButton>
</template>
</UDropdownMenu>
</template>
Different Positions
<template>
<div class="flex gap-4">
<UDropdownMenu :items="menuItems" :content="{ side: 'top' }">
<UButton>Top</UButton>
</UDropdownMenu>
<UDropdownMenu :items="menuItems" :content="{ side: 'right' }">
<UButton>Right</UButton>
</UDropdownMenu>
<UDropdownMenu :items="menuItems" :content="{ side: 'bottom' }">
<UButton>Bottom</UButton>
</UDropdownMenu>
<UDropdownMenu :items="menuItems" :content="{ side: 'left' }">
<UButton>Left</UButton>
</UDropdownMenu>
</div>
</template>
With Loading State
<template>
<UDropdownMenu :items="menuItems">
<template #default>
<UButton>Actions</UButton>
</template>
</UDropdownMenu>
</template>
<script setup>
const isRefreshing = ref(false)
const menuItems = computed(() => [
{
label: 'Refresh',
icon: 'i-lucide-refresh-cw',
loading: isRefreshing.value,
onSelect: async () => {
isRefreshing.value = true
await new Promise(resolve => setTimeout(resolve, 2000))
isRefreshing.value = false
}
},
{ label: 'Settings', icon: 'i-lucide-settings' }
])
</script>
Controlled State
<template>
<div>
<UDropdownMenu v-model:open="isOpen" :items="menuItems">
<template #default>
<UButton>{{ isOpen ? 'Close' : 'Open' }} Menu</UButton>
</template>
</UDropdownMenu>
<UButton @click="isOpen = !isOpen" variant="outline">
Toggle from Outside
</UButton>
</div>
</template>
<script setup>
const isOpen = ref(false)
const menuItems = [
{ label: 'Item 1' },
{ label: 'Item 2' },
{ label: 'Item 3' }
]
</script>
Content Props
The content prop accepts the following positioning options:
side: 'top' | 'right' | 'bottom' | 'left' - Which side to position the menu
sideOffset: number - Distance in pixels from the trigger
align: 'start' | 'center' | 'end' - Alignment along the side
alignOffset: number - Offset along the alignment axis
collisionPadding: number | Partial<Record<'top' | 'right' | 'bottom' | 'left', number>> - Padding from viewport edges
avoidCollisions: boolean - Whether to adjust position to avoid collisions
loop: boolean - Whether keyboard navigation should loop