Dropdown Menu
Vue overlayFloating menu launched from a trigger button — for account switchers, row actions, editor menus, and any short list of commands. Supports labels, icons, separators, keyboard shortcuts, checkbox/radio items, and nested submenus. Built on reka-ui; ARIA + keyboard navigation handled.
Also available for React ->Installation
$ pnpm dlx shadcn-vue@latest add https://uipkge.dev/r/vue/dropdown-menu.json$ npx shadcn-vue@latest add https://uipkge.dev/r/vue/dropdown-menu.json$ yarn dlx shadcn-vue@latest add https://uipkge.dev/r/vue/dropdown-menu.json$ bunx shadcn-vue@latest add https://uipkge.dev/r/vue/dropdown-menu.json
Or with the named registry:
npx shadcn-vue@latest add @uipkge/dropdown-menu
Examples
Dependencies
Used by
Files (15)
-
app/components/ui/dropdown-menu/DropdownMenu.vue 0.5 kB
<script setup lang="ts"> import type { DropdownMenuRootEmits, DropdownMenuRootProps } from 'reka-ui' import { DropdownMenuRoot, useForwardPropsEmits } from 'reka-ui' const props = defineProps<DropdownMenuRootProps>() const emits = defineEmits<DropdownMenuRootEmits>() const forwarded = useForwardPropsEmits(props, emits) </script> <template> <DropdownMenuRoot v-slot="slotProps" data-uipkge data-slot="dropdown-menu" v-bind="forwarded"> <slot v-bind="slotProps" /> </DropdownMenuRoot> </template> -
app/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue 1.5 kB
<script setup lang="ts"> import type { DropdownMenuCheckboxItemEmits, DropdownMenuCheckboxItemProps } from 'reka-ui' import type { HTMLAttributes } from 'vue' import { reactiveOmit } from '@vueuse/core' import { Check } from 'lucide-vue-next' import { DropdownMenuCheckboxItem, DropdownMenuItemIndicator, useForwardPropsEmits } from 'reka-ui' import { cn } from '@/lib/utils' const props = defineProps<DropdownMenuCheckboxItemProps & { class?: HTMLAttributes['class'] }>() const emits = defineEmits<DropdownMenuCheckboxItemEmits>() const delegatedProps = reactiveOmit(props, 'class') const forwarded = useForwardPropsEmits(delegatedProps, emits) </script> <template> <DropdownMenuCheckboxItem data-uipkge data-slot="dropdown-menu-checkbox-item" v-bind="forwarded" :class=" cn( 'focus:bg-accent focus:text-accent-foreground focus-visible:ring-ring relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus-visible:ring-2 focus-visible:ring-inset data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4', props.class ) " > <span class="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center"> <DropdownMenuItemIndicator> <slot name="indicator-icon"> <Check class="size-4" aria-hidden="true" /> </slot> </DropdownMenuItemIndicator> </span> <slot /> </DropdownMenuCheckboxItem> </template> -
app/components/ui/dropdown-menu/DropdownMenuContent.vue 1.5 kB
<script setup lang="ts"> import type { DropdownMenuContentEmits, DropdownMenuContentProps } from 'reka-ui' import type { HTMLAttributes } from 'vue' import { reactiveOmit } from '@vueuse/core' import { DropdownMenuContent, DropdownMenuPortal, useForwardPropsEmits } from 'reka-ui' import { cn } from '@/lib/utils' defineOptions({ inheritAttrs: false, }) const props = withDefaults(defineProps<DropdownMenuContentProps & { class?: HTMLAttributes['class'] }>(), { sideOffset: 4, }) const emits = defineEmits<DropdownMenuContentEmits>() const delegatedProps = reactiveOmit(props, 'class') const forwarded = useForwardPropsEmits(delegatedProps, emits) </script> <template> <DropdownMenuPortal> <DropdownMenuContent data-uipkge data-slot="dropdown-menu-content" v-bind="{ ...$attrs, ...forwarded }" :class=" cn( 'bg-popover text-popover-foreground 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 z-50 max-h-(--reka-dropdown-menu-content-available-height) min-w-[8rem] origin-(--reka-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md', props.class, ) " > <slot /> </DropdownMenuContent> </DropdownMenuPortal> </template> -
app/components/ui/dropdown-menu/DropdownMenuGroup.vue 0.3 kB
<script setup lang="ts"> import type { DropdownMenuGroupProps } from 'reka-ui' import { DropdownMenuGroup } from 'reka-ui' const props = defineProps<DropdownMenuGroupProps>() </script> <template> <DropdownMenuGroup data-uipkge data-slot="dropdown-menu-group" v-bind="props"> <slot /> </DropdownMenuGroup> </template> -
app/components/ui/dropdown-menu/DropdownMenuItem.vue 1.6 kB
<script setup lang="ts"> import type { DropdownMenuItemProps } from 'reka-ui' import type { HTMLAttributes } from 'vue' import { reactiveOmit } from '@vueuse/core' import { DropdownMenuItem, useForwardProps } from 'reka-ui' import { cn } from '@/lib/utils' const props = withDefaults( defineProps< DropdownMenuItemProps & { class?: HTMLAttributes['class'] inset?: boolean variant?: 'default' | 'destructive' } >(), { variant: 'default', }, ) const delegatedProps = reactiveOmit(props, 'inset', 'variant', 'class') const forwardedProps = useForwardProps(delegatedProps) </script> <template> <DropdownMenuItem data-uipkge data-slot="dropdown-menu-item" :data-inset="inset ? '' : undefined" :data-variant="variant" v-bind="forwardedProps" :class=" cn( 'focus:bg-accent focus:text-accent-foreground focus-visible:ring-ring data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*=\'text-\'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus-visible:ring-2 focus-visible:ring-inset data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4', props.class ) " > <slot /> </DropdownMenuItem> </template> -
app/components/ui/dropdown-menu/DropdownMenuLabel.vue 0.8 kB
<script setup lang="ts"> import type { DropdownMenuLabelProps } from 'reka-ui' import type { HTMLAttributes } from 'vue' import { reactiveOmit } from '@vueuse/core' import { DropdownMenuLabel, useForwardProps } from 'reka-ui' import { cn } from '@/lib/utils' const props = defineProps<DropdownMenuLabelProps & { class?: HTMLAttributes['class']; inset?: boolean }>() const delegatedProps = reactiveOmit(props, 'class', 'inset') const forwardedProps = useForwardProps(delegatedProps) </script> <template> <DropdownMenuLabel data-uipkge data-slot="dropdown-menu-label" :data-inset="inset ? '' : undefined" v-bind="forwardedProps" :class="cn('px-2 py-1.5 text-sm font-medium data-[inset]:pl-8', props.class)" > <slot /> </DropdownMenuLabel> </template> -
app/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue 0.5 kB
<script setup lang="ts"> import type { DropdownMenuRadioGroupEmits, DropdownMenuRadioGroupProps } from 'reka-ui' import { DropdownMenuRadioGroup, useForwardPropsEmits } from 'reka-ui' const props = defineProps<DropdownMenuRadioGroupProps>() const emits = defineEmits<DropdownMenuRadioGroupEmits>() const forwarded = useForwardPropsEmits(props, emits) </script> <template> <DropdownMenuRadioGroup data-uipkge data-slot="dropdown-menu-radio-group" v-bind="forwarded"> <slot /> </DropdownMenuRadioGroup> </template> -
app/components/ui/dropdown-menu/DropdownMenuRadioItem.vue 1.5 kB
<script setup lang="ts"> import type { DropdownMenuRadioItemEmits, DropdownMenuRadioItemProps } from 'reka-ui' import type { HTMLAttributes } from 'vue' import { reactiveOmit } from '@vueuse/core' import { Circle } from 'lucide-vue-next' import { DropdownMenuItemIndicator, DropdownMenuRadioItem, useForwardPropsEmits } from 'reka-ui' import { cn } from '@/lib/utils' const props = defineProps<DropdownMenuRadioItemProps & { class?: HTMLAttributes['class'] }>() const emits = defineEmits<DropdownMenuRadioItemEmits>() const delegatedProps = reactiveOmit(props, 'class') const forwarded = useForwardPropsEmits(delegatedProps, emits) </script> <template> <DropdownMenuRadioItem data-uipkge data-slot="dropdown-menu-radio-item" v-bind="forwarded" :class=" cn( 'focus:bg-accent focus:text-accent-foreground focus-visible:ring-ring relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus-visible:ring-2 focus-visible:ring-inset data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4', props.class ) " > <span class="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center"> <DropdownMenuItemIndicator> <slot name="indicator-icon"> <Circle class="size-2 fill-current" /> </slot> </DropdownMenuItemIndicator> </span> <slot /> </DropdownMenuRadioItem> </template> -
app/components/ui/dropdown-menu/DropdownMenuSeparator.vue 0.6 kB
<script setup lang="ts"> import type { DropdownMenuSeparatorProps } from 'reka-ui' import type { HTMLAttributes } from 'vue' import { reactiveOmit } from '@vueuse/core' import { DropdownMenuSeparator } from 'reka-ui' import { cn } from '@/lib/utils' const props = defineProps< DropdownMenuSeparatorProps & { class?: HTMLAttributes['class'] } >() const delegatedProps = reactiveOmit(props, 'class') </script> <template> <DropdownMenuSeparator data-uipkge data-slot="dropdown-menu-separator" v-bind="delegatedProps" :class="cn('bg-border -mx-1 my-1 h-px', props.class)" /> </template> -
app/components/ui/dropdown-menu/DropdownMenuShortcut.vue 0.4 kB
<script setup lang="ts"> import type { HTMLAttributes } from 'vue' import { cn } from '@/lib/utils' const props = defineProps<{ class?: HTMLAttributes['class'] }>() </script> <template> <span data-uipkge data-slot="dropdown-menu-shortcut" :class="cn('text-muted-foreground ml-auto text-xs tracking-widest', props.class)" > <slot /> </span> </template> -
app/components/ui/dropdown-menu/DropdownMenuSub.vue 0.5 kB
<script setup lang="ts"> import type { DropdownMenuSubEmits, DropdownMenuSubProps } from 'reka-ui' import { DropdownMenuSub, useForwardPropsEmits } from 'reka-ui' const props = defineProps<DropdownMenuSubProps>() const emits = defineEmits<DropdownMenuSubEmits>() const forwarded = useForwardPropsEmits(props, emits) </script> <template> <DropdownMenuSub v-slot="slotProps" data-uipkge data-slot="dropdown-menu-sub" v-bind="forwarded"> <slot v-bind="slotProps" /> </DropdownMenuSub> </template> -
app/components/ui/dropdown-menu/DropdownMenuSubContent.vue 1.3 kB
<script setup lang="ts"> import type { DropdownMenuSubContentEmits, DropdownMenuSubContentProps } from 'reka-ui' import type { HTMLAttributes } from 'vue' import { reactiveOmit } from '@vueuse/core' import { DropdownMenuSubContent, useForwardPropsEmits } from 'reka-ui' import { cn } from '@/lib/utils' const props = defineProps<DropdownMenuSubContentProps & { class?: HTMLAttributes['class'] }>() const emits = defineEmits<DropdownMenuSubContentEmits>() const delegatedProps = reactiveOmit(props, 'class') const forwarded = useForwardPropsEmits(delegatedProps, emits) </script> <template> <DropdownMenuSubContent data-uipkge data-slot="dropdown-menu-sub-content" v-bind="forwarded" :class=" cn( 'bg-popover text-popover-foreground 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 z-50 min-w-[8rem] origin-(--reka-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg', props.class, ) " > <slot /> </DropdownMenuSubContent> </template> -
app/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue 1.4 kB
<script setup lang="ts"> import type { DropdownMenuSubTriggerProps } from 'reka-ui' import type { HTMLAttributes } from 'vue' import { reactiveOmit } from '@vueuse/core' import { ChevronRight } from 'lucide-vue-next' import { DropdownMenuSubTrigger, useForwardProps } from 'reka-ui' import { cn } from '@/lib/utils' const props = defineProps<DropdownMenuSubTriggerProps & { class?: HTMLAttributes['class']; inset?: boolean }>() const delegatedProps = reactiveOmit(props, 'class', 'inset') const forwardedProps = useForwardProps(delegatedProps) </script> <template> <DropdownMenuSubTrigger data-uipkge data-slot="dropdown-menu-sub-trigger" v-bind="forwardedProps" :data-inset="inset ? '' : undefined" :class=" cn( 'focus:bg-accent focus:text-accent-foreground focus-visible:ring-ring data-[state=open]:bg-accent data-[state=open]:text-accent-foreground data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*=\'text-\'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus-visible:ring-2 focus-visible:ring-inset data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4', props.class ) " > <slot /> <ChevronRight class="ml-auto size-4" /> </DropdownMenuSubTrigger> </template> -
app/components/ui/dropdown-menu/DropdownMenuTrigger.vue 0.4 kB
<script setup lang="ts"> import type { DropdownMenuTriggerProps } from 'reka-ui' import { DropdownMenuTrigger, useForwardProps } from 'reka-ui' const props = defineProps<DropdownMenuTriggerProps>() const forwardedProps = useForwardProps(props) </script> <template> <DropdownMenuTrigger data-uipkge data-slot="dropdown-menu-trigger" v-bind="forwardedProps"> <slot /> </DropdownMenuTrigger> </template> -
app/components/ui/dropdown-menu/index.ts 1.1 kB
export { default as DropdownMenu } from './DropdownMenu.vue' export { default as DropdownMenuCheckboxItem } from './DropdownMenuCheckboxItem.vue' export { default as DropdownMenuContent } from './DropdownMenuContent.vue' export { default as DropdownMenuGroup } from './DropdownMenuGroup.vue' export { default as DropdownMenuItem } from './DropdownMenuItem.vue' export { default as DropdownMenuLabel } from './DropdownMenuLabel.vue' export { default as DropdownMenuRadioGroup } from './DropdownMenuRadioGroup.vue' export { default as DropdownMenuRadioItem } from './DropdownMenuRadioItem.vue' export { default as DropdownMenuSeparator } from './DropdownMenuSeparator.vue' export { default as DropdownMenuShortcut } from './DropdownMenuShortcut.vue' export { default as DropdownMenuSub } from './DropdownMenuSub.vue' export { default as DropdownMenuSubContent } from './DropdownMenuSubContent.vue' export { default as DropdownMenuSubTrigger } from './DropdownMenuSubTrigger.vue' export { default as DropdownMenuTrigger } from './DropdownMenuTrigger.vue' export { DropdownMenuPortal } from 'reka-ui'
Raw manifest: https://uipkge.dev/r/vue/dropdown-menu.json