A comprehensive context menu component library for Vue 3 applications with themes, keyboard navigation, and programmatic control
npx @tessl/cli install tessl/npm-imengyu--vue3-context-menu@1.5.0Vue3 Context Menu is a comprehensive context menu component library for Vue 3 applications that enables developers to create customizable right-click menus with rich functionality. It offers both functional and component-based APIs for displaying context menus, supports nested submenus and separators, provides multiple built-in themes with both light and dark variants, and includes keyboard navigation support.
npm install @imengyu/vue3-context-menuimport ContextMenu from '@imengyu/vue3-context-menu';
import '@imengyu/vue3-context-menu/lib/vue3-context-menu.css';
// For functional API
import ContextMenu from '@imengyu/vue3-context-menu';
// For component imports
import {
ContextMenu as ContextMenuComponent,
ContextMenuItem,
ContextMenuSeparator,
ContextMenuGroup,
MenuBar
} from '@imengyu/vue3-context-menu';For CommonJS:
const ContextMenu = require('@imengyu/vue3-context-menu');
require('@imengyu/vue3-context-menu/lib/vue3-context-menu.css');import { createApp } from 'vue';
import ContextMenu from '@imengyu/vue3-context-menu';
import '@imengyu/vue3-context-menu/lib/vue3-context-menu.css';
const app = createApp(App);
app.use(ContextMenu);import ContextMenu from '@imengyu/vue3-context-menu';
function onContextMenu(e: MouseEvent) {
e.preventDefault();
ContextMenu.showContextMenu({
x: e.x,
y: e.y,
items: [
{
label: "Copy",
icon: "copy-icon",
onClick: () => console.log("Copy clicked")
},
{
label: "Edit",
children: [
{ label: "Cut", onClick: () => console.log("Cut clicked") },
{ label: "Paste", onClick: () => console.log("Paste clicked") },
]
},
{ divided: true, label: "Delete", onClick: () => console.log("Delete clicked") }
]
});
}<template>
<div @contextmenu="onRightClick">
<context-menu v-model:show="show" :options="menuOptions">
<context-menu-item label="Copy" icon="copy-icon" @click="handleCopy" />
<context-menu-separator />
<context-menu-group label="Edit">
<context-menu-item label="Cut" @click="handleCut" />
<context-menu-item label="Paste" @click="handlePaste" />
</context-menu-group>
</context-menu>
</div>
</template>
<script setup>
import { ref } from 'vue';
const show = ref(false);
const menuOptions = ref({
x: 0,
y: 0,
zIndex: 1000
});
function onRightClick(e) {
e.preventDefault();
menuOptions.value.x = e.x;
menuOptions.value.y = e.y;
show.value = true;
}
</script>Vue3 Context Menu is built around several key components:
showContextMenu, closeContextMenu)ContextMenu, ContextMenuItem, etc.)Core programmatic interface for displaying context menus with full configuration control. Perfect for dynamic menus and event-driven scenarios.
interface ContextMenuGlobal {
showContextMenu(options: MenuOptions, customSlots?: Record<string, Slot>): ContextMenuInstance;
closeContextMenu(): void;
isAnyContextMenuOpen(): boolean;
transformMenuPosition(element: HTMLElement, x: number, y: number, container?: HTMLElement): { x: number, y: number };
}
interface MenuOptions {
/** Menu items array */
items?: MenuItem[];
/** Menu display X position */
x: number;
/** Menu display Y position */
y: number;
/** X-coordinate offset of submenu and parent menu */
xOffset?: number;
/** Y-coordinate offset of submenu and parent menu */
yOffset?: number;
/** Theme name (default, flat, win10, mac, with optional 'dark' suffix) */
theme?: string;
/** Menu pop-up direction relative to coordinates */
direction?: MenuPopDirection;
/** Z-index for menu display */
zIndex?: number;
/** Menu zoom level */
zoom?: number;
/** Custom CSS class for menu */
customClass?: string;
/** Enable mouse scroll wheel in menu area */
mouseScroll?: boolean;
/** Provide space placeholder for up/down buttons */
updownButtonSpaceholder?: boolean;
/** Element class name to ignore clicks */
ignoreClickClassName?: string;
/** Close menu when clicking outside */
clickCloseOnOutside?: boolean;
/** Element class name that closes menu when clicked */
clickCloseClassName?: string;
/** Custom icon library font class name */
iconFontClass?: string;
/** Vue Transition props for menu show/hide */
menuTransitionProps?: TransitionProps;
/** Reserve fixed-width icon area for items without icon */
preserveIconWidth?: boolean;
/** Enable keyboard control */
keyboardControl?: boolean;
/** Maximum width of menu (pixels) */
maxWidth?: number;
/** Maximum height of menu (pixels) */
maxHeight?: number;
/** Minimum width of menu (pixels) */
minWidth?: number;
/** Close when user scrolls */
closeWhenScroll?: boolean;
/** Padding for submenu position adjustment */
adjustPadding?: { x: number, y: number } | number;
/** Automatically adjust position to prevent overflow */
adjustPosition?: boolean;
/** Container element for menu mounting */
getContainer?: HTMLElement | (() => HTMLElement);
/** Event when menu is closing */
onClose?: (lastClickItem: MenuItem | undefined) => void;
/** Event when clicking outside (when clickCloseOnOutside is false) */
onClickOnOutside?: (e: MouseEvent) => void;
/** Event for MenuBar left focus move */
onKeyFocusMoveLeft?: () => void;
/** Event for MenuBar right focus move */
onKeyFocusMoveRight?: () => void;
}Vue components for declarative menu construction with full template integration. Ideal for static menus and component-based architectures.
// Main context menu component
declare component ContextMenu {
props: {
options: MenuOptions;
show: boolean;
};
events: {
'update:show': (show: boolean) => void;
'close': () => void;
};
}
// Menu item component
declare component ContextMenuItem {
props: {
label?: string;
icon?: string;
disabled?: boolean;
hidden?: boolean;
checked?: boolean;
shortcut?: string;
};
events: {
'click': (e: MouseEvent | KeyboardEvent) => void;
};
}
// Menu separator component
declare component ContextMenuSeparator {
// No props
}
// Menu group component for nested submenus
declare component ContextMenuGroup {
props: {
label: string;
};
// Contains slot for child menu items
}Traditional horizontal menu bar component for desktop-style applications with dropdown menus.
declare component MenuBar {
props: {
options: MenuBarOptions;
};
}
interface MenuBarOptions extends Omit<MenuOptions, 'x'|'y'|'getContainer'> {
mini?: boolean;
barPopDirection?: MenuPopDirection;
}Built-in theme system with light and dark variants for different UI styles.
type ThemeNames =
| 'default'
| 'default dark'
| 'flat'
| 'flat dark'
| 'win10'
| 'win10 dark'
| 'mac'
| 'mac dark';// Vue framework types used throughout the API
interface VNode {
// Vue virtual node for custom renders
}
interface ComputedRef<T> {
// Vue computed reference
readonly value: T;
}
interface Slot {
// Vue slot function
(...args: any[]): VNode[];
}type MenuPopDirection = 'br'|'b'|'bl'|'tr'|'t'|'tl'|'l'|'r';
interface MenuItem {
/** Menu item label */
label?: string | VNode | ((label: string) => VNode);
/** Menu item icon */
icon?: string | VNode | ((icon: string) => VNode);
/** Custom icon font class name */
iconFontClass?: string;
/** Reserve fixed-width icon area */
preserveIconWidth?: boolean;
/** SVG symbol icon reference */
svgIcon?: string;
/** SVG element properties */
svgProps?: SVGAttributes;
/** Disable menu item */
disabled?: boolean | ComputedRef<boolean>;
/** Hide menu item */
hidden?: boolean | ComputedRef<boolean>;
/** Show check mark */
checked?: boolean | ComputedRef<boolean>;
/** Shortcut key display text */
shortcut?: string;
/** Submenu popup direction */
direction?: MenuPopDirection;
/** Adjust submenu position */
adjustSubMenuPosition?: boolean;
/** Allow click when has children */
clickableWhenHasChildren?: boolean;
/** Close menu on click */
clickClose?: boolean;
/** Separator display option */
divided?: boolean | 'up' | 'down' | 'self';
/** Custom CSS class */
customClass?: string;
/** Maximum height in pixels */
maxHeight?: number;
/** Maximum width in pixels */
maxWidth?: number | string;
/** Minimum width in pixels */
minWidth?: number | string;
/** Click event handler */
onClick?: (e?: MouseEvent | KeyboardEvent) => void;
/** Submenu close event */
onSubMenuClose?: (itemInstance?: MenuItemContext) => void;
/** Submenu open event */
onSubMenuOpen?: (itemInstance?: MenuItemContext) => void;
/** Custom render function */
customRender?: VNode | ((item: MenuItem) => VNode);
/** Child menu items */
children?: MenuItem[];
}
interface ContextMenuInstance {
/** Close the menu */
closeMenu(fromItem?: MenuItem | undefined): void;
/** Check if menu is closed */
isClosed(): boolean;
/** Get root menu instance */
getMenuRef(): ContextSubMenuInstance | undefined;
/** Get menu dimensions */
getMenuDimensions(): { width: number, height: number };
}
interface ContextSubMenuInstance {
/** Get submenu root element */
getSubmenuRoot(): HTMLElement | undefined;
/** Get menu container element */
getMenu(): HTMLElement | undefined;
/** Get child menu item by index */
getChildItem(index: number): MenuItemContext | undefined;
/** Get menu dimensions */
getMenuDimensions(): { width: number, height: number };
/** Get current scroll value */
getScrollValue(): number;
/** Set scroll value */
setScrollValue(v: number): void;
/** Get scroll height */
getScrollHeight(): number;
/** Force adjust position */
adjustPosition(): void;
/** Get maximum height */
getMaxHeight(): number;
/** Get current position */
getPosition(): { x: number, y: number };
/** Set position */
setPosition(x: number, y: number): void;
}
interface MenuItemContext {
/** Get submenu instance */
getSubMenuInstance(): ContextSubMenuInstance | undefined;
/** Show submenu */
showSubMenu(): boolean;
/** Hide submenu */
hideSubMenu(): void;
/** Get HTML element */
getElement(): HTMLElement | undefined;
/** Check if disabled or hidden */
isDisabledOrHidden(): boolean;
/** Focus item */
focus(): void;
/** Blur item */
blur(): void;
/** Click item */
click(e: MouseEvent | KeyboardEvent): void;
}
interface SVGAttributes {
[key: string]: any;
}
interface TransitionProps {
name?: string;
mode?: 'in-out' | 'out-in' | 'default';
appear?: boolean;
css?: boolean;
type?: 'transition' | 'animation';
duration?: number | { enter: number; leave: number };
enterFromClass?: string;
enterActiveClass?: string;
enterToClass?: string;
appearFromClass?: string;
appearActiveClass?: string;
appearToClass?: string;
leaveFromClass?: string;
leaveActiveClass?: string;
leaveToClass?: string;
}