๐ŸŸข Dropdown Menu


  • Can be controlled or uncontrolled.
  • Supports submenus with configurable reading direction.
  • Supports items, labels, groups of items.
  • Supports checkable items (single or multiple) with optional indeterminate state.
  • Supports modal and non-modal modes.
  • Customize side, alignment, offsets, collision handling.
  • Optionally render a pointing arrow.
  • Focus is fully managed.
  • Full keyboard navigation.
  • Typeahead support.
  • Dismissing and layering behavior is highly customizable.

Basic

NDropdownMenu is a component that can be used to display a list of actions or options.

PropTypeDefaultDescription
itemsDropdownMenuItemProps[][]The items to display in the dropdown-menu.
labelstringundefinedThe label to display in the dropdown-menu.
All the props available in the Radix Vue Dropdown Menu are also available via its subcomponents' prop names, e.g., _dropdown-menu-item, _dropdown-menu-trigger, etc. refer to DropdownMenu Props for more details.
<script setup lang="ts">
const items = [
  {
    label: 'Profile',
    shortcut: 'โ‡งโŒ˜P',
    onclick: () => {
      // eslint-disable-next-line no-alert
      alert('Profile clicked')
    },
  },
  {
    label: 'Billing',
    shortcut: 'โŒ˜B',
  },
  {
    label: 'Settings',
    shortcut: 'โŒ˜S',
  },
  {
    label: 'Keyboard shortcuts',
    shortcut: 'โŒ˜K',
  },
  {}, // to add a separator between items (label or items should be null).
  {
    label: 'Teams',
  },
  {
    label: 'Invite users',
    items: [
      {
        label: 'Email',
        shortcut: 'โŒ˜E',
      },
      {
        label: 'Message',
        shortcut: 'โŒ˜M',
      },
      {},
      {
        label: 'More',
        items: [
          {
            label: 'Slack',
            shortcut: 'โŒ˜S',
          },
          {
            label: 'Discord',
            shortcut: 'โŒ˜D',
          },
          {},
          {
            label: 'More',
            items: [
              {
                label: 'Telegram',
                shortcut: 'โŒ˜T',
              },
              {
                label: 'WhatsApp',
                shortcut: 'โŒ˜W',
              },
            ],
          },
        ],
      },
    ],
  },
  {
    label: 'New team',
    shortcut: 'โŒ˜T',
  },
  {},
  {
    label: 'GitHub',
    items: [
      {
        label: 'Personal',
        shortcut: 'โŒ˜P',
      },
      {
        label: 'Organization',
        shortcut: 'โŒ˜O',
      },
    ],
  },
  {
    label: 'Support',
  },
  {
    label: 'API',
    disabled: true,
  },
  {},
  {
    label: 'Logout',
    shortcut: 'โ‡งโŒ˜Q',
  },
]
</script>

<template>
  <div class="grid h-50 place-items-center">
    <NDropdownMenu
      :items
      menu-label="My Account"
      :_dropdown-menu-content="{
        class: 'w-52',
        align: 'end',
        side: 'bottom',
      }"
      label="Open"
    />
  </div>
</template>

Inset

inset prop is used to set the dropdown-menu to be inset.

<script setup lang="ts">
const items = [
  {
    label: 'Profile',
    shortcut: 'โ‡งโŒ˜P',
    onclick: () => {
      // eslint-disable-next-line no-alert
      alert('Profile clicked')
    },
    leading: 'i-lucide-user',
  },
  {
    label: 'Billing',
    shortcut: 'โŒ˜B',
    leading: 'i-lucide-banknote',
  },
  {
    label: 'Settings',
    shortcut: 'โŒ˜S',
    leading: 'i-lucide-settings',
  },
  {
    label: 'Keyboard shortcuts',
    shortcut: 'โŒ˜K',
    leading: 'i-lucide-keyboard',
  },
  {}, // to add a separator between items (label or items should be null).
  {
    label: 'Teams',
    leading: 'i-lucide-users',
  },
  {
    label: 'Invite users',
    leading: 'i-lucide-plus',
    items: [
      {
        label: 'Email',
        shortcut: 'โŒ˜E',
        leading: 'i-lucide-mail',
      },
      {
        label: 'Message',
        shortcut: 'โŒ˜M',
        leading: 'i-lucide-message-circle',
      },
      {},
      {
        label: 'More',
        leading: 'i-lucide-more-horizontal',
        items: [
          {
            label: 'Slack',
            shortcut: 'โŒ˜S',
            leading: 'i-logos-slack-icon',
          },
          {
            label: 'Discord',
            shortcut: 'โŒ˜D',
            leading: 'i-logos-discord-icon',
          },
          {
            label: 'Telegram',
            shortcut: 'โŒ˜T',
            leading: 'i-logos-telegram',
          },
          {
            label: 'WhatsApp',
            shortcut: 'โŒ˜W',
            leading: 'i-logos-whatsapp-icon',
          },
        ],
      },
    ],
  },
  {
    label: 'New team',
    shortcut: 'โŒ˜T',
  },
  {},
  {
    label: 'GitHub',
    items: [
      {
        label: 'Personal',
        shortcut: 'โŒ˜P',
      },
      {
        label: 'Organization',
        shortcut: 'โŒ˜O',
      },
    ],
  },
  {
    label: 'Support',
  },
  {
    label: 'API',
    disabled: true,
  },
  {},
  {
    label: 'Logout',
    shortcut: 'โ‡งโŒ˜Q',
  },
]
</script>

<template>
  <div class="grid h-50 place-items-center">
    <NDropdownMenu
      :items
      menu-label="My Account"
      :_dropdown-menu-content="{
        class: 'w-60',
      }"
      inset
      label="Open"
    />
  </div>
</template>

Variant and Color

dropdown-menu="{variant}-{color}" is used to set the variant of the dropdown-menu. The default variant is soft-black.

dropdown-menu-item="{color}" is used to set the variant of the dropdown-menu item. The default variant is soft-black.

PropDescription
dropdown-menuSet the dropdown-menu variant and color.
_dropdown-menu-trigger.dropdown-menuSet the dropdown-menu variant and color via _dropdown-menu-trigger.
dropdown-menu-itemSet the dropdown-menu item variant and color.
_dropdown-menu-item.dropdown-menu-itemSet the dropdown-menu item variant and color via _dropdown-menu-item.
NDropdownMenuTrigger is wrapped around the NButton component. This means that all the props and slots of NButton are available to use or through _dropdown-menu-trigger prop.
<script setup lang="ts">
const items = [
  {
    label: 'Profile',
    shortcut: 'โ‡งโŒ˜P',
    onclick: () => {
      // eslint-disable-next-line no-alert
      alert('Profile clicked')
    },
  },
  {
    label: 'Billing',
    shortcut: 'โŒ˜B',
  },
  {
    label: 'Settings',
    shortcut: 'โŒ˜S',
  },
  {
    label: 'Keyboard shortcuts',
    shortcut: 'โŒ˜K',
  },
  {}, // to add a separator between items (label or items should be null).
  {
    label: 'Teams',
  },
  {
    label: 'Invite users',
    items: [
      {
        label: 'Email',
        shortcut: 'โŒ˜E',
      },
      {
        label: 'Message',
        shortcut: 'โŒ˜M',
      },
      {},
      {
        label: 'More',
        items: [
          {
            label: 'Slack',
            shortcut: 'โŒ˜S',
          },
          {
            label: 'Discord',
            shortcut: 'โŒ˜D',
          },
          {},
          {
            label: 'More',
            items: [
              {
                label: 'Telegram',
                shortcut: 'โŒ˜T',
              },
              {
                label: 'WhatsApp',
                shortcut: 'โŒ˜W',
              },
            ],
          },
        ],
      },
    ],
  },
  {
    label: 'New team',
    shortcut: 'โŒ˜T',
  },
  {},
  {
    label: 'GitHub',
    items: [
      {
        label: 'Personal',
        shortcut: 'โŒ˜P',
      },
      {
        label: 'Organization',
        shortcut: 'โŒ˜O',
      },
    ],
  },
  {
    label: 'Support',
  },
  {
    label: 'API',
    disabled: true,
  },
  {},
  {
    label: 'Logout',
    shortcut: 'โ‡งโŒ˜Q',
  },
]
</script>

<template>
  <div class="h-50 flex items-center justify-around">
    <NDropdownMenu
      :items
      dropdown-menu="ghost-pink"
      dropdown-menu-item="pink"
      menu-label="My Account"
      :_dropdown-menu-content="{
        class: 'w-52',
      }"
      label="Open"
    />

    <NDropdownMenu
      :items
      dropdown-menu="outline-gray"
      dropdown-menu-item="gray"
      menu-label="My Account"
      :_dropdown-menu-content="{
        class: 'w-52',
      }"
      label="Open"
    />

    <NDropdownMenu
      :items
      dropdown-menu="solid-primary"
      dropdown-menu-item="primary"
      menu-label="My Account"
      :_dropdown-menu-content="{
        class: 'w-52',
      }"
      label="Open"
    />
  </div>
</template>

Size

PropDescription
sizeSet the dropdown-menu general size.
_dropdownMenuTrigger.sizeSet the trigger size only.
_dropdownMenuItem.sizeSet the item size only.
_dropdownMenuLabel.sizeSet the menu label size only.

๐Ÿš€ You can freely adjust the size of the dropdown-menu using any size imaginable. No limits exist, and you can use breakpoints such as sm:sm, xs:lg to change size based on screen size or states such as hover:lg, focus:3xl to change size based on input state and more.

The height and width of the dropdown-menu scale depends on the dropdown-menu-size. If you want to change the height and width simultaneously, you can always customize it using utility classes.
<script setup lang="ts">
const items = [
  {
    label: 'Profile',
    shortcut: 'โ‡งโŒ˜P',
    onclick: () => {
      // eslint-disable-next-line no-alert
      alert('Profile clicked')
    },
  },
  {
    label: 'Billing',
    shortcut: 'โŒ˜B',
  },
  {
    label: 'Settings',
    shortcut: 'โŒ˜S',
  },
  {
    label: 'Keyboard shortcuts',
    shortcut: 'โŒ˜K',
  },
  {}, // to add a separator between items (label or items should be null).
  {
    label: 'Teams',
  },
  {
    label: 'Invite users',
    items: [
      {
        label: 'Email',
        shortcut: 'โŒ˜E',
      },
      {
        label: 'Message',
        shortcut: 'โŒ˜M',
      },
      {},
      {
        label: 'More',
        items: [
          {
            label: 'Slack',
            shortcut: 'โŒ˜S',
          },
          {
            label: 'Discord',
            shortcut: 'โŒ˜D',
          },
          {},
          {
            label: 'More',
            items: [
              {
                label: 'Telegram',
                shortcut: 'โŒ˜T',
              },
              {
                label: 'WhatsApp',
                shortcut: 'โŒ˜W',
              },
            ],
          },
        ],
      },
    ],
  },
  {
    label: 'New team',
    shortcut: 'โŒ˜T',
  },
  {},
  {
    label: 'GitHub',
    items: [
      {
        label: 'Personal',
        shortcut: 'โŒ˜P',
      },
      {
        label: 'Organization',
        shortcut: 'โŒ˜O',
      },
    ],
  },
  {
    label: 'Support',
  },
  {
    label: 'API',
    disabled: true,
  },
  {},
  {
    label: 'Logout',
    shortcut: 'โ‡งโŒ˜Q',
  },
]
</script>

<template>
  <div class="h-50 flex items-center justify-around">
    <NDropdownMenu
      :items
      size="xs"
      menu-label="My Account"
      :_dropdown-menu-content="{
        class: 'w-52',
      }"
      label="Open XS"
    />

    <NDropdownMenu
      :items
      size="sm"
      menu-label="My Account"
      :_dropdown-menu-content="{
        class: 'w-52',
      }"
      label="Open SM/Default"
    />

    <NDropdownMenu
      :items
      size="md"
      menu-label="My Account"
      :_dropdown-menu-content="{
        class: 'w-52',
      }"
      label="Open MD"
    />

    <NDropdownMenu
      :items
      size="lg"
      menu-label="My Account"
      :_dropdown-menu-content="{
        class: 'w-52',
      }"
      label="Open LG"
    />
  </div>
</template>

Slots

You can use the following slots to customize the dropdown-menu.

NameDescriptionProps
triggerThe trigger slot.-
itemThe item slot.item
sub-triggerThe sub-trigger slot.-
contentThe content slot.items
labelThe label slot.label
groupThe group slot.items
<template>
  <div class="">
    <NDropdownMenu
      :modal="false"
      :_dropdown-menu-content="{
        class: 'w-60',
        align: 'start',
        side: 'right',
      }"
    >
      <template #trigger>
        <NDropdownMenuTrigger
          dropdown-menu="data-[state=closed]:soft-gray data-[state=open]:solid-gray"
          class="rounded-full p-1"
        >
          <NAvatar
            src="https://avatars.githubusercontent.com/u/499550?v=4"
          />
        </NDropdownMenuTrigger>
      </template>

      <template #menu-label>
        <div class="flex flex-col items-start">
          <span class="text-accent font-semibold leading-5">Evan You</span>
          <span class="text-xs text-muted">evan@vuejs.org</span>
        </div>
      </template>

      <template #items>
        <NDropdownMenuGroup>
          <NDropdownMenuItem
            dropdown-menu-item="primary"
            label="Join or create a workspace"
            leading="i-lucide-plus"
          />

          <NDropdownMenuSeparator />

          <NDropdownMenuSub>
            <NDropdownMenuSubTrigger
              inset
              dropdown-menu-item="primary"
            >
              <span>Invite users</span>
            </NDropdownMenuSubTrigger>
            <NDropdownMenuPortal>
              <NDropdownMenuSubContent>
                <NDropdownMenuItem
                  dropdown-menu-item="primary"
                >
                  <span>Email</span>
                </NDropdownMenuItem>
                <NDropdownMenuItem
                  dropdown-menu-item="primary"
                >
                  <span>Message</span>
                </NDropdownMenuItem>
                <NDropdownMenuSeparator />
                <NDropdownMenuItem
                  dropdown-menu-item="primary"
                >
                  <span>More...</span>
                </NDropdownMenuItem>
              </NDropdownMenuSubContent>
            </NDropdownMenuPortal>
          </NDropdownMenuSub>
        </NDropdownMenuGroup>

        <NDropdownMenuSeparator />

        <NDropdownMenuGroup>
          <NDropdownMenuItem
            dropdown-menu-item="primary"
            label="Profile"
            leading="i-lucide-user-round"
            shortcut="โŒ˜P"
          />
          <NDropdownMenuItem
            dropdown-menu-item="primary"
            label="Settings"
            leading="i-lucide-settings"
            shortcut="โŒ˜S"
          />
        </NDropdownMenuGroup>

        <NDropdownMenuSeparator />

        <NDropdownMenuGroup>
          <NDropdownMenuItem
            dropdown-menu-item="primary"
            label="Logout"
            leading="i-lucide-log-out"
            shortcut="โŒ˜L"
          />
        </NDropdownMenuGroup>
      </template>
    </NDropdownMenu>
  </div>
</template>

Props

import type {
  DropdownMenuContentProps,
  DropdownMenuGroupProps,
  DropdownMenuLabelProps,
  DropdownMenuRootProps,
  DropdownMenuSeparatorProps,
  DropdownMenuSubContentProps,
  DropdownMenuSubTriggerProps,
  DropdownMenuTriggerProps,
} from 'radix-vue'
import type { HTMLAttributes } from 'vue'
import type { NButtonProps } from './button'
import type { NSeparatorProps } from './separator'

/**
 * Base extensions for dropdown menu components.
 */
interface BaseExtensions {
  /** CSS class for the component */
  class?: HTMLAttributes['class']
  /** Size of the component */
  size?: HTMLAttributes['class']
}

/**
 * Props for the NDropdownMenu component.
 */
export interface NDropdownMenuProps extends
  Omit<NDropdownMenuRootProps, 'class' | 'size'>,
  Omit<NDropdownMenuTriggerProps, 'una'>,
  Pick<NDropdownMenuItemProps, 'shortcut' | 'dropdownMenuItem'> {
  /** Label for the menu */
  menuLabel?: string
  /** Items in the dropdown menu */
  items?: NDropdownMenuProps[]
  /** Whether the menu is inset */
  inset?: boolean

  // Subcomponents
  /** Props for the dropdown menu root */
  _dropdownMenuRoot?: Partial<NDropdownMenuRootProps>
  /** Props for the dropdown menu item */
  _dropdownMenuItem?: Partial<NDropdownMenuItemProps>
  /** Props for the dropdown menu trigger */
  _dropdownMenuTrigger?: Partial<NDropdownMenuTriggerProps>
  /** Props for the dropdown menu content */
  _dropdownMenuContent?: Partial<NDropdownMenuContentProps>
  /** Props for the dropdown menu sub-content */
  _dropdownMenuSubContent?: Partial<NDropdownMenuSubContentProps>
  /** Props for the dropdown menu label */
  _dropdownMenuLabel?: Partial<NDropdownMenuLabelProps>
  /** Props for the dropdown menu separator */
  _dropdownMenuSeparator?: Partial<NDropdownMenuSeparatorProps>
  /** Props for the dropdown menu group */
  _dropdownMenuGroup?: Partial<NDropdownMenuGroupProps>
  /** Props for the dropdown menu sub-trigger */
  _dropdownMenuSubTrigger?: Partial<NDropdownMenuSubTriggerProps>

  /** Additional properties for the una component */
  una?: NDropdownMenuUnaProps & NButtonProps['una']
}

/**
 * Props for the NDropdownMenuRoot component.
 */
export interface NDropdownMenuRootProps extends BaseExtensions, DropdownMenuRootProps {
  /** Additional properties for the una component */
  una?: NDropdownMenuUnaProps['dropdownMenuRoot']
}

/**
 * Props for the NDropdownMenuTrigger component.
 */
export interface NDropdownMenuTriggerProps extends NButtonProps, DropdownMenuTriggerProps {
  /** Additional properties for the una component */
  una?: NDropdownMenuUnaProps['dropdownMenuTrigger'] & NButtonProps['una']
}

/**
 * Props for the NDropdownMenuContent component.
 */
export interface NDropdownMenuContentProps extends BaseExtensions, DropdownMenuContentProps {
  /** Additional properties for the una component */
  una?: NDropdownMenuUnaProps['dropdownMenuContent']
}

/**
 * Props for the NDropdownMenuLabel component.
 */
export interface NDropdownMenuLabelProps extends BaseExtensions, DropdownMenuLabelProps {
  /** Whether the label is inset */
  inset?: boolean
  /** Size of the label */
  size?: HTMLAttributes['class']
  /** Additional properties for the una component */
  una?: NDropdownMenuUnaProps['dropdownMenuLabel']
}

/**
 * Props for the NDropdownMenuSeparator component.
 */
export interface NDropdownMenuSeparatorProps extends DropdownMenuSeparatorProps, NSeparatorProps {
  /** Additional properties for the una component */
  una?: NDropdownMenuUnaProps['dropdownMenuSeparator'] & NSeparatorProps['una']
}

/**
 * Props for the NDropdownMenuGroup component.
 */
export interface NDropdownMenuGroupProps extends BaseExtensions, DropdownMenuGroupProps {
  /** Additional properties for the una component */
  una?: NDropdownMenuUnaProps['dropdownMenuGroup']
}

/**
 * Props for the NDropdownMenuSubContent component.
 */
export interface NDropdownMenuSubContentProps extends BaseExtensions, DropdownMenuSubContentProps {
  /** Additional properties for the una component */
  una?: NDropdownMenuUnaProps['dropdownMenuSubContent']
}

/**
 * Props for the NDropdownMenuItem component.
 */
export interface NDropdownMenuItemProps extends NButtonProps {
  /** Dropdown menu item */
  dropdownMenuItem?: HTMLAttributes['class']
  /** Whether the item is inset */
  inset?: boolean
  /** Shortcut key for the item */
  shortcut?: string
  /** Additional properties for the una component */
  una?: NDropdownMenuUnaProps['dropdownMenuItem'] & NButtonProps['una']
}

/**
 * Props for the NDropdownMenuSubTrigger component.
 */
export interface NDropdownMenuSubTriggerProps extends NButtonProps, DropdownMenuSubTriggerProps {
  /** Dropdown menu item */
  dropdownMenuItem?: HTMLAttributes['class']
  /** Whether the sub-trigger is inset */
  inset?: boolean
}

/**
 * Props for the NDropdownMenuShortcut component.
 */
export interface NDropdownMenuShortcutProps extends BaseExtensions {
  /** Shortcut key for the item */
  value?: string
  /** Additional properties for the una component */
  una?: NDropdownMenuUnaProps['dropdownMenuShortcut']
}

/**
 * Props for the NDropdownMenuUna component.
 */
interface NDropdownMenuUnaProps {
  /** CSS class for the dropdown menu content */
  dropdownMenuContent?: HTMLAttributes['class']
  /** CSS class for the dropdown menu sub-content */
  dropdownMenuSubContent?: HTMLAttributes['class']
  /** CSS class for the dropdown menu sub-trigger */
  dropdownMenuSubTrigger?: HTMLAttributes['class']
  /** CSS class for the dropdown menu trigger */
  dropdownMenuTrigger?: HTMLAttributes['class']
  /** CSS class for the dropdown menu label */
  dropdownMenuLabel?: HTMLAttributes['class']
  /** CSS class for the dropdown menu separator */
  dropdownMenuSeparator?: HTMLAttributes['class']
  /** CSS class for the dropdown menu group */
  dropdownMenuGroup?: HTMLAttributes['class']
  /** CSS class for the dropdown menu item */
  dropdownMenuItem?: HTMLAttributes['class']
  /** CSS class for the dropdown menu root */
  dropdownMenuRoot?: HTMLAttributes['class']
  /** CSS class for the dropdown menu shortcut */
  dropdownMenuShortcut?: HTMLAttributes['class']
}

Presets

type DropdownMenuPrefix = 'dropdown-menu'

export const staticDropdownMenu: Record<`${DropdownMenuPrefix}-${string}` | DropdownMenuPrefix, string> = {
  // configurations
  'dropdown-menu': '',
  'dropdown-menu-default-variant': 'btn-solid-white',

  // dropdown-menu-trigger
  'dropdown-menu-trigger': '',
  'dropdown-menu-trigger-leading': '',
  'dropdown-menu-trigger-trailing': 'ml-auto',

  // dropdown-menu-content
  'dropdown-menu-content': 'z-50 min-w-32 overflow-hidden rounded-md border border-base bg-popover p-1 text-popover shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',

  // dropdown-menu-item
  'dropdown-menu-item-base': 'text-left transition-color focus-visible:outline-0',
  'dropdown-menu-item-leading': 'opacity-75 text-1.1em',
  'dropdown-menu-item-trailing': 'ml-auto opacity-75 text-1.1em',

  // dropdown-menu-label
  'dropdown-menu-label': 'px-2 py-1.5 text-1em font-semibold',

  // dropdown-menu-separator
  'dropdown-menu-separator-root': 'relative -mx-1',
  'dropdown-menu-separator': '',

  // dropdown-menu-shortcut
  'dropdown-menu-shortcut': 'pl-10 ml-auto text-0.875em tracking-widest n-disabled space-x-0.5',

  // dropdown-menu-group
  'dropdown-menu-group': '',

  // dropdown-menu-sub
  'dropdown-menu-sub-trigger': 'transition-color focus-visible:outline-0',
  'dropdown-menu-sub-trigger-leading': 'opacity-75 text-1.1em',
  'dropdown-menu-sub-trigger-trailing': 'ml-auto opacity-75 text-1.1em',
  'dropdown-menu-sub-content': 'z-50 min-w-32 overflow-hidden rounded-md border border-base bg-popover p-1 text-popover shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',

}

export const dynamicDropdownMenu = [
  [/^dropdown-menu-([^-]+)-([^-]+)$/, ([, v = 'solid', c = 'white']) => `btn-${v}-${c}`],

  [/^dropdown-menu-item(?:-(\S+))?$/, ([, c = 'gray']) => `focus:bg-${c}-100 focus:text-${c}-800 dark:focus:bg-${c}-800 dark:focus:text-${c}-100 data-[state=open]:bg-${c}-100 dark:data-[state=open]:bg-${c}-800`],
]

export const dropdowMenu = [
  ...dynamicDropdownMenu,
  staticDropdownMenu,
]

Component

<script setup lang="ts">
import type { DropdownMenuContentEmits, DropdownMenuRootEmits } from 'radix-vue'
import type { NDropdownMenuProps } from '../../../types'
import { createReusableTemplate } from '@vueuse/core'
import { DropdownMenuPortal, useForwardPropsEmits } from 'radix-vue'
import { omitProps, pickProps } from '../../../utils'
import DropdownMenuContent from './DropdownMenuContent.vue'
import DropdownMenuGroup from './DropdownMenuGroup.vue'
import DropdownMenuItem from './DropdownMenuItem.vue'
import DropdownMenuLabel from './DropdownMenuLabel.vue'
import DropdownMenuRoot from './DropdownMenuRoot.vue'
import DropdownMenuSeparator from './DropdownMenuSeparator.vue'
import DropdownMenuSub from './DropdownMenuSub.vue'
import DropdownMenuSubContent from './DropdownMenuSubContent.vue'
import DropdownMenuSubTrigger from './DropdownMenuSubTrigger.vue'
import DropdownMenuTrigger from './DropdownMenuTrigger.vue'

const props = defineProps<NDropdownMenuProps>()
const emits = defineEmits<DropdownMenuRootEmits & DropdownMenuContentEmits>()
const forwarded = useForwardPropsEmits(props, emits)

const [DefineMenuSub, ReuseMenuSub] = createReusableTemplate<NDropdownMenuProps>()
</script>

<template>
  <DropdownMenuRoot
    v-bind="pickProps(forwarded, ['defaultOpen', 'open', 'modal', 'dir'])"
  >
    <slot>
      <slot name="trigger">
        <DropdownMenuTrigger
          v-bind="omitProps({ ...forwarded, ...forwarded._dropdownMenuTrigger }, [
            'dropdownMenuItem',
            'items',
            'menuLabel',

            '_dropdownMenuItem',
            '_dropdownMenuContent',
            '_dropdownMenuLabel',
            '_dropdownMenuSeparator',
            '_dropdownMenuGroup',
            '_dropdownMenuSubTrigger',
            '_dropdownMenuSubContent',
          ])"
        />
      </slot>

      <DropdownMenuContent
        v-bind="forwarded._dropdownMenuContent"
      >
        <slot name="content">
          <template
            v-if="menuLabel || $slots['menu-label']"
          >
            <DropdownMenuLabel
              :size
              :inset
              :una="forwarded.una?.dropdownMenuLabel"
              v-bind="forwarded._dropdownMenuLabel"
            >
              <slot name="menu-label">
                {{ menuLabel }}
              </slot>
            </DropdownMenuLabel>
            <DropdownMenuSeparator
              :una="forwarded.una?.dropdownMenuSeparator"
              v-bind="forwarded._dropdownMenuSeparator"
            />
          </template>

          <slot name="items" :items>
            <DropdownMenuGroup
              :una="forwarded.una?.dropdownMenuGroup"
              v-bind="forwarded._dropdownMenuGroup"
            >
              <template
                v-for="item in items"
                :key="item.label"
              >
                <slot
                  v-if="!item.items && item.label"
                  :name="`item-${item.label}`"
                >
                  <DropdownMenuItem
                    :size
                    :inset
                    :dropdown-menu-item
                    :una="forwarded.una?.dropdownMenuItem"
                    v-bind="{ ...item, ...forwarded._dropdownMenuItem, ...item._dropdownMenuItem }"
                  />
                </slot>

                <DropdownMenuSeparator
                  v-else-if="!item.label && !item.items"
                  :una="forwarded.una?.dropdownMenuSeparator"
                  v-bind="{ ...forwarded._dropdownMenuSeparator, ...item._dropdownMenuSeparator }"
                />

                <ReuseMenuSub
                  v-else
                  v-bind="item"
                />
              </template>
            </DropdownMenuGroup>
          </slot>
        </slot>
      </DropdownMenuContent>
    </slot>
  </DropdownMenuRoot>

  <DefineMenuSub
    v-slot="subProps"
    as="div"
  >
    <template
      v-if="subProps.menuLabel"
    >
      <DropdownMenuLabel
        :size
        :inset
        :una="forwarded.una?.dropdownMenuLabel"
        v-bind="{ ...forwarded._dropdownMenuLabel, ...subProps._dropdownMenuLabel }"
      >
        {{ subProps.menuLabel }}
      </DropdownMenuLabel>
      <DropdownMenuSeparator
        :una="forwarded.una?.dropdownMenuSeparator"
        v-bind="{ ...forwarded._dropdownMenuSeparator, ...subProps._dropdownMenuSeparator }"
      />
    </template>

    <DropdownMenuGroup
      :una="forwarded.una?.dropdownMenuGroup"
      v-bind="{ ...forwarded._dropdownMenuGroup, ...subProps._dropdownMenuGroup }"
    >
      <DropdownMenuSub>
        <DropdownMenuSubTrigger
          :size
          :inset
          :una="forwarded.una?.dropdownMenuSubTrigger"
          :dropdown-menu-item
          v-bind="omitProps({
            ...subProps,
            ...forwarded._dropdownMenuSubTrigger,
            ...subProps._dropdownMenuSubTrigger,
          }, ['$slots'])"
        >
          <slot name="sub-trigger" :label="subProps.label" />
        </DropdownMenuSubTrigger>

        <DropdownMenuPortal>
          <DropdownMenuSubContent
            v-bind="subProps._dropdownMenuSubContent"
            :una="forwarded.una?.dropdownMenuSubContent"
          >
            <template
              v-for="subItem in subProps.items"
              :key="subItem.label"
            >
              <DropdownMenuItem
                v-if="!subItem.items && subItem.label"
                :size
                :inset
                :dropdown-menu-item
                :una="forwarded.una?.dropdownMenuItem"
                v-bind="{ ...subItem, ...forwarded._dropdownMenuItem, ...subItem._dropdownMenuItem }"
              >
                {{ subItem.label }}
              </DropdownMenuItem>

              <DropdownMenuSeparator
                v-else-if="!subItem.label && !subItem.items"
                :una="forwarded.una?.dropdownMenuSeparator"
                v-bind="{ ...forwarded._dropdownMenuSeparator, ...subItem._dropdownMenuSeparator }"
              />

              <ReuseMenuSub
                v-else
                v-bind="subItem"
              />
            </template>
          </DropdownMenuSubContent>
        </DropdownMenuPortal>
      </DropdownMenuSub>
    </DropdownMenuGroup>
  </DefineMenuSub>
</template>
<script setup lang="ts">
import type { NDropdownMenuTriggerProps } from '../../../types'
import { DropdownMenuTrigger, useForwardProps } from 'radix-vue'
import { cn, randomId } from '../../../utils'
import Button from '../Button.vue'

const props = defineProps<NDropdownMenuTriggerProps>()

const forwardedProps = useForwardProps(props)
</script>

<template>
  <DropdownMenuTrigger
    as-child
  >
    <Button
      v-bind="forwardedProps"
      :id="randomId('dropdown-menu-trigger')"
      :class="cn(
        'dropdown-menu-trigger justify-start font-normal',
        props.class,
      )"
      :una="{
        btnDefaultVariant: 'dropdown-menu-default-variant',
        btnLeading: cn('dropdown-menu-trigger-leading', forwardedProps.una?.btnLeading),
        btnTrailing: cn('dropdown-menu-trigger-trailing', forwardedProps.una?.btnTrailing),
        ...forwardedProps.una,
      }"
    >
      <template v-for="(_, name) in $slots" #[name]="slotData">
        <slot :name="name" v-bind="slotData" />
      </template>
    </Button>
  </DropdownMenuTrigger>
</template>
<script setup lang="ts">
import type { NDropdownMenuItemProps } from '../../../types'
import { DropdownMenuItem, useForwardProps } from 'radix-vue'
import { computed } from 'vue'
import { cn } from '../../../utils'
import Button from '../Button.vue'
import DropdownMenuShortcut from './DropdownMenuShortcut.vue'

const props = withDefaults(defineProps<NDropdownMenuItemProps>(), {
  dropdownMenuItem: '~',
})

const delegatedProps = computed(() => {
  const { class: _, ...delegated } = props

  return delegated
})

const forwardedProps = useForwardProps(delegatedProps)
</script>

<template>
  <div>
    <DropdownMenuItem
      as-child
    >
      <Button
        v-bind="forwardedProps"
        :dropdown-menu-item
        :class="cn(
          'dropdown-menu-item-base w-full justify-start font-normal rounded-sm px-2',
          forwardedProps.inset && !(forwardedProps.leading || $slots.leading) && 'pl-8',
          props.class,
        )"
        btn="~"
        :una="{
          btnLeading: cn('dropdown-menu-item-leading', forwardedProps.una?.btnLeading),
          btnTrailing: cn('dropdown-menu-item-trailing', forwardedProps.una?.btnTrailing),
          ...forwardedProps.una,
        }"
      >
        <template v-for="(_, name) in $slots" #[name]="slotData">
          <slot :name="name" v-bind="slotData" />
        </template>

        <template
          v-if="forwardedProps.shortcut"
          #trailing
        >
          <DropdownMenuShortcut>
            {{ forwardedProps.shortcut }}
          </DropdownMenuShortcut>
        </template>
      </Button>
    </DropdownMenuItem>
  </div>
</template>
<script setup lang="ts">
import type { NDropdownMenuGroupProps } from '../../../types'
import { DropdownMenuGroup } from 'radix-vue'
import { cn } from '../../../utils'

const props = defineProps<NDropdownMenuGroupProps>()
</script>

<template>
  <DropdownMenuGroup
    v-bind="props"
    :class="cn(
      'dropdown-menu-group',
      props.una?.dropdownMenuGroup,
      props.class,
    )"
  >
    <slot />
  </DropdownMenuGroup>
</template>
<script setup lang="ts">
import type { NDropdownMenuLabelProps } from '../../../types'
import { DropdownMenuLabel, useForwardProps } from 'radix-vue'
import { computed } from 'vue'
import { cn } from '../../../utils'

const props = withDefaults(defineProps<NDropdownMenuLabelProps>(), {
  size: 'sm',
})

const delegatedProps = computed(() => {
  const { class: _, ...delegated } = props

  return delegated
})

const forwardedProps = useForwardProps(delegatedProps)
</script>

<template>
  <DropdownMenuLabel
    v-bind="forwardedProps"
    :class="cn(
      'dropdown-menu-label',
      forwardedProps.inset && 'pl-8',
      props.class,
      props.una?.dropdownMenuLabel,
    )"
  >
    <slot />
  </DropdownMenuLabel>
</template>
<script setup lang="ts">
import type { NDropdownMenuSeparatorProps } from '../../../types'
import {
  DropdownMenuSeparator,
} from 'radix-vue'
import { computed } from 'vue'
import { cn } from '../../../utils'
import Separator from '../Separator.vue'

const props = defineProps<NDropdownMenuSeparatorProps>()

const delegatedProps = computed(() => {
  const { class: _, ...delegated } = props

  return delegated
})
</script>

<template>
  <DropdownMenuSeparator
    as-child
  >
    <div class="dropdown-menu-separator-root">
      <Separator
        v-bind="delegatedProps"
        :class="cn(
          'dropdown-menu-separator my-1',
          props.class,
          props.una?.dropdownMenuSeparator,
        )"
      />
    </div>
  </DropdownMenuSeparator>
</template>
<script setup lang="ts">
import type { NDropdownMenuContentProps } from '../../../types'
import {
  DropdownMenuContent,
  type DropdownMenuContentEmits,
  DropdownMenuPortal,
  useForwardPropsEmits,
} from 'radix-vue'
import { computed } from 'vue'
import { cn } from '../../../utils'

const props = withDefaults(
  defineProps<NDropdownMenuContentProps>(),
  {
    sideOffset: 4,
  },
)
const emits = defineEmits<DropdownMenuContentEmits>()

const delegatedProps = computed(() => {
  const { class: _, ...delegated } = props

  return delegated
})

const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>

<template>
  <DropdownMenuPortal>
    <DropdownMenuContent
      v-bind="forwarded"
      :class="cn(
        'dropdown-menu-content',
        props.class,
        props.una?.dropdownMenuContent,
      )"
    >
      <slot />
    </DropdownMenuContent>
  </DropdownMenuPortal>
</template>
<script setup lang="ts">
import type {
  DropdownMenuSubEmits,
  DropdownMenuSubProps,
} from 'radix-vue'

import {
  DropdownMenuSub,
  useForwardPropsEmits,
} from 'radix-vue'

const props = defineProps<DropdownMenuSubProps>()
const emits = defineEmits<DropdownMenuSubEmits>()

const forwarded = useForwardPropsEmits(props, emits)
</script>

<template>
  <DropdownMenuSub
    v-bind="forwarded"
  >
    <slot />
  </DropdownMenuSub>
</template>
<script setup lang="ts">
import type { NDropdownMenuSubTriggerProps } from '../../../types'
import {
  DropdownMenuSubTrigger,
  useForwardProps,
} from 'radix-vue'
import { computed } from 'vue'
import { cn } from '../../../utils'
import Button from '../Button.vue'

const props = withDefaults(defineProps<NDropdownMenuSubTriggerProps>(), {
  dropdownMenuItem: '~',
})

const delegatedProps = computed(() => {
  const { class: _, ...delegated } = props

  return delegated
})

const forwardedProps = useForwardProps(delegatedProps)
</script>

<template>
  <DropdownMenuSubTrigger
    as-child
  >
    <Button
      v-bind="forwardedProps"
      :dropdown-menu-item
      :class="cn(
        'dropdown-menu-sub-trigger w-full justify-start font-normal rounded-sm px-2',
        forwardedProps.inset && !(forwardedProps.leading || $slots.leading) && 'pl-8',
        props.class,
      )"
      btn="~"
      :una="{
        btnLeading: cn('dropdown-menu-sub-trigger-leading', forwardedProps.una?.btnLeading),
        btnTrailing: cn('dropdown-menu-sub-trigger-trailing', forwardedProps.una?.btnTrailing),
        ...forwardedProps.una,
      }"
      trailing="i-radix-icons-chevron-right"
    >
      <template v-for="(_, name) in $slots" #[name]="slotData">
        <slot :name="name" v-bind="slotData" />
      </template>
    </Button>
  </dropdownmenusubtrigger>
</template>
<script setup lang="ts">
import type { NDropdownMenuSubContentProps } from '../../../types'
import {
  DropdownMenuSubContent,
  type DropdownMenuSubContentEmits,
  useForwardPropsEmits,
} from 'radix-vue'
import { computed } from 'vue'
import { cn } from '../../../utils'

const props = defineProps<NDropdownMenuSubContentProps>()
const emits = defineEmits<DropdownMenuSubContentEmits>()

const delegatedProps = computed(() => {
  const { class: _, ...delegated } = props

  return delegated
})

const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>

<template>
  <DropdownMenuSubContent
    v-bind="forwarded"
    :class="cn(
      'dropdown-menu-sub-content',
      props.class,
      forwarded.una?.dropdownMenuSubContent,
    )"
  >
    <slot />
  </DropdownMenuSubContent>
</template>