Context Menu
The same menu as Dropdown Menu, opened with a right-click or long-press on an area.
import { ContextMenu, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger,} from "@/components/ui/context-menu";
export default function Demo() { return ( <ContextMenu> <ContextMenuTrigger className="flex h-40 w-72 select-none items-center justify-center rounded-lg border border-dashed border-border text-sm text-muted-foreground"> Right-click an index row </ContextMenuTrigger> <ContextMenuContent className="w-52"> <ContextMenuGroup> <ContextMenuItem>Open</ContextMenuItem> <ContextMenuItem> Copy address <ContextMenuShortcut>⌘C</ContextMenuShortcut> </ContextMenuItem> </ContextMenuGroup> <ContextMenuSeparator /> <ContextMenuSub> <ContextMenuSubTrigger>Share</ContextMenuSubTrigger> <ContextMenuSubContent> <ContextMenuItem>Copy link</ContextMenuItem> <ContextMenuItem>Share card</ContextMenuItem> </ContextMenuSubContent> </ContextMenuSub> <ContextMenuSeparator /> <ContextMenuItem variant="destructive">Hide index</ContextMenuItem> </ContextMenuContent> </ContextMenu> );}<script setup lang="ts">import { ContextMenu, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger,} from "@/components/ui/context-menu";</script>
<template> <ContextMenu> <ContextMenuTrigger class="flex h-40 w-72 select-none items-center justify-center rounded-lg border border-dashed border-border text-sm text-muted-foreground" > Right-click an index row </ContextMenuTrigger> <ContextMenuContent class="w-52"> <ContextMenuGroup> <ContextMenuItem>Open</ContextMenuItem> <ContextMenuItem> Copy address <ContextMenuShortcut>⌘C</ContextMenuShortcut> </ContextMenuItem> </ContextMenuGroup> <ContextMenuSeparator /> <ContextMenuSub> <ContextMenuSubTrigger>Share</ContextMenuSubTrigger> <ContextMenuSubContent> <ContextMenuItem>Copy link</ContextMenuItem> <ContextMenuItem>Share card</ContextMenuItem> </ContextMenuSubContent> </ContextMenuSub> <ContextMenuSeparator /> <ContextMenuItem variant="destructive">Hide index</ContextMenuItem> </ContextMenuContent> </ContextMenu></template><script lang="ts"> import * as ContextMenu from "$lib/components/ui/context-menu";</script>
<ContextMenu.Root> <ContextMenu.Trigger class="flex h-40 w-72 select-none items-center justify-center rounded-lg border border-dashed border-border text-sm text-muted-foreground" > Right-click an index row </ContextMenu.Trigger> <ContextMenu.Content class="w-52"> <ContextMenu.Group> <ContextMenu.Item>Open</ContextMenu.Item> <ContextMenu.Item> Copy address <ContextMenu.Shortcut>⌘C</ContextMenu.Shortcut> </ContextMenu.Item> </ContextMenu.Group> <ContextMenu.Separator /> <ContextMenu.Sub> <ContextMenu.SubTrigger>Share</ContextMenu.SubTrigger> <ContextMenu.SubContent> <ContextMenu.Item>Copy link</ContextMenu.Item> <ContextMenu.Item>Share card</ContextMenu.Item> </ContextMenu.SubContent> </ContextMenu.Sub> <ContextMenu.Separator /> <ContextMenu.Item variant="destructive">Hide index</ContextMenu.Item> </ContextMenu.Content></ContextMenu.Root>Installation
Section titled “Installation”npx shadcn@latest add @edmi-ui/context-menupnpm dlx shadcn@latest add @edmi-ui/context-menuyarn dlx shadcn@latest add @edmi-ui/context-menubunx shadcn@latest add @edmi-ui/context-menuPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Install the dependencies:
Terminal window npm install @base-ui/reactTerminal window pnpm add @base-ui/reactTerminal window yarn add @base-ui/reactTerminal window bun add @base-ui/reactAdd the Edmi components this one builds on:
elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-vue@latest add @edmi-ui/context-menupnpm dlx shadcn-vue@latest add @edmi-ui/context-menuyarn dlx shadcn-vue@latest add @edmi-ui/context-menubunx shadcn-vue@latest add @edmi-ui/context-menuPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Install the dependencies:
Terminal window npm install reka-ui @vueuse/coreTerminal window pnpm add reka-ui @vueuse/coreTerminal window yarn add reka-ui @vueuse/coreTerminal window bun add reka-ui @vueuse/coreAdd the Edmi components this one builds on:
elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/context-menu.jsonpnpm dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/context-menu.jsonyarn dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/context-menu.jsonbunx --bun shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/context-menu.jsonPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Add the Edmi components this one builds on:
elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
import { ContextMenu, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger,} from "@/components/ui/context-menu";
<ContextMenu> <ContextMenuTrigger className="flex h-40 w-72 select-none items-center justify-center rounded-lg border border-dashed border-border text-sm text-muted-foreground"> Right-click an index row </ContextMenuTrigger> <ContextMenuContent className="w-52"> <ContextMenuGroup> <ContextMenuItem>Open</ContextMenuItem> <ContextMenuItem> Copy address <ContextMenuShortcut>⌘C</ContextMenuShortcut> </ContextMenuItem> </ContextMenuGroup> <ContextMenuSeparator /> <ContextMenuSub> <ContextMenuSubTrigger>Share</ContextMenuSubTrigger> <ContextMenuSubContent> <ContextMenuItem>Copy link</ContextMenuItem> <ContextMenuItem>Share card</ContextMenuItem> </ContextMenuSubContent> </ContextMenuSub> <ContextMenuSeparator /> <ContextMenuItem variant="destructive">Hide index</ContextMenuItem> </ContextMenuContent></ContextMenu><script setup lang="ts">import { ContextMenu, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger,} from "@/components/ui/context-menu";</script>
<template> <ContextMenu> <ContextMenuTrigger class="flex h-40 w-72 select-none items-center justify-center rounded-lg border border-dashed border-border text-sm text-muted-foreground" > Right-click an index row </ContextMenuTrigger> <ContextMenuContent class="w-52"> <ContextMenuGroup> <ContextMenuItem>Open</ContextMenuItem> <ContextMenuItem> Copy address <ContextMenuShortcut>⌘C</ContextMenuShortcut> </ContextMenuItem> </ContextMenuGroup> <ContextMenuSeparator /> <ContextMenuSub> <ContextMenuSubTrigger>Share</ContextMenuSubTrigger> <ContextMenuSubContent> <ContextMenuItem>Copy link</ContextMenuItem> <ContextMenuItem>Share card</ContextMenuItem> </ContextMenuSubContent> </ContextMenuSub> <ContextMenuSeparator /> <ContextMenuItem variant="destructive">Hide index</ContextMenuItem> </ContextMenuContent> </ContextMenu></template><script lang="ts"> import * as ContextMenu from "$lib/components/ui/context-menu";</script>
<ContextMenu.Root> <ContextMenu.Trigger class="flex h-40 w-72 select-none items-center justify-center rounded-lg border border-dashed border-border text-sm text-muted-foreground" > Right-click an index row </ContextMenu.Trigger> <ContextMenu.Content class="w-52"> <ContextMenu.Group> <ContextMenu.Item>Open</ContextMenu.Item> <ContextMenu.Item> Copy address <ContextMenu.Shortcut>⌘C</ContextMenu.Shortcut> </ContextMenu.Item> </ContextMenu.Group> <ContextMenu.Separator /> <ContextMenu.Sub> <ContextMenu.SubTrigger>Share</ContextMenu.SubTrigger> <ContextMenu.SubContent> <ContextMenu.Item>Copy link</ContextMenu.Item> <ContextMenu.Item>Share card</ContextMenu.Item> </ContextMenu.SubContent> </ContextMenu.Sub> <ContextMenu.Separator /> <ContextMenu.Item variant="destructive">Hide index</ContextMenu.Item> </ContextMenu.Content></ContextMenu.Root>Component source
Section titled “Component source”"use client";
import { ContextMenu as ContextMenuPrimitive } from "@base-ui/react/context-menu";import { cn } from "cn";import type * as React from "react";import { CaretRightIcon, CheckIcon } from "@phosphor-icons/react";
import { type Elevation, useElevation } from "@/registry/edmi/ui/elevation";
// ✦ depth (v4): overlay role. Natural level is floating in layered mode; flat otherwise.const overlayElevation = { sunken: "", flat: "", raised: "border-transparent shadow-raised", floating: "border-transparent shadow-floating",};
function ContextMenu({ ...props }: ContextMenuPrimitive.Root.Props) { return <ContextMenuPrimitive.Root data-slot="context-menu" {...props} />;}
function ContextMenuPortal({ ...props }: ContextMenuPrimitive.Portal.Props) { return ( <ContextMenuPrimitive.Portal data-slot="context-menu-portal" {...props} /> );}
function ContextMenuTrigger({ className, ...props}: ContextMenuPrimitive.Trigger.Props) { return ( <ContextMenuPrimitive.Trigger data-slot="context-menu-trigger" className={cn("select-none", className)} {...props} /> );}
function ContextMenuContent({ className, align = "start", alignOffset = 4, side = "right", sideOffset = 0, elevation, ...props}: ContextMenuPrimitive.Popup.Props & Pick< ContextMenuPrimitive.Positioner.Props, "align" | "alignOffset" | "side" | "sideOffset" > & { /** ✦ depth: flat 0, raised +1 (bevel), floating +2 (bevel + drop). */ elevation?: Elevation; }) { const level = useElevation(elevation, "overlay"); return ( <ContextMenuPrimitive.Portal> <ContextMenuPrimitive.Positioner className="isolate z-50 outline-none" align={align} alignOffset={alignOffset} side={side} sideOffset={sideOffset} > <ContextMenuPrimitive.Popup data-slot="context-menu-content" className={cn( "z-50 max-h-(--available-height) min-w-40 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-xl border border-border bg-popover p-1.5 text-popover-foreground duration-100 outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-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 data-[open]:animate-in data-[open]:fade-in-0 data-[open]:zoom-in-95 data-[closed]:animate-out data-[closed]:fade-out-0 data-[closed]:zoom-out-95 data-[closed]:overflow-hidden", overlayElevation[level], className, )} {...props} /> </ContextMenuPrimitive.Positioner> </ContextMenuPrimitive.Portal> );}
function ContextMenuGroup({ ...props }: ContextMenuPrimitive.Group.Props) { return ( <ContextMenuPrimitive.Group data-slot="context-menu-group" {...props} /> );}
function ContextMenuLabel({ className, inset, ...props}: ContextMenuPrimitive.GroupLabel.Props & { inset?: boolean;}) { return ( <ContextMenuPrimitive.GroupLabel data-slot="context-menu-label" data-inset={inset} className={cn( "px-2 pt-1.5 pb-1 text-xs font-semibold text-muted-foreground data-[inset]:pl-8", className, )} {...props} /> );}
function ContextMenuItem({ className, inset, variant = "default", ...props}: ContextMenuPrimitive.Item.Props & { inset?: boolean; variant?: "default" | "destructive";}) { return ( <ContextMenuPrimitive.Item data-slot="context-menu-item" data-inset={inset} data-variant={variant} className={cn( "group/menu-item relative flex h-8 cursor-default items-center gap-2.5 rounded-[7px] px-2 text-[13.5px] outline-none select-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[inset]:pl-8 data-[disabled]:pointer-events-none data-[disabled]:opacity-45 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-[15px] [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:text-destructive-text data-[variant=destructive]:data-[highlighted]:bg-destructive-soft data-[variant=destructive]:data-[highlighted]:text-destructive-text data-[variant=destructive]:*:[svg]:text-destructive-text", className, )} {...props} /> );}
function ContextMenuSub({ ...props }: ContextMenuPrimitive.SubmenuRoot.Props) { return ( <ContextMenuPrimitive.SubmenuRoot data-slot="context-menu-sub" {...props} /> );}
function ContextMenuSubTrigger({ className, inset, children, ...props}: ContextMenuPrimitive.SubmenuTrigger.Props & { inset?: boolean;}) { return ( <ContextMenuPrimitive.SubmenuTrigger data-slot="context-menu-sub-trigger" data-inset={inset} className={cn( "relative flex h-8 cursor-default items-center gap-2.5 rounded-[7px] px-2 text-[13.5px] outline-none select-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[inset]:pl-8 data-[disabled]:pointer-events-none data-[disabled]:opacity-45 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-[15px] [&_svg:not([class*='text-'])]:text-muted-foreground data-[popup-open]:bg-accent data-[popup-open]:text-accent-foreground data-[open]:bg-accent data-[open]:text-accent-foreground", className, )} {...props} > {children} <CaretRightIcon className="rtl:rotate-180 ml-auto" /> </ContextMenuPrimitive.SubmenuTrigger> );}
function ContextMenuSubContent({ ...props}: React.ComponentProps<typeof ContextMenuContent>) { return ( <ContextMenuContent data-slot="context-menu-sub-content" side="right" {...props} /> );}
function ContextMenuCheckboxItem({ className, children, checked, inset, ...props}: ContextMenuPrimitive.CheckboxItem.Props & { inset?: boolean;}) { return ( <ContextMenuPrimitive.CheckboxItem data-slot="context-menu-checkbox-item" data-inset={inset} className={cn( "relative flex h-8 cursor-default items-center gap-2.5 rounded-[7px] px-2 text-[13.5px] outline-none select-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[inset]:pl-8 data-[disabled]:pointer-events-none data-[disabled]:opacity-45 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-[15px] [&_svg:not([class*='text-'])]:text-muted-foreground pl-8", className, )} checked={checked} {...props} > <span className="pointer-events-none absolute left-1.5 flex size-4 items-center justify-center"> <ContextMenuPrimitive.CheckboxItemIndicator> <CheckIcon /> </ContextMenuPrimitive.CheckboxItemIndicator> </span> {children} </ContextMenuPrimitive.CheckboxItem> );}
function ContextMenuRadioGroup({ ...props}: ContextMenuPrimitive.RadioGroup.Props) { return ( <ContextMenuPrimitive.RadioGroup data-slot="context-menu-radio-group" {...props} /> );}
function ContextMenuRadioItem({ className, children, inset, ...props}: ContextMenuPrimitive.RadioItem.Props & { inset?: boolean;}) { return ( <ContextMenuPrimitive.RadioItem data-slot="context-menu-radio-item" data-inset={inset} className={cn( "relative flex h-8 cursor-default items-center gap-2.5 rounded-[7px] px-2 text-[13.5px] outline-none select-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[inset]:pl-8 data-[disabled]:pointer-events-none data-[disabled]:opacity-45 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-[15px] [&_svg:not([class*='text-'])]:text-muted-foreground pl-8", className, )} {...props} > <span className="pointer-events-none absolute left-1.5 flex size-4 items-center justify-center"> <ContextMenuPrimitive.RadioItemIndicator> <span className="block size-1.5 rounded-full bg-current" /> </ContextMenuPrimitive.RadioItemIndicator> </span> {children} </ContextMenuPrimitive.RadioItem> );}
function ContextMenuSeparator({ className, ...props}: ContextMenuPrimitive.Separator.Props) { return ( <ContextMenuPrimitive.Separator data-slot="context-menu-separator" className={cn("-mx-1 my-1 h-px bg-border", className)} {...props} /> );}
function ContextMenuShortcut({ className, ...props}: React.ComponentProps<"span">) { return ( <span data-slot="context-menu-shortcut" className={cn( "ml-auto font-mono text-[11.5px] tracking-wide text-muted-foreground", className, )} {...props} /> );}
export { ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger,};<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-slot="context-menu" v-bind="forwarded" > <slot /> </ContextMenuRoot></template><script setup lang="ts">import type { ContextMenuCheckboxItemEmits, ContextMenuCheckboxItemProps } from "reka-ui"import type { HTMLAttributes } from "vue"import { Check } from "@lucide/vue"import { reactiveOmit } from "@vueuse/core"import { ContextMenuCheckboxItem, ContextMenuItemIndicator, useForwardPropsEmits,} from "reka-ui"import { cn } from "@/registry/edmi/lib/utils"
const props = defineProps<ContextMenuCheckboxItemProps & { class?: HTMLAttributes["class"], closeOnSelect?: boolean }>()const emits = defineEmits<ContextMenuCheckboxItemEmits>()
const delegatedProps = reactiveOmit(props, "class", "closeOnSelect")
const forwarded = useForwardPropsEmits(delegatedProps, emits)
// Checkbox and radio items keep the menu open (like Base UI and Bits); pass close-on-select to close it.function keepOpen(event: Event) { if (!props.closeOnSelect) event.preventDefault()}</script>
<template> <ContextMenuCheckboxItem data-slot="context-menu-checkbox-item" v-bind="forwarded" @select="keepOpen" :class="cn( `relative flex h-8 cursor-default items-center gap-2.5 rounded-[7px] px-2 text-[13.5px] outline-none select-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[inset]:pl-8 data-[disabled]:pointer-events-none data-[disabled]:opacity-45 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-[15px] [&_svg:not([class*='text-'])]:text-muted-foreground pl-8`, props.class, )" > <span class="pointer-events-none absolute left-1.5 flex size-4 items-center justify-center"> <ContextMenuItemIndicator> <slot name="indicator-icon"> <Check /> </slot> </ContextMenuItemIndicator> </span> <slot /> </ContextMenuCheckboxItem></template><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 "@/registry/edmi/lib/utils"import { type Elevation, useElevation } from "@/registry/edmi/ui/elevation"
defineOptions({ inheritAttrs: false,})
const props = withDefaults(defineProps<ContextMenuContentProps & { class?: HTMLAttributes["class"], elevation?: Elevation }>(), { elevation: undefined })const emits = defineEmits<ContextMenuContentEmits>()
// ✦ depth (v4): overlay role. Natural level is floating in layered mode; flat otherwise.const overlayElevation = { sunken: '', flat: '', raised: 'border-transparent shadow-raised', floating: 'border-transparent shadow-floating',}const level = useElevation(() => props.elevation, 'overlay')
const delegatedProps = reactiveOmit(props, "class", "elevation")
const forwarded = useForwardPropsEmits(delegatedProps, emits)</script>
<template> <ContextMenuPortal> <ContextMenuContent data-slot="context-menu-content" v-bind="{ ...$attrs, ...forwarded }" :class="cn( `z-50 max-h-(--reka-context-menu-content-available-height) min-w-40 origin-(--reka-context-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-xl border border-border bg-popover p-1.5 text-popover-foreground duration-100 outline-none 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 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=closed]:overflow-hidden`, overlayElevation[level], props.class, )" > <slot /> </ContextMenuContent> </ContextMenuPortal></template><script setup lang="ts">import type { ContextMenuGroupProps } from "reka-ui"import { ContextMenuGroup } from "reka-ui"
const props = defineProps<ContextMenuGroupProps>()</script>
<template> <ContextMenuGroup data-slot="context-menu-group" v-bind="props" > <slot /> </ContextMenuGroup></template><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 "@/registry/edmi/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-slot="context-menu-item" :data-inset="inset ? '' : undefined" :data-variant="variant" v-bind="forwarded" :class="cn( `group/menu-item relative flex h-8 cursor-default items-center gap-2.5 rounded-[7px] px-2 text-[13.5px] outline-none select-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[inset]:pl-8 data-[disabled]:pointer-events-none data-[disabled]:opacity-45 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-[15px] [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:text-destructive-text data-[variant=destructive]:data-[highlighted]:bg-destructive-soft data-[variant=destructive]:data-[highlighted]:text-destructive-text data-[variant=destructive]:*:[svg]:text-destructive-text`, props.class, )" > <slot /> </ContextMenuItem></template><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 "@/registry/edmi/lib/utils"
const props = defineProps<ContextMenuLabelProps & { class?: HTMLAttributes["class"], inset?: boolean }>()
const delegatedProps = reactiveOmit(props, "class")</script>
<template> <ContextMenuLabel data-slot="context-menu-label" :data-inset="inset ? '' : undefined" v-bind="delegatedProps" :class="cn( `px-2 pt-1.5 pb-1 text-xs font-semibold text-muted-foreground data-[inset]:pl-8`, props.class)" > <slot /> </ContextMenuLabel></template><script setup lang="ts">import type { ContextMenuPortalProps } from "reka-ui"import { ContextMenuPortal } from "reka-ui"
const props = defineProps<ContextMenuPortalProps>()</script>
<template> <ContextMenuPortal data-slot="context-menu-portal" v-bind="props" > <slot /> </ContextMenuPortal></template><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-slot="context-menu-radio-group" v-bind="forwarded" > <slot /> </ContextMenuRadioGroup></template><script setup lang="ts">import type { ContextMenuRadioItemEmits, ContextMenuRadioItemProps } from "reka-ui"import type { HTMLAttributes } from "vue"import { reactiveOmit } from "@vueuse/core"import { ContextMenuItemIndicator, ContextMenuRadioItem, useForwardPropsEmits,} from "reka-ui"import { cn } from "@/registry/edmi/lib/utils"
const props = defineProps<ContextMenuRadioItemProps & { class?: HTMLAttributes["class"], closeOnSelect?: boolean }>()const emits = defineEmits<ContextMenuRadioItemEmits>()
const delegatedProps = reactiveOmit(props, "class", "closeOnSelect")
const forwarded = useForwardPropsEmits(delegatedProps, emits)
// Checkbox and radio items keep the menu open (like Base UI and Bits); pass close-on-select to close it.function keepOpen(event: Event) { if (!props.closeOnSelect) event.preventDefault()}</script>
<template> <ContextMenuRadioItem data-slot="context-menu-radio-item" v-bind="forwarded" @select="keepOpen" :class="cn( `relative flex h-8 cursor-default items-center gap-2.5 rounded-[7px] px-2 text-[13.5px] outline-none select-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[inset]:pl-8 data-[disabled]:pointer-events-none data-[disabled]:opacity-45 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-[15px] [&_svg:not([class*='text-'])]:text-muted-foreground pl-8`, props.class, )" > <span class="pointer-events-none absolute left-1.5 flex size-4 items-center justify-center"> <ContextMenuItemIndicator> <slot name="indicator-icon"> <span class="block size-1.5 rounded-full bg-current" /> </slot> </ContextMenuItemIndicator> </span> <slot /> </ContextMenuRadioItem></template><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 "@/registry/edmi/lib/utils"
const props = defineProps<ContextMenuSeparatorProps & { class?: HTMLAttributes["class"] }>()
const delegatedProps = reactiveOmit(props, "class")</script>
<template> <ContextMenuSeparator data-slot="context-menu-separator" v-bind="delegatedProps" :class="cn( `-mx-1 my-1 h-px bg-border`, props.class)" /></template><script setup lang="ts">import type { HTMLAttributes } from "vue"import { cn } from "@/registry/edmi/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"]}>()</script>
<template> <span data-slot="context-menu-shortcut" :class="cn( `ml-auto font-mono text-[11.5px] tracking-wide text-muted-foreground`, props.class)" > <slot /> </span></template><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-slot="context-menu-sub" v-bind="forwarded" > <slot /> </ContextMenuSub></template><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 "@/registry/edmi/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-slot="context-menu-sub-content" v-bind="forwarded" :class=" cn( `z-50 w-auto min-w-36 origin-(--reka-context-menu-content-transform-origin) overflow-hidden rounded-xl border border-border bg-popover p-1.5 text-popover-foreground duration-100 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 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95`, props.class, ) " > <slot /> </ContextMenuSubContent></template><script setup lang="ts">import type { ContextMenuSubTriggerProps } from "reka-ui"import type { HTMLAttributes } from "vue"import { ChevronRight } from "@lucide/vue"import { reactiveOmit } from "@vueuse/core"import { ContextMenuSubTrigger, useForwardProps,} from "reka-ui"import { cn } from "@/registry/edmi/lib/utils"
const props = defineProps<ContextMenuSubTriggerProps & { class?: HTMLAttributes["class"], inset?: boolean }>()
const delegatedProps = reactiveOmit(props, "class")
const forwardedProps = useForwardProps(delegatedProps)</script>
<template> <ContextMenuSubTrigger data-slot="context-menu-sub-trigger" :data-inset="inset ? '' : undefined" v-bind="forwardedProps" :class="cn( `relative flex h-8 cursor-default items-center gap-2.5 rounded-[7px] px-2 text-[13.5px] outline-none select-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[inset]:pl-8 data-[disabled]:pointer-events-none data-[disabled]:opacity-45 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-[15px] [&_svg:not([class*='text-'])]:text-muted-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground`, props.class, )" > <slot /> <ChevronRight class="ml-auto" /> </ContextMenuSubTrigger></template><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-slot="context-menu-trigger" v-bind="forwardedProps" > <slot /> </ContextMenuTrigger></template>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";<script lang="ts"> import { ContextMenu as ContextMenuPrimitive } from "bits-ui"; import CheckIcon from "phosphor-svelte/lib/Check"; import { cn, type WithoutChildrenOrChild } from "$lib/utils.js"; import type { Snippet } from "svelte";
let { ref = $bindable(null), checked = $bindable(false), indeterminate = $bindable(false), class: className, inset, children: childrenProp, closeOnSelect = false, ...restProps }: WithoutChildrenOrChild<ContextMenuPrimitive.CheckboxItemProps> & { inset?: boolean; children?: Snippet; } = $props();</script>
<ContextMenuPrimitive.CheckboxItem bind:ref {closeOnSelect} bind:checked bind:indeterminate data-slot="context-menu-checkbox-item" data-inset={inset} class={cn( "relative flex h-8 cursor-default items-center gap-2.5 rounded-[7px] px-2 text-[13.5px] outline-none select-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[inset]:pl-8 data-[disabled]:pointer-events-none data-[disabled]:opacity-45 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-[15px] [&_svg:not([class*='text-'])]:text-muted-foreground pl-8", className )} {...restProps}> {#snippet children({ checked })} <span class="pointer-events-none absolute left-1.5 flex size-4 items-center justify-center"> {#if checked} <CheckIcon /> {/if} </span> {@render childrenProp?.()} {/snippet}</ContextMenuPrimitive.CheckboxItem><script lang="ts"> import { ContextMenu as ContextMenuPrimitive } from "bits-ui"; import { cn } from "$lib/utils.js"; import type { WithoutChildrenOrChild } from "$lib/utils.js"; import { type Elevation, useElevation } from "$lib/registry/ui/elevation/index.js"; import ContextMenuPortal from "./context-menu-portal.svelte"; import type { ComponentProps } from "svelte";
let { ref = $bindable(null), portalProps, class: className, elevation = "auto", ...restProps }: ContextMenuPrimitive.ContentProps & { /** ✦ depth: flat 0, raised +1 (bevel), floating +2 (bevel + drop). */ elevation?: Elevation; portalProps?: WithoutChildrenOrChild<ComponentProps<typeof ContextMenuPortal>>; } = $props();
// ✦ depth (v4): overlay role. Natural level is floating in layered mode; flat otherwise. const overlayElevation = { sunken: "", flat: "", raised: "border-transparent shadow-raised", floating: "border-transparent shadow-floating", }; const level = useElevation(() => elevation, "overlay");</script>
<ContextMenuPortal {...portalProps}> <ContextMenuPrimitive.Content bind:ref data-slot="context-menu-content" class={cn( "z-50 max-h-(--bits-context-menu-content-available-height) min-w-40 origin-(--bits-context-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-xl border border-border bg-popover p-1.5 text-popover-foreground duration-100 outline-none 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 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=closed]:overflow-hidden", overlayElevation[level.current], className )} {...restProps} /></ContextMenuPortal><script lang="ts"> import { ContextMenu as ContextMenuPrimitive } from "bits-ui"; import { cn } from "$lib/utils.js";
let { ref = $bindable(null), class: className, inset, ...restProps }: ContextMenuPrimitive.GroupHeadingProps & { inset?: boolean; } = $props();</script>
<ContextMenuPrimitive.GroupHeading bind:ref data-slot="context-menu-group-heading" data-inset={inset} class={cn( "px-2 pt-1.5 pb-1 text-xs font-semibold text-muted-foreground data-[inset]:pl-8", className)} {...restProps}/><script lang="ts"> import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
let { ref = $bindable(null), ...restProps }: ContextMenuPrimitive.GroupProps = $props();</script>
<ContextMenuPrimitive.Group bind:ref data-slot="context-menu-group" {...restProps} /><script lang="ts"> import { ContextMenu as ContextMenuPrimitive } from "bits-ui"; import { cn } from "$lib/utils.js";
let { ref = $bindable(null), class: className, inset, variant = "default", ...restProps }: ContextMenuPrimitive.ItemProps & { inset?: boolean; variant?: "default" | "destructive"; } = $props();</script>
<ContextMenuPrimitive.Item bind:ref data-slot="context-menu-item" data-inset={inset} data-variant={variant} class={cn( "group/menu-item relative flex h-8 cursor-default items-center gap-2.5 rounded-[7px] px-2 text-[13.5px] outline-none select-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[inset]:pl-8 data-[disabled]:pointer-events-none data-[disabled]:opacity-45 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-[15px] [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:text-destructive-text data-[variant=destructive]:data-[highlighted]:bg-destructive-soft data-[variant=destructive]:data-[highlighted]:text-destructive-text data-[variant=destructive]:*:[svg]:text-destructive-text", className )} {...restProps}/><script lang="ts"> import { cn, type WithElementRef } from "$lib/utils.js"; import type { HTMLAttributes } from "svelte/elements";
let { ref = $bindable(null), class: className, inset, children, ...restProps }: WithElementRef<HTMLAttributes<HTMLDivElement>> & { inset?: boolean; } = $props();</script>
<div bind:this={ref} data-slot="context-menu-label" data-inset={inset} class={cn( "px-2 pt-1.5 pb-1 text-xs font-semibold text-muted-foreground data-[inset]:pl-8", className)} {...restProps}> {@render children?.()}</div><script lang="ts"> import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
let { ...restProps }: ContextMenuPrimitive.PortalProps = $props();</script>
<ContextMenuPrimitive.Portal {...restProps} /><script lang="ts"> import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
let { ref = $bindable(null), value = $bindable(""), ...restProps }: ContextMenuPrimitive.RadioGroupProps = $props();</script>
<ContextMenuPrimitive.RadioGroup bind:ref bind:value data-slot="context-menu-radio-group" {...restProps}/><script lang="ts"> import { ContextMenu as ContextMenuPrimitive } from "bits-ui"; import { cn, type WithoutChild } from "$lib/utils.js";
let { ref = $bindable(null), class: className, inset, children: childrenProp, closeOnSelect = false, ...restProps }: WithoutChild<ContextMenuPrimitive.RadioItemProps> & { inset?: boolean; } = $props();</script>
<ContextMenuPrimitive.RadioItem bind:ref {closeOnSelect} data-slot="context-menu-radio-item" data-inset={inset} class={cn( "relative flex h-8 cursor-default items-center gap-2.5 rounded-[7px] px-2 text-[13.5px] outline-none select-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[inset]:pl-8 data-[disabled]:pointer-events-none data-[disabled]:opacity-45 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-[15px] [&_svg:not([class*='text-'])]:text-muted-foreground pl-8", className )} {...restProps}> {#snippet children({ checked })} <span class="pointer-events-none absolute left-1.5 flex size-4 items-center justify-center"> {#if checked} <span class="size-1.5 rounded-full bg-current"></span> {/if} </span> {@render childrenProp?.({ checked })} {/snippet}</ContextMenuPrimitive.RadioItem><script lang="ts"> import { ContextMenu as ContextMenuPrimitive } from "bits-ui"; import { cn } from "$lib/utils.js";
let { ref = $bindable(null), class: className, ...restProps }: ContextMenuPrimitive.SeparatorProps = $props();</script>
<ContextMenuPrimitive.Separator bind:ref data-slot="context-menu-separator" class={cn( "-mx-1 my-1 h-px bg-border", className)} {...restProps}/><script lang="ts"> import { cn, type WithElementRef } from "$lib/utils.js"; import type { HTMLAttributes } from "svelte/elements";
let { ref = $bindable(null), class: className, children, ...restProps }: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props();</script>
<span bind:this={ref} data-slot="context-menu-shortcut" class={cn( "ml-auto font-mono text-[11.5px] tracking-wide text-muted-foreground", className)} {...restProps}> {@render children?.()}</span><script lang="ts"> import { ContextMenu as ContextMenuPrimitive } from "bits-ui"; import { cn, type WithoutChildrenOrChild } from "$lib/utils.js"; import ContextMenuPortal from "./context-menu-portal.svelte"; import type { ComponentProps } from "svelte";
let { ref = $bindable(null), class: className, align = "start", alignOffset = -3, portalProps, ...restProps }: ContextMenuPrimitive.SubContentProps & { portalProps?: WithoutChildrenOrChild<ComponentProps<typeof ContextMenuPortal>>; } = $props();</script>
<ContextMenuPortal {...portalProps}> <ContextMenuPrimitive.SubContent bind:ref data-slot="context-menu-sub-content" {align} {alignOffset} class={cn( "z-50 w-auto min-w-36 origin-(--bits-context-menu-content-transform-origin) overflow-hidden rounded-xl border border-border bg-popover p-1.5 text-popover-foreground duration-100 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 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95", className )} {...restProps} /></ContextMenuPortal><script lang="ts"> import { ContextMenu as ContextMenuPrimitive } from "bits-ui"; import CaretRightIcon from "phosphor-svelte/lib/CaretRight"; import { cn, type WithoutChild } from "$lib/utils.js";
let { ref = $bindable(null), class: className, inset, children, ...restProps }: WithoutChild<ContextMenuPrimitive.SubTriggerProps> & { inset?: boolean; } = $props();</script>
<ContextMenuPrimitive.SubTrigger bind:ref data-slot="context-menu-sub-trigger" data-inset={inset} class={cn( "relative flex h-8 cursor-default items-center gap-2.5 rounded-[7px] px-2 text-[13.5px] outline-none select-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[inset]:pl-8 data-[disabled]:pointer-events-none data-[disabled]:opacity-45 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-[15px] [&_svg:not([class*='text-'])]:text-muted-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground", className )} {...restProps}> {@render children?.()} <CaretRightIcon class="ml-auto rtl:rotate-180" /></ContextMenuPrimitive.SubTrigger><script lang="ts"> import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
let { open = $bindable(false), ...restProps }: ContextMenuPrimitive.SubProps = $props();</script>
<ContextMenuPrimitive.Sub bind:open {...restProps} /><script lang="ts"> import { ContextMenu as ContextMenuPrimitive } from "bits-ui"; import { cn } from "$lib/utils.js";
let { ref = $bindable(null), class: className, ...restProps }: ContextMenuPrimitive.TriggerProps = $props();</script>
<ContextMenuPrimitive.Trigger bind:ref data-slot="context-menu-trigger" class={cn("select-none", className)} {...restProps}/><script lang="ts"> import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
let { open = $bindable(false), ...restProps }: ContextMenuPrimitive.RootProps = $props();</script>
<ContextMenuPrimitive.Root bind:open {...restProps} />import Root from "./context-menu.svelte";import CheckboxItem from "./context-menu-checkbox-item.svelte";import Content from "./context-menu-content.svelte";import Group from "./context-menu-group.svelte";import GroupHeading from "./context-menu-group-heading.svelte";import Item from "./context-menu-item.svelte";import Label from "./context-menu-label.svelte";import Portal from "./context-menu-portal.svelte";import RadioGroup from "./context-menu-radio-group.svelte";import RadioItem from "./context-menu-radio-item.svelte";import Separator from "./context-menu-separator.svelte";import Shortcut from "./context-menu-shortcut.svelte";import Sub from "./context-menu-sub.svelte";import SubContent from "./context-menu-sub-content.svelte";import SubTrigger from "./context-menu-sub-trigger.svelte";import Trigger from "./context-menu-trigger.svelte";
export { CheckboxItem, CheckboxItem as ContextMenuCheckboxItem, Content, Content as ContextMenuContent, Group, Group as ContextMenuGroup, GroupHeading, GroupHeading as ContextMenuGroupHeading, Item, Item as ContextMenuItem, Label, Label as ContextMenuLabel, Portal, Portal as ContextMenuPortal, RadioGroup, RadioGroup as ContextMenuRadioGroup, RadioItem, RadioItem as ContextMenuRadioItem, Root, // Root as ContextMenu, Separator, Separator as ContextMenuSeparator, Shortcut, Shortcut as ContextMenuShortcut, Sub, Sub as ContextMenuSub, SubContent, SubContent as ContextMenuSubContent, SubTrigger, SubTrigger as ContextMenuSubTrigger, Trigger, Trigger as ContextMenuTrigger,};Elevation ✦
Section titled “Elevation ✦”See the Elevation guide for the levels, roles and layered mode.
Flat by default. On ContextMenuContent, raised (+1) adds the bevel and floating (+2) the bevel plus one soft drop.
import { ContextMenu, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuTrigger,} from "@/components/ui/context-menu";
const levels = [ { value: "flat", label: "Flat" }, { value: "raised", label: "Raised (+1)" }, { value: "floating", label: "Floating (+2)" },] as const;
export default function Demo() { return ( <div className="flex flex-wrap gap-3"> {levels.map(({ value, label }) => ( <ContextMenu key={value}> <ContextMenuTrigger className="flex h-28 w-48 select-none items-center justify-center rounded-lg border border-dashed border-border text-sm text-muted-foreground"> Right-click: {label} </ContextMenuTrigger> <ContextMenuContent elevation={value} className="w-52"> <ContextMenuGroup> <ContextMenuItem>Open</ContextMenuItem> <ContextMenuItem> Copy address <ContextMenuShortcut>⌘C</ContextMenuShortcut> </ContextMenuItem> </ContextMenuGroup> <ContextMenuSeparator /> <ContextMenuItem variant="destructive">Hide index</ContextMenuItem> </ContextMenuContent> </ContextMenu> ))} </div> );}<script setup lang="ts">import { ContextMenu, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuTrigger,} from "@/components/ui/context-menu";
const levels = [{ value: "flat", label: "Flat" }, { value: "raised", label: "Raised (+1)" }, { value: "floating", label: "Floating (+2)" }] as const;</script>
<template> <div class="flex flex-wrap gap-3"> <ContextMenu v-for="level in levels" :key="level.value"> <ContextMenuTrigger class="flex h-28 w-48 select-none items-center justify-center rounded-lg border border-dashed border-border text-sm text-muted-foreground"> Right-click: {{ level.label }} </ContextMenuTrigger> <ContextMenuContent :elevation="level.value" class="w-52"> <ContextMenuGroup> <ContextMenuItem>Open</ContextMenuItem> <ContextMenuItem> Copy address <ContextMenuShortcut>⌘C</ContextMenuShortcut> </ContextMenuItem> </ContextMenuGroup> <ContextMenuSeparator /> <ContextMenuItem variant="destructive">Hide index</ContextMenuItem> </ContextMenuContent> </ContextMenu> </div></template><script lang="ts"> import * as ContextMenu from "$lib/components/ui/context-menu";
const levels = [{ value: "flat", label: "Flat" }, { value: "raised", label: "Raised (+1)" }, { value: "floating", label: "Floating (+2)" }] as const;</script>
<div class="flex flex-wrap gap-3"> {#each levels as level (level.value)} <ContextMenu.Root> <ContextMenu.Trigger class="flex h-28 w-48 select-none items-center justify-center rounded-lg border border-dashed border-border text-sm text-muted-foreground" > Right-click: {level.label} </ContextMenu.Trigger> <ContextMenu.Content elevation={level.value} class="w-52"> <ContextMenu.Group> <ContextMenu.Item>Open</ContextMenu.Item> <ContextMenu.Item> Copy address <ContextMenu.Shortcut>⌘C</ContextMenu.Shortcut> </ContextMenu.Item> </ContextMenu.Group> <ContextMenu.Separator /> <ContextMenu.Item variant="destructive">Hide index</ContextMenu.Item> </ContextMenu.Content> </ContextMenu.Root> {/each}</div>| Prop | Type | Default |
|---|---|---|
ContextMenuContent elevation |
"auto" | "sunken" | "flat" | "raised" | "floating" |
"auto" |
Same parts and props as Dropdown Menu, prefixed ContextMenu: ContextMenu, ContextMenuTrigger, ContextMenuContent, ContextMenuGroup, ContextMenuLabel, ContextMenuItem (variant default | destructive), ContextMenuCheckboxItem, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubTrigger, ContextMenuSubContent.
✦ Edmi additions
Section titled “✦ Edmi additions”- Same recipe as Dropdown Menu:
data-[highlighted]items, left-hand indicators, optionalelevationon the content.