| name | n-dropdown |
| description | Dropdown component for displaying contextual menus and action lists. Invoke when user needs to implement dropdown menus, context menus, or cascading selections in Naive UI. |
| metadata | {"author":"jiaiyan","version":"1.0.0"} |
Naive UI Dropdown Component
Dropdown component for displaying contextual menus, action lists, and cascading selections.
When to Use
Use this component when:
- Context menus: Right-click or action-triggered menus
- Navigation menus: Dropdown navigation items
- Action lists: Lists of actions for an item
- User menus: Profile and settings dropdowns
When to Invoke
Invoke this skill when:
- User needs to implement a dropdown menu
- User wants cascading (nested) menus
- User needs context menu (right-click menu)
- User wants to customize option rendering
- User asks about different trigger modes (hover, click, manual)
- User needs to add icons or tooltips to options
Features
- Multiple Triggers: Hover, click, or manual control
- Cascading Menus: Support for nested submenus
- Custom Rendering: Full control over option rendering
- Context Menu: Manual positioning for right-click menus
- Icons Support: Add icons to menu options
- Grouping: Group related options together
- Keyboard Navigation: Full keyboard support
- Placement Control: Configurable popup placement
API Reference
Dropdown Props
| Name | Type | Default | Description |
|---|
| animated | boolean | true | Use animation when showing options. |
| inverted | boolean | false | Use inverted style. |
| children-field | string | 'children' | Field name of children. |
| keyboard | boolean | true | Whether keyboard operation is supported. |
| key-field | string | 'key' | Field name of key. |
| label-field | string | 'label' | Field name of label. |
| node-props | (option) => HTMLAttributes | undefined | Option HTML attributes generator. |
| menu-props | (option, options) => HTMLAttributes | undefined | Menu HTML attributes generator. |
| options | Array<DropdownOption> | [] | Dropdown options. |
| render-icon | (option) => VNodeChild | undefined | Render function for option icons. |
| render-label | (option) => VNodeChild | undefined | Render function for option labels. |
| render-option | ({ node, option }) => VNodeChild | undefined | Render function for option itself. |
| size | 'small' | 'medium' | 'large' | 'huge' | 'medium' | Dropdown size. |
| on-clickoutside | (e: MouseEvent) => void | undefined | Callback when click outside. |
| on-select | (key, option) => void | undefined | Callback when option is selected. |
DropdownOption Type
| Property | Type | Description |
|---|
| children? | Array<DropdownOption> | Child options (for cascading). |
| disabled? | boolean | Whether to disable the option. |
| icon? | () => VNodeChild | Custom render function for icon. |
| key? | string | number | Option ID (should be unique). |
| label? | string | () => VNodeChild | Displayed label value. |
| props? | HTMLAttributes | Customize option props. |
| show? | boolean | Whether to show the option. |
DropdownDividerOption Type
| Property | Type | Description |
|---|
| key? | string | number | Divider ID. |
| show? | boolean | Whether to show. |
| type | 'divider' | The type identifier. |
DropdownGroupOption Type
| Property | Type | Description |
|---|
| children? | Array<DropdownOption> | Children options. |
| icon? | () => VNodeChild | Custom icon render. |
| label? | string | Group label. |
| key? | string | number | Group ID. |
| show? | boolean | Whether to show. |
| type | 'group' | The type identifier. |
DropdownRenderOption Type
| Property | Type | Description |
|---|
| key? | string | number | Render option ID. |
| render? | () => VNodeChild | Render function for content. |
| show? | boolean | Whether to show. |
| type | 'render' | The type identifier. |
Basic Usage
Basic Dropdown
<template>
<n-dropdown trigger="hover" :options="options" @select="handleSelect">
<n-button>Hover Me</n-button>
</n-dropdown>
</template>
<script setup>
const options = [
{ label: 'Edit', key: 'edit' },
{ label: 'Delete', key: 'delete' },
{ label: 'Share', key: 'share' },
];
const handleSelect = key => {
console.log(`Selected: ${key}`);
};
</script>
Click Trigger
<template>
<n-dropdown trigger="click" :options="options" @select="handleSelect">
<n-button>Click Me</n-button>
</n-dropdown>
</template>
Dropdown with Icons
<template>
<n-dropdown :options="options">
<n-button>User Profile</n-button>
</n-dropdown>
</template>
<script setup>
import { h } from 'vue';
import {
PersonOutline,
SettingsOutline,
LogOutOutline,
} from '@vicons/ionicons5';
const options = [
{
label: 'Profile',
key: 'profile',
icon: () => h(PersonOutline),
},
{
label: 'Settings',
key: 'settings',
icon: () => h(SettingsOutline),
},
{
type: 'divider',
key: 'd1',
},
{
label: 'Logout',
key: 'logout',
icon: () => h(LogOutOutline),
},
];
</script>
Common Patterns
Cascading Menu
<template>
<n-dropdown
:options="options"
placement="bottom-start"
trigger="click"
@select="handleSelect"
>
<n-button>Menu</n-button>
</n-dropdown>
</template>
<script setup>
const options = [
{
label: 'File',
key: 'file',
children: [
{ label: 'New', key: 'new' },
{ label: 'Open', key: 'open' },
{ type: 'divider', key: 'd1' },
{ label: 'Save', key: 'save' },
],
},
{
label: 'Edit',
key: 'edit',
children: [
{ label: 'Undo', key: 'undo' },
{ label: 'Redo', key: 'redo' },
],
},
];
</script>
Context Menu (Right-Click)
<template>
<div
style="width: 200px; height: 200px; background: rgba(0, 128, 0, 0.5)"
@contextmenu="handleContextMenu"
>
Right Click Here
</div>
<n-dropdown
placement="bottom-start"
trigger="manual"
:x="x"
:y="y"
:options="options"
:show="showDropdown"
:on-clickoutside="onClickoutside"
@update:show="showDropdown = $event"
@select="handleSelect"
/>
</template>
<script setup>
import { ref } from 'vue';
const showDropdown = ref(false);
const x = ref(0);
const y = ref(0);
const options = [
{ label: 'Copy', key: 'copy' },
{ label: 'Paste', key: 'paste' },
{ label: 'Delete', key: 'delete' },
];
const handleContextMenu = e => {
e.preventDefault();
showDropdown.value = false;
x.value = e.clientX;
y.value = e.clientY;
showDropdown.value = true;
};
const onClickoutside = () => {
showDropdown.value = false;
};
const handleSelect = () => {
showDropdown.value = false;
};
</script>
Grouped Options
<template>
<n-dropdown :options="options" trigger="click">
<n-button>Grouped Menu</n-button>
</n-dropdown>
</template>
<script setup>
const options = [
{
type: 'group',
label: 'Actions',
key: 'actions',
children: [
{ label: 'Edit', key: 'edit' },
{ label: 'Delete', key: 'delete' },
],
},
{
type: 'divider',
key: 'd1',
},
{
type: 'group',
label: 'Export',
key: 'export',
children: [
{ label: 'PDF', key: 'pdf' },
{ label: 'Excel', key: 'excel' },
],
},
];
</script>
Custom Render
<template>
<n-dropdown
:options="options"
:render-label="renderLabel"
:render-icon="renderIcon"
trigger="click"
>
<n-button>Custom Render</n-button>
</n-dropdown>
</template>
<script setup>
import { h } from 'vue';
import { NText } from 'naive-ui';
const options = [
{ label: 'Edit', key: 'edit' },
{ label: 'Delete', key: 'delete', disabled: true },
];
const renderLabel = option => {
return h(
NText,
{ type: option.disabled ? 'disabled' : 'default' },
{
default: () => option.label,
},
);
};
const renderIcon = option => {
if (option.key === 'edit') {
return h('span', '✏️');
}
return null;
};
</script>
With Arrow
<template>
<n-dropdown
trigger="click"
:options="options"
:show-arrow="true"
@select="handleSelect"
>
<n-button>Dropdown with Arrow</n-button>
</n-dropdown>
</template>
Placement Control
<template>
<n-space>
<n-dropdown placement="bottom-start" :options="options" trigger="click">
<n-button>Bottom Start</n-button>
</n-dropdown>
<n-dropdown placement="bottom-end" :options="options" trigger="click">
<n-button>Bottom End</n-button>
</n-dropdown>
<n-dropdown placement="top" :options="options" trigger="click">
<n-button>Top</n-button>
</n-dropdown>
</n-space>
</template>
Different Sizes
<template>
<n-space>
<n-dropdown size="small" :options="options" trigger="click">
<n-button>Small</n-button>
</n-dropdown>
<n-dropdown size="medium" :options="options" trigger="click">
<n-button>Medium</n-button>
</n-dropdown>
<n-dropdown size="large" :options="options" trigger="click">
<n-button>Large</n-button>
</n-dropdown>
</n-space>
</template>
Add Tooltip for Option
<template>
<n-dropdown
trigger="hover"
:options="options"
:render-option="renderOption"
@select="handleSelect"
>
<n-button>With Tooltips</n-button>
</n-dropdown>
</template>
<script setup>
import { h } from 'vue';
import { NTooltip } from 'naive-ui';
const options = [
{ label: 'Edit', key: 'edit' },
{ label: 'Delete', key: 'delete' },
];
const renderOption = ({ node, option }) => {
return h(NTooltip, null, {
trigger: () => node,
default: () => `Click to ${option.label}`,
});
};
</script>
Pure Render Content
<template>
<n-dropdown trigger="hover" :options="options">
<n-button>Custom Content</n-button>
</n-dropdown>
</template>
<script setup>
import { h } from 'vue';
import { NText } from 'naive-ui';
const options = [
{
type: 'render',
key: 'render',
render: () =>
h(
NText,
{ type: 'primary' },
{ default: () => 'Custom rendered content' },
),
},
];
</script>
Best Practices
-
Use appropriate trigger: Choose hover or click based on use case
- Hover: Quick access menus
- Click: Action menus, forms
- Manual: Context menus
-
Unique keys: Ensure each option has a unique key
{ label: 'Edit', key: 'edit' }
-
Group related options: Use dividers and groups
[
{ label: 'Edit', key: 'edit' },
{ type: 'divider', key: 'd1' },
{ label: 'Delete', key: 'delete' },
];
-
Disable unavailable options: Use disabled for unavailable actions
{ label: 'Delete', key: 'delete', disabled: true }
-
Context menu pattern: Always handle clickoutside for manual triggers
<n-dropdown trigger="manual" :on-clickoutside="() => (show = false)" />
-
Keyboard accessibility: Keep keyboard enabled for accessibility
<n-dropdown keyboard :options="options">
-
Icons for clarity: Add icons to improve visual recognition
{ label: 'Delete', key: 'delete', icon: () => h(TrashIcon) }