Context Menu
Vue overlayRight-click menu — same primitives as Dropdown Menu but triggered by `contextmenu` events. Drop on any element you want to attach row actions, file-system style operations, or copy/paste menus to.
Also available for React ->Installation
$ pnpm dlx shadcn-vue@latest add https://uipkge.dev/r/vue/context-menu.json$ npx shadcn-vue@latest add https://uipkge.dev/r/vue/context-menu.json$ yarn dlx shadcn-vue@latest add https://uipkge.dev/r/vue/context-menu.json$ bunx shadcn-vue@latest add https://uipkge.dev/r/vue/context-menu.json
Or with the named registry:
npx shadcn-vue@latest add @uipkge/context-menu
Examples
Dependencies
Used by
Files (16)
-
app/components/ui/context-menu/ContextMenu.vue 0.5 kB
<script setup lang="ts"> import type { ContextMenuRootEmits, ContextMenuRootProps } from 'reka-ui' import { ContextMenuRoot, useForwardPropsEmits } from 'reka-ui' const props = defineProps<ContextMenuRootProps>() const emits = defineEmits<ContextMenuRootEmits>() const forwarded = useForwardPropsEmits(props, emits) </script> <template> <ContextMenuRoot data-uipkge data-slot="context-menu" v-bind="forwarded"> <slot /> </ContextMenuRoot> </template> -
app/components/ui/context-menu/ContextMenuCheckboxItem.vue 1.5 kB
<script setup lang="ts"> import type { ContextMenuCheckboxItemEmits, ContextMenuCheckboxItemProps } from 'reka-ui' import type { HTMLAttributes } from 'vue' import { reactiveOmit } from '@vueuse/core' import { Check } from 'lucide-vue-next' import { ContextMenuCheckboxItem, ContextMenuItemIndicator, useForwardPropsEmits } from 'reka-ui' import { cn } from '@/lib/utils' const props = defineProps<ContextMenuCheckboxItemProps & { class?: HTMLAttributes['class'] }>() const emits = defineEmits<ContextMenuCheckboxItemEmits>() const delegatedProps = reactiveOmit(props, 'class') const forwarded = useForwardPropsEmits(delegatedProps, emits) </script> <template> <ContextMenuCheckboxItem data-uipkge data-slot="context-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"> <ContextMenuItemIndicator> <slot name="indicator-icon"> <Check class="size-4" aria-hidden="true" /> </slot> </ContextMenuItemIndicator> </span> <slot /> </ContextMenuCheckboxItem> </template> -
app/components/ui/context-menu/ContextMenuContent.vue 1.4 kB
<script setup lang="ts"> import type { ContextMenuContentEmits, ContextMenuContentProps } from 'reka-ui' import type { HTMLAttributes } from 'vue' import { reactiveOmit } from '@vueuse/core' import { ContextMenuContent, ContextMenuPortal, useForwardPropsEmits } from 'reka-ui' import { cn } from '@/lib/utils' defineOptions({ inheritAttrs: false, }) const props = defineProps<ContextMenuContentProps & { class?: HTMLAttributes['class'] }>() const emits = defineEmits<ContextMenuContentEmits>() const delegatedProps = reactiveOmit(props, 'class') const forwarded = useForwardPropsEmits(delegatedProps, emits) </script> <template> <ContextMenuPortal> <ContextMenuContent data-uipkge data-slot="context-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-context-menu-content-available-height) min-w-[8rem] overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md', props.class, ) " > <slot /> </ContextMenuContent> </ContextMenuPortal> </template> -
app/components/ui/context-menu/ContextMenuGroup.vue 0.3 kB
<script setup lang="ts"> import type { ContextMenuGroupProps } from 'reka-ui' import { ContextMenuGroup } from 'reka-ui' const props = defineProps<ContextMenuGroupProps>() </script> <template> <ContextMenuGroup data-uipkge data-slot="context-menu-group" v-bind="props"> <slot /> </ContextMenuGroup> </template> -
app/components/ui/context-menu/ContextMenuItem.vue 1.6 kB
<script setup lang="ts"> import type { ContextMenuItemEmits, ContextMenuItemProps } from 'reka-ui' import type { HTMLAttributes } from 'vue' import { reactiveOmit } from '@vueuse/core' import { ContextMenuItem, useForwardPropsEmits } from 'reka-ui' import { cn } from '@/lib/utils' const props = withDefaults( defineProps< ContextMenuItemProps & { class?: HTMLAttributes['class'] inset?: boolean variant?: 'default' | 'destructive' } >(), { variant: 'default', }, ) const emits = defineEmits<ContextMenuItemEmits>() const delegatedProps = reactiveOmit(props, 'class') const forwarded = useForwardPropsEmits(delegatedProps, emits) </script> <template> <ContextMenuItem data-uipkge data-slot="context-menu-item" :data-inset="inset ? '' : undefined" :data-variant="variant" v-bind="forwarded" :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/40 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 /> </ContextMenuItem> </template> -
app/components/ui/context-menu/ContextMenuLabel.vue 0.7 kB
<script setup lang="ts"> import type { ContextMenuLabelProps } from 'reka-ui' import type { HTMLAttributes } from 'vue' import { reactiveOmit } from '@vueuse/core' import { ContextMenuLabel } from 'reka-ui' import { cn } from '@/lib/utils' const props = defineProps<ContextMenuLabelProps & { class?: HTMLAttributes['class']; inset?: boolean }>() const delegatedProps = reactiveOmit(props, 'class') </script> <template> <ContextMenuLabel data-uipkge data-slot="context-menu-label" :data-inset="inset ? '' : undefined" v-bind="delegatedProps" :class="cn('text-foreground px-2 py-1.5 text-sm font-medium data-[inset]:pl-8', props.class)" > <slot /> </ContextMenuLabel> </template> -
app/components/ui/context-menu/ContextMenuPortal.vue 0.3 kB
<script setup lang="ts"> import type { ContextMenuPortalProps } from 'reka-ui' import { ContextMenuPortal } from 'reka-ui' const props = defineProps<ContextMenuPortalProps>() </script> <template> <ContextMenuPortal data-uipkge data-slot="context-menu-portal" v-bind="props"> <slot /> </ContextMenuPortal> </template> -
app/components/ui/context-menu/ContextMenuRadioGroup.vue 0.5 kB
<script setup lang="ts"> import type { ContextMenuRadioGroupEmits, ContextMenuRadioGroupProps } from 'reka-ui' import { ContextMenuRadioGroup, useForwardPropsEmits } from 'reka-ui' const props = defineProps<ContextMenuRadioGroupProps>() const emits = defineEmits<ContextMenuRadioGroupEmits>() const forwarded = useForwardPropsEmits(props, emits) </script> <template> <ContextMenuRadioGroup data-uipkge data-slot="context-menu-radio-group" v-bind="forwarded"> <slot /> </ContextMenuRadioGroup> </template> -
app/components/ui/context-menu/ContextMenuRadioItem.vue 1.5 kB
<script setup lang="ts"> import type { ContextMenuRadioItemEmits, ContextMenuRadioItemProps } from 'reka-ui' import type { HTMLAttributes } from 'vue' import { reactiveOmit } from '@vueuse/core' import { Circle } from 'lucide-vue-next' import { ContextMenuItemIndicator, ContextMenuRadioItem, useForwardPropsEmits } from 'reka-ui' import { cn } from '@/lib/utils' const props = defineProps<ContextMenuRadioItemProps & { class?: HTMLAttributes['class'] }>() const emits = defineEmits<ContextMenuRadioItemEmits>() const delegatedProps = reactiveOmit(props, 'class') const forwarded = useForwardPropsEmits(delegatedProps, emits) </script> <template> <ContextMenuRadioItem data-uipkge data-slot="context-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"> <ContextMenuItemIndicator> <slot name="indicator-icon"> <Circle class="size-2 fill-current" /> </slot> </ContextMenuItemIndicator> </span> <slot /> </ContextMenuRadioItem> </template> -
app/components/ui/context-menu/ContextMenuSeparator.vue 0.6 kB
<script setup lang="ts"> import type { ContextMenuSeparatorProps } from 'reka-ui' import type { HTMLAttributes } from 'vue' import { reactiveOmit } from '@vueuse/core' import { ContextMenuSeparator } from 'reka-ui' import { cn } from '@/lib/utils' const props = defineProps<ContextMenuSeparatorProps & { class?: HTMLAttributes['class'] }>() const delegatedProps = reactiveOmit(props, 'class') </script> <template> <ContextMenuSeparator data-uipkge data-slot="context-menu-separator" v-bind="delegatedProps" :class="cn('bg-border -mx-1 my-1 h-px', props.class)" /> </template> -
app/components/ui/context-menu/ContextMenuShortcut.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="context-menu-shortcut" :class="cn('text-muted-foreground ml-auto text-xs tracking-widest', props.class)" > <slot /> </span> </template> -
app/components/ui/context-menu/ContextMenuSub.vue 0.4 kB
<script setup lang="ts"> import type { ContextMenuSubEmits, ContextMenuSubProps } from 'reka-ui' import { ContextMenuSub, useForwardPropsEmits } from 'reka-ui' const props = defineProps<ContextMenuSubProps>() const emits = defineEmits<ContextMenuSubEmits>() const forwarded = useForwardPropsEmits(props, emits) </script> <template> <ContextMenuSub data-uipkge data-slot="context-menu-sub" v-bind="forwarded"> <slot /> </ContextMenuSub> </template> -
app/components/ui/context-menu/ContextMenuSubContent.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 { ContextMenuSubContent, 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> <ContextMenuSubContent data-uipkge data-slot="context-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-context-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg', props.class, ) " > <slot /> </ContextMenuSubContent> </template> -
app/components/ui/context-menu/ContextMenuSubTrigger.vue 1.2 kB
<script setup lang="ts"> import type { ContextMenuSubTriggerProps } from 'reka-ui' import type { HTMLAttributes } from 'vue' import { reactiveOmit } from '@vueuse/core' import { ChevronRight } from 'lucide-vue-next' import { ContextMenuSubTrigger, useForwardProps } from 'reka-ui' import { cn } from '@/lib/utils' const props = defineProps<ContextMenuSubTriggerProps & { class?: HTMLAttributes['class']; inset?: boolean }>() const delegatedProps = reactiveOmit(props, 'class') const forwardedProps = useForwardProps(delegatedProps) </script> <template> <ContextMenuSubTrigger data-uipkge data-slot="context-menu-sub-trigger" :data-inset="inset ? '' : undefined" v-bind="forwardedProps" :class=" cn( 'focus:bg-accent focus:text-accent-foreground focus-visible:ring-ring data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center 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" /> </ContextMenuSubTrigger> </template> -
app/components/ui/context-menu/ContextMenuTrigger.vue 0.4 kB
<script setup lang="ts"> import type { ContextMenuTriggerProps } from 'reka-ui' import { ContextMenuTrigger, useForwardProps } from 'reka-ui' const props = defineProps<ContextMenuTriggerProps>() const forwardedProps = useForwardProps(props) </script> <template> <ContextMenuTrigger data-uipkge data-slot="context-menu-trigger" v-bind="forwardedProps"> <slot /> </ContextMenuTrigger> </template> -
app/components/ui/context-menu/index.ts 1 kB
export { default as ContextMenu } from './ContextMenu.vue' export { default as ContextMenuCheckboxItem } from './ContextMenuCheckboxItem.vue' export { default as ContextMenuContent } from './ContextMenuContent.vue' export { default as ContextMenuGroup } from './ContextMenuGroup.vue' export { default as ContextMenuItem } from './ContextMenuItem.vue' export { default as ContextMenuLabel } from './ContextMenuLabel.vue' export { default as ContextMenuRadioGroup } from './ContextMenuRadioGroup.vue' export { default as ContextMenuRadioItem } from './ContextMenuRadioItem.vue' export { default as ContextMenuSeparator } from './ContextMenuSeparator.vue' export { default as ContextMenuShortcut } from './ContextMenuShortcut.vue' export { default as ContextMenuSub } from './ContextMenuSub.vue' export { default as ContextMenuSubContent } from './ContextMenuSubContent.vue' export { default as ContextMenuSubTrigger } from './ContextMenuSubTrigger.vue' export { default as ContextMenuTrigger } from './ContextMenuTrigger.vue'
Raw manifest: https://uipkge.dev/r/vue/context-menu.json