n-dropdown

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.

SKILL.md

GitHub

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

  1. Use appropriate trigger: Choose hover or click based on use case

    • Hover: Quick access menus
    • Click: Action menus, forms
    • Manual: Context menus
  2. Unique keys: Ensure each option has a unique key

    { label: 'Edit', key: 'edit' }
    
  3. Group related options: Use dividers and groups

    [
      { label: 'Edit', key: 'edit' },
      { type: 'divider', key: 'd1' },
      { label: 'Delete', key: 'delete' }
    ]
    
  4. Disable unavailable options: Use disabled for unavailable actions

    { label: 'Delete', key: 'delete', disabled: true }
    
  5. Context menu pattern: Always handle clickoutside for manual triggers

    <n-dropdown
      trigger="manual"
      :on-clickoutside="() => show = false"
    />
    
  6. Keyboard accessibility: Keep keyboard enabled for accessibility

    <n-dropdown keyboard :options="options">
    
  7. Icons for clarity: Add icons to improve visual recognition

    { label: 'Delete', key: 'delete', icon: () => h(TrashIcon) }