DropdownMenu
Usage
Use a Button or any other component in the default slot of the DropdownMenu.
Items
Use the items
prop as an array of objects with the following properties:
label?: string
icon?: string
color?: string
avatar?: AvatarProps
kbds?: string[] | KbdProps[]
type?: "link" | "label" | "separator" | "checkbox"
color?: "error" | "primary" | "secondary" | "success" | "info" | "warning" | "neutral"
checked?: boolean
disabled?: boolean
class?: any
slot?: string
onSelect?(e: Event): void
onUpdateChecked?(checked: boolean): void
You can also pass any property from the Link component such as to
, target
, etc.
<script setup lang="ts">
const items = ref([
[
{
label: 'Benjamin',
avatar: {
src: 'https://github.com/benjamincanac.png'
},
type: 'label'
}
],
[
{
label: 'Profile',
icon: 'i-lucide-user'
},
{
label: 'Billing',
icon: 'i-lucide-credit-card'
},
{
label: 'Settings',
icon: 'i-lucide-cog',
kbds: [',']
},
{
label: 'Keyboard shortcuts',
icon: 'i-lucide-monitor'
}
],
[
{
label: 'Team',
icon: 'i-lucide-users'
},
{
label: 'Invite users',
icon: 'i-lucide-user-plus',
children: [
[
{
label: 'Email',
icon: 'i-lucide-mail'
},
{
label: 'Message',
icon: 'i-lucide-message-square'
}
],
[
{
label: 'More',
icon: 'i-lucide-circle-plus'
}
]
]
},
{
label: 'New team',
icon: 'i-lucide-plus',
kbds: ['meta', 'n']
}
],
[
{
label: 'GitHub',
icon: 'i-simple-icons-github',
to: 'https://github.com/nuxt/ui',
target: '_blank'
},
{
label: 'Support',
icon: 'i-lucide-life-buoy',
to: '/components/dropdown-menu'
},
{
label: 'API',
icon: 'i-lucide-cloud',
disabled: true
}
],
[
{
label: 'Logout',
icon: 'i-lucide-log-out',
kbds: ['shift', 'meta', 'q']
}
]
])
</script>
<template>
<UDropdownMenu
:items="items"
:ui="{
content: 'w-48'
}"
>
<UButton icon="i-lucide-menu" color="neutral" variant="outline" />
</UDropdownMenu>
</template>
items
prop to create separated groups of items.children
array of objects with the same properties as the items
prop to create a nested menu which can be controlled using the open
, defaultOpen
and content
properties.Content
Use the content
prop to control how the DropdownMenu content is rendered, like its align
or side
for example.
<script setup lang="ts">
const items = ref([
{
label: 'Profile',
icon: 'i-lucide-user'
},
{
label: 'Billing',
icon: 'i-lucide-credit-card'
},
{
label: 'Settings',
icon: 'i-lucide-cog'
}
])
</script>
<template>
<UDropdownMenu
:items="items"
:content="{
align: 'start',
side: 'bottom',
sideOffset: 8
}"
:ui="{
content: 'w-48'
}"
>
<UButton label="Open" icon="i-lucide-menu" color="neutral" variant="outline" />
</UDropdownMenu>
</template>
Arrow
Use the arrow
prop to display an arrow on the DropdownMenu.
<script setup lang="ts">
const items = ref([
{
label: 'Profile',
icon: 'i-lucide-user'
},
{
label: 'Billing',
icon: 'i-lucide-credit-card'
},
{
label: 'Settings',
icon: 'i-lucide-cog'
}
])
</script>
<template>
<UDropdownMenu
arrow
:items="items"
:ui="{
content: 'w-48'
}"
>
<UButton label="Open" icon="i-lucide-menu" color="neutral" variant="outline" />
</UDropdownMenu>
</template>
Size
Use the size
prop to control the size of the DropdownMenu.
<script setup lang="ts">
const items = ref([
{
label: 'Profile',
icon: 'i-lucide-user'
},
{
label: 'Billing',
icon: 'i-lucide-credit-card'
},
{
label: 'Settings',
icon: 'i-lucide-cog'
}
])
</script>
<template>
<UDropdownMenu
size="xl"
:items="items"
:content="{
align: 'start'
}"
:ui="{
content: 'w-48'
}"
>
<UButton size="xl" label="Open" icon="i-lucide-menu" color="neutral" variant="outline" />
</UDropdownMenu>
</template>
size
prop will not be proxied to the Button, you need to set it yourself.Disabled
Use the disabled
prop to disable the DropdownMenu.
<script setup lang="ts">
const items = ref([
{
label: 'Profile',
icon: 'i-lucide-user'
},
{
label: 'Billing',
icon: 'i-lucide-credit-card'
},
{
label: 'Settings',
icon: 'i-lucide-cog'
}
])
</script>
<template>
<UDropdownMenu
disabled
:items="items"
:ui="{
content: 'w-48'
}"
>
<UButton label="Open" icon="i-lucide-menu" color="neutral" variant="outline" />
</UDropdownMenu>
</template>
Examples
With checkbox items
You can use the type
property with checkbox
and use the checked
/ onUpdateChecked
properties to control the checked state of the item.
<script setup lang="ts">
const showBookmarks = ref(true)
const showHistory = ref(false)
const showDownloads = ref(false)
const items = computed(() => [{
label: 'Interface',
icon: 'i-lucide-app-window',
type: 'label' as const
}, {
type: 'separator' as const
}, {
label: 'Show Bookmarks',
icon: 'i-lucide-bookmark',
type: 'checkbox' as const,
checked: showBookmarks.value,
onUpdateChecked(checked: boolean) {
showBookmarks.value = checked
},
onSelect(e: Event) {
e.preventDefault()
}
}, {
label: 'Show History',
icon: 'i-lucide-clock',
type: 'checkbox' as const,
checked: showHistory.value,
onUpdateChecked(checked: boolean) {
showHistory.value = checked
}
}, {
label: 'Show Downloads',
icon: 'i-lucide-download',
type: 'checkbox' as const,
checked: showDownloads.value,
onUpdateChecked(checked: boolean) {
showDownloads.value = checked
}
}])
</script>
<template>
<UDropdownMenu :items="items" :content="{ align: 'start' }" :ui="{ content: 'w-48' }">
<UButton label="Open" color="neutral" variant="outline" icon="i-lucide-menu" />
</UDropdownMenu>
</template>
checked
state of items, it's recommended to wrap your items
array inside a computed
.With color items
You can use the color
property to highlight certain items with a color.
<script setup lang="ts">
const items = [
[
{
label: 'View',
icon: 'i-lucide-eye'
},
{
label: 'Copy',
icon: 'i-lucide-copy'
},
{
label: 'Edit',
icon: 'i-lucide-pencil'
}
],
[
{
label: 'Delete',
color: 'error' as const,
icon: 'i-lucide-trash'
}
]
]
</script>
<template>
<UDropdownMenu :items="items" :ui="{ content: 'w-48' }">
<UButton label="Open" color="neutral" variant="outline" icon="i-lucide-menu" />
<template #profile-trailing>
<UIcon name="i-lucide-badge-check" class="shrink-0 size-5 text-[var(--ui-primary)]" />
</template>
</UDropdownMenu>
</template>
Control open state
You can control the open state by using the default-open
prop or the v-model:open
directive.
<script setup lang="ts">
const open = ref(false)
defineShortcuts({
o: () => open.value = !open.value
})
const items = [{
label: 'Profile',
icon: 'i-lucide-user'
}, {
label: 'Billing',
icon: 'i-lucide-credit-card'
}, {
label: 'Settings',
icon: 'i-lucide-cog'
}]
</script>
<template>
<UDropdownMenu v-model:open="open" :items="items" :ui="{ content: 'w-48' }">
<UButton label="Open" color="neutral" variant="outline" icon="i-lucide-menu" />
</UDropdownMenu>
</template>
With custom slot
Use the slot
property to customize a specific item.
You will have access to the following slots:
#{{ item.slot }}
#{{ item.slot }}-leading
#{{ item.slot }}-label
#{{ item.slot }}-trailing
<script setup lang="ts">
const items = [{
label: 'Profile',
icon: 'i-lucide-user',
slot: 'profile'
}, {
label: 'Billing',
icon: 'i-lucide-credit-card'
}, {
label: 'Settings',
icon: 'i-lucide-cog'
}]
</script>
<template>
<UDropdownMenu :items="items" :ui="{ content: 'w-48' }">
<UButton label="Open" color="neutral" variant="outline" icon="i-lucide-menu" />
<template #profile-trailing>
<UIcon name="i-lucide-badge-check" class="shrink-0 size-5 text-[var(--ui-primary)]" />
</template>
</UDropdownMenu>
</template>
#item
, #item-leading
, #item-label
and #item-trailing
slots to customize all items.Extract shortcuts
When you have some items with kbds
property (displaying some Kbd), you can easily make them work with the defineShortcuts composable.
Inside the defineShortcuts
composable, there is an extractShortcuts
utility that will extract the shortcuts recursively from the items and return an object that you can pass to defineShortcuts
. It will automatically call the select
function of the item when the shortcut is pressed.
<script setup lang="ts">
const items = [{
label: 'Invite users',
icon: 'i-lucide-user-plus',
children: [{
label: 'Invite by email',
icon: 'i-lucide-send-horizontal',
kbds: ['meta', 'e'],
onSelect() {
console.log('Invite by email clicked')
}
}, {
label: 'Invite by link',
icon: 'i-lucide-link',
kbds: ['meta', 'i'],
onSelect() {
console.log('Invite by link clicked')
}
}]
}, {
label: 'New team',
icon: 'i-lucide-plus',
kbds: ['meta', 'n'],
onSelect() {
console.log('New team clicked')
}
}]
defineShortcuts(extractShortcuts(items))
</script>
select
function of the corresponding item.API
Props
Prop | Default | Type |
---|---|---|
size |
|
|
items |
| |
checkedIcon |
|
The icon displayed when an item is checked. |
loadingIcon |
|
The icon displayed when an item is loading. |
content |
|
The content of the menu. |
arrow |
|
Display an arrow alongside the menu. |
portal |
|
Render the menu in a portal. |
labelKey |
|
The key used to get the label from the item. |
disabled |
| |
defaultOpen |
The open state of the dropdown menu when it is initially rendered. Use when you do not need to control its open state. | |
open |
The controlled open state of the menu. Can be used as | |
modal |
|
The modality of the dropdown menu. When set to |
ui |
|
Slots
Slot | Type |
---|---|
default |
|
item |
|
item-leading |
|
item-label |
|
item-trailing |
|
Emits
Event | Type |
---|---|
update:open |
|
Theme
export default defineAppConfig({
ui: {
dropdownMenu: {
slots: {
content: 'min-w-32 bg-[var(--ui-bg)] shadow-lg rounded-[calc(var(--ui-radius)*1.5)] ring ring-[var(--ui-border)] divide-y divide-[var(--ui-border)] overflow-y-auto scroll-py-1 data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in]',
arrow: 'fill-[var(--ui-border)]',
group: 'p-1 isolate',
label: 'w-full flex items-center font-semibold text-[var(--ui-text-highlighted)]',
separator: '-mx-1 my-1 h-px bg-[var(--ui-border)]',
item: 'group relative w-full flex items-center select-none outline-none before:absolute before:z-[-1] before:inset-px before:rounded-[calc(var(--ui-radius)*1.5)] data-disabled:cursor-not-allowed data-disabled:opacity-75',
itemLeadingIcon: 'shrink-0',
itemLeadingAvatar: 'shrink-0',
itemLeadingAvatarSize: '',
itemTrailing: 'ms-auto inline-flex',
itemTrailingIcon: 'shrink-0',
itemTrailingKbds: 'hidden lg:inline-flex items-center shrink-0',
itemTrailingKbdsSize: '',
itemLabel: 'truncate',
itemLabelExternalIcon: 'inline-block size-3 align-top text-[var(--ui-text-dimmed)]'
},
variants: {
color: {
primary: '',
secondary: '',
success: '',
info: '',
warning: '',
error: '',
neutral: ''
},
active: {
true: {
item: 'text-[var(--ui-text-highlighted)] before:bg-[var(--ui-bg-elevated)]',
itemLeadingIcon: 'text-[var(--ui-text)]'
},
false: {
item: [
'text-[var(--ui-text)] data-highlighted:text-[var(--ui-text-highlighted)] data-[state=open]:text-[var(--ui-text-highlighted)] data-highlighted:before:bg-[var(--ui-bg-elevated)]/50 data-[state=open]:before:bg-[var(--ui-bg-elevated)]/50',
'transition-colors before:transition-colors'
],
itemLeadingIcon: [
'text-[var(--ui-text-dimmed)] group-data-highlighted:text-[var(--ui-text)] group-data-[state=open]:text-[var(--ui-text)]',
'transition-colors'
]
}
},
loading: {
true: {
itemLeadingIcon: 'animate-spin'
}
},
size: {
xs: {
label: 'p-1 text-xs gap-1',
item: 'p-1 text-xs gap-1',
itemLeadingIcon: 'size-4',
itemLeadingAvatarSize: '3xs',
itemTrailingIcon: 'size-4',
itemTrailingKbds: 'gap-0.5',
itemTrailingKbdsSize: 'sm'
},
sm: {
label: 'p-1.5 text-xs gap-1.5',
item: 'p-1.5 text-xs gap-1.5',
itemLeadingIcon: 'size-4',
itemLeadingAvatarSize: '3xs',
itemTrailingIcon: 'size-4',
itemTrailingKbds: 'gap-0.5',
itemTrailingKbdsSize: 'sm'
},
md: {
label: 'p-1.5 text-sm gap-1.5',
item: 'p-1.5 text-sm gap-1.5',
itemLeadingIcon: 'size-5',
itemLeadingAvatarSize: '2xs',
itemTrailingIcon: 'size-5',
itemTrailingKbds: 'gap-0.5',
itemTrailingKbdsSize: 'md'
},
lg: {
label: 'p-2 text-sm gap-2',
item: 'p-2 text-sm gap-2',
itemLeadingIcon: 'size-5',
itemLeadingAvatarSize: '2xs',
itemTrailingIcon: 'size-5',
itemTrailingKbds: 'gap-1',
itemTrailingKbdsSize: 'md'
},
xl: {
label: 'p-2 text-base gap-2',
item: 'p-2 text-base gap-2',
itemLeadingIcon: 'size-6',
itemLeadingAvatarSize: 'xs',
itemTrailingIcon: 'size-6',
itemTrailingKbds: 'gap-1',
itemTrailingKbdsSize: 'lg'
}
}
},
compoundVariants: [
{
color: 'primary',
active: false,
class: {
item: 'text-[var(--ui-primary)] data-highlighted:text-[var(--ui-primary)] data-highlighted:before:bg-[var(--ui-primary)]/10 data-[state=open]:before:bg-[var(--ui-primary)]/10',
itemLeadingIcon: 'text-[var(--ui-primary)]/75 group-data-highlighted:text-[var(--ui-primary)] group-data-[state=open]:text-[var(--ui-primary)]'
}
},
{
color: 'primary',
active: true,
class: {
item: 'text-[var(--ui-primary)] before:bg-[var(--ui-primary)]/10',
itemLeadingIcon: 'text-[var(--ui-primary)]'
}
}
],
defaultVariants: {
size: 'md'
}
}
}
})