@ozankurt/context-menu is a JavaScript UI library that renders customized right-click context menus into the browser prime layer.
The library mounts floating panels utilizing the HTML Popover API to stop container clipping from overflow: hidden, coordinate distortion from CSS transforms, and z-index collisions.
It runs as a zero-dependency TypeScript engine with devoted adapters for jQuery, React, and Vue.
Options
- Zero runtime dependencies within the core library.
- jQuery, React, Vue, and vanilla JavaScript entry factors.
- One delegated listener for selector-based menu registrations.
- High-layer menu placement by the browser Popover API.
- Nested submenus with synchronous or asynchronous merchandise lists.
- Normal actions, separators, headings, checkboxes, radio selections, and customized content material.
- Keyboard navigation, type-ahead search, and contact lengthy press.
- Conditional hidden and disabled actions from aspect metadata.
- Effervescent DOM occasions for framework-independent lifecycle dealing with.
- CSS customized properties, darkish themes, RTL format, and reduced-motion dealing with.
How To Use It
jQuery
Load the stylesheet first, adopted by jQuery and the worldwide context menu construct.
<hyperlink rel="stylesheet" href="https://unpkg.com/@ozankurt/context-menu/dist/kinds.css" > <script src="/path/to/cdn/jquery.min.js"></script> <script src="https://unpkg.com/@ozankurt/context-menu/dist/context-menu.world.js"></script>
If jQuery masses after the context menu construct, register the adapter as soon as:
ContextMenu.registerJQueryPlugin(jQuery);
For package-based initiatives:
npm set up @ozankurt/context-menu jquery
import $ from 'jquery';
import { registerJQueryPlugin } from '@ozankurt/context-menu/jquery';
import '@ozankurt/context-menu/kinds.css';
registerJQueryPlugin($);
Primary Utilization
Add the weather that ought to open the context menu:
<div class="document-row" data-document-id="42" data-title="Quarterly finances.xlsx" data-locked="false" > Quarterly finances.xlsx </div> <div class="document-row" data-document-id="57" data-title="Contract.pdf" data-locked="true" > Contract.pdf </div>
Initialize the plugin:
$('.document-row').contextMenu({
header: perform (meta) {
return meta.title;
},
objects: [
{
label: 'Open',
hint: 'Enter',
action: function (meta) {
console.log('Open document', meta.documentId);
}
},
{
label: 'Rename',
disabled: function (meta) {
return meta.locked === true;
},
action: function (meta) {
console.log('Rename document', meta.documentId);
}
},
{ type: 'separator' },
{
label: 'Delete',
variant: 'danger',
disabled: function (meta) {
return meta.locked === true;
},
action: function (meta) {
console.log('Delete document', meta.documentId);
}
}
]
});
Open or destroy the menu programmatically when wanted:
// Open on the aspect edge.
$('.document-row').contextMenu('open');
// Open at coordinates from a mouse occasion.
$('.document-row').contextMenu('open', occasion);
// Take away the registration.
$('.document-row').contextMenu('destroy');
Vanilla JavaScript
Browser Script
Load the CSS and world JavaScript construct:
<hyperlink rel="https://unpkg.com/@ozankurt/context-menu/dist/kinds.css" > <script src="https://unpkg.com/@ozankurt/context-menu/dist/context-menu.world.js"></script>
Add any parts you need to match:
<button class="project-row" sort="button" data-id="12" data-name="Shopper Portal" data-locked="false" > Shopper Portal </button>
Create a menu and register a CSS selector:
var menu = ContextMenu.createContextMenu();
menu.register({
on: '.project-row',
header: perform (meta) {
return meta.identify;
},
objects: [
{
label: 'Open project',
action: function (meta) {
console.log('Open project', meta.id);
}
},
{
label: 'Rename',
disabled: function (meta) {
return meta.locked === 'true';
}
},
{ type: 'separator' },
{
label: 'Delete',
variant: 'danger',
action: function (meta) {
console.log('Delete project', meta.id);
}
}
]
});
Provide resolveMeta when software code wants typed values or richer state:
menu.register({
on: '.project-row',
resolveMeta: perform (el) {
return {
id: Quantity(el.dataset.id),
identify: el.dataset.identify,
locked: el.dataset.locked === 'true'
};
},
objects: [
{
label: 'Rename',
disabled: function (meta) {
return meta.locked;
}
}
]
});
For one particular aspect, use connect():
var dispose = menu.connect(
doc.querySelector('#project-actions'),
{
objects: [
{
label: 'Open settings',
action: function () {
openSettings();
}
}
]
}
);
// Take away the registration later.
dispose();
npm
npm set up @ozankurt/context-menu
import { createContextMenu } from '@ozankurt/context-menu';
import '@ozankurt/context-menu/kinds.css';
const menu = createContextMenu();
menu.register({
on: '.project-row',
objects: [
{
label: 'Open project',
action: (meta) => console.log(meta.id)
}
]
});
React
Set up the bundle:
npm set up @ozankurt/context-menu
Import the React hook and stylesheet:
import { useContextMenu } from '@ozankurt/context-menu/react';
import '@ozankurt/context-menu/kinds.css';
Cross software information by meta and connect the returned ref to the aspect:
const menuConfig = {
header: (challenge) => challenge.identify,
objects: [
{
label: 'Open project',
action: (project) => {
console.log('Open', project.id);
}
},
{
label: 'Rename',
disabled: (project) => project.locked
},
{ type: 'separator' },
{
label: 'Delete',
variant: 'danger',
action: (project) => {
console.log('Delete', project.id);
}
}
]
};
perform ProjectRow({ challenge }) {
const { ref } = useContextMenu(
{
...menuConfig,
meta: challenge
},
[project]
);
return (
<button ref={ref} sort="button">
{challenge.identify}
</button>
);
}
The hook additionally returns open and shut for click on buttons or keyboard-driven instructions:
perform ProjectActions({ challenge }) {
const { ref, open, shut } = useContextMenu(
{
...menuConfig,
meta: challenge
},
[project]
);
return (
<button ref={ref} sort="button" onClick={open}>
Undertaking actions
</button>
);
}
Wrap a part of an software in ContextMenuProvider when it wants customized occasion settings:
import { ContextMenuProvider } from '@ozankurt/context-menu/react';
perform App() {
return (
<ContextMenuProvider
choices={{
closeOnScroll: false,
offset: { x: 4, y: 4 }
}}
>
<ProjectList />
</ContextMenuProvider>
);
}
For TypeScript parts akin to buttons, move the aspect sort when required:
const { ref } = useContextMenu<HTMLButtonElement>(
{
...menuConfig,
meta: challenge
},
[project]
);
Vue
Set up the bundle:
npm set up @ozankurt/context-menu
The Vue directive is the quickest setup for a component that already has its information object within the template.
<script setup>
import { vContextMenu } from '@ozankurt/context-menu/vue';
import '@ozankurt/context-menu/kinds.css';
defineProps({
challenge: Object
});
const projectMenu = {
header: (challenge) => challenge.identify,
objects: [
{
label: 'Open project',
action: (project) => {
console.log('Open', project.id);
}
},
{
label: 'Rename',
disabled: (project) => project.locked
},
{ type: 'separator' },
{
label: 'Delete',
variant: 'danger',
action: (project) => {
console.log('Delete', project.id);
}
}
]
};
</script>
<template>
<button
sort="button"
v-context-menu="{ ...projectMenu, meta: challenge }"
>
{{ challenge.identify }}
</button>
</template>
Register the directive throughout the applying with ContextMenuPlugin:
import { createApp } from 'vue';
import { ContextMenuPlugin } from '@ozankurt/context-menu/vue';
import '@ozankurt/context-menu/kinds.css';
import App from './App.vue';
createApp(App)
.use(ContextMenuPlugin, {
closeOnScroll: false
})
.mount('#app');
The composable works when menu registration will not be tied on to directive markup:
import { ref } from 'vue';
import { useContextMenu } from '@ozankurt/context-menu/vue';
const projectButton = ref(null);
const {
open,
shut,
dispose
} = useContextMenu(
{
objects: projectMenu.objects
},
{
goal: projectButton
}
);
Use on within the second argument for delegated selector binding:
useContextMenu(
{
objects: projectMenu.objects
},
{
on: '.project-row'
}
);
Configuration Choices
ContextMenu Occasion Choices
root(HTMLElement | Doc): Delegation root. Default:doc.offset({ x: quantity, y: quantity }): Horizontal and vertical distance from the anchor. Default:{ x: 2, y: 2 }.closeOnScroll(boolean): Closes the menu when an ancestor of the set off scrolls. Default:true.longPress(quantity | false): Contact maintain delay in milliseconds. Default:500.className(string): Further courses utilized to panels created by the occasion.zIndexFallback(quantity): Fastened-position fallback z-index when Popover is unavailable. Default:2147483000.
Core Menu Definition
on(string): Delegated CSS selector.objects(Merchandise[] | ItemsResolver): Menu objects or a synchronous/asynchronous resolver.resolveMeta((el, occasion) => Meta): Builds metadata when the menu opens.header(string | (meta, ctx) => string): Textual content above the merchandise checklist.class(string | string[]): Customized courses utilized to the panel.variant(string): Panel-leveldata-variantworth.offset({ x: quantity, y: quantity }): Per-menu offset override.precedence(quantity): Resolves definitions that match the similar aspect.
Adapter Fields
React, Vue, and jQuery can move software objects by:
meta(Report<string, unknown>): Object handed on to predicates, labels, and actions.
The jQuery adapter additionally accepts:
occasion(ContextMenu): Registers parts on a particular ContextMenu occasion.
The Vue plugin accepts:
directiveName(string): World directive identify. Default:'context-menu'.occasion(ContextMenu): Reuses an present ContextMenu occasion.
The Vue composable binding accepts:
goal(HTMLElement | Ref | getter): Factor used for direct binding.on(string): Delegated selector.occasion(ContextMenu): ContextMenu occasion used for registration.
Menu Merchandise Choices
label(string | predicate): Merchandise textual content.sort('merchandise' | 'separator' | 'header' | 'checkbox' | 'radio' | 'customized'): Merchandise sort.icon(string): Icon class checklist or trusted markup.variant(string): Merchandise-level variant.class(string | string[]): Customized merchandise courses.trace(string): Proper-aligned helper textual content.disabled(boolean | predicate): Retains the merchandise seen and blocks activation.hidden(boolean | predicate): Removes the merchandise.motion(perform): Runs after choice and menu closure.objects(Merchandise[] | ItemsResolver): Defines a submenu.id(string): Utility merchandise identifier.checked(boolean | predicate): Checkbox state.group(string): Metadata key for radio objects.worth(unknown): Radio worth in contrast towards the group worth.render(perform): Returns anHTMLElementor trusted HTML string for a customized merchandise.
API Strategies
// Create a ContextMenu occasion.
var menu = ContextMenu.createContextMenu({
offset: { x: 4, y: 4 }
});
// Create an occasion by the category.
var secondMenu = new ContextMenu.ContextMenu({
longPress: 650
});
// Register a delegated definition.
// Returns a disposer.
var disposeRegistration = menu.register({
on: '.record-row',
objects: [
{ label: 'Open record' }
]
});
// Connect one aspect.
// Returns a disposer.
var disposeElement = menu.connect(
doc.querySelector('#record-17'),
{
objects: [
{ label: 'Edit record' }
]
}
);
// Open a menu.
// Returns Promise<void>.
menu.open(
{ x: 180, y: 120 },
{
on: '.record-row',
objects: [
{ label: 'Open record' }
]
},
doc.querySelector('.record-row')
);
// Shut the energetic menu.
// Default cause: "api".
menu.shut();
// Subscribe to an occasion.
// Returns an unsubscribe perform.
var offOpen = menu.on('open', perform (occasion) {
console.log(occasion.meta);
});
// Learn the open state.
console.log(menu.isOpen);
// Destroy the occasion.
menu.destroy();
// Take away registrations or subscriptions.
disposeRegistration();
disposeElement();
offOpen();
// Set up the jQuery adapter manually.
ContextMenu.registerJQueryPlugin(jQuery);
// jQuery adapter instructions.
$('.record-row').contextMenu({
objects: [
{ label: 'Open record' }
]
});
$('.record-row').contextMenu('open');
$('.record-row').contextMenu('open', occasion);
$('.record-row').contextMenu('destroy');
Occasions
doc.addEventListener('ctxmenu:beforeopen', perform (e) {
console.log(e.element.meta);
});
doc.addEventListener('ctxmenu:beforeitems', perform (e) {
console.log(e.element.objects);
});
doc.addEventListener('ctxmenu:open', perform (e) {
console.log(e.element.el, e.element.meta);
});
doc.addEventListener('ctxmenu:spotlight', perform (e) {
console.log(e.element.merchandise);
});
doc.addEventListener('ctxmenu:submenu:open', perform (e) {
console.log(e.element.merchandise);
});
doc.addEventListener('ctxmenu:submenu:shut', perform (e) {
console.log(e.element.merchandise);
});
doc.addEventListener('ctxmenu:choose', perform (e) {
console.log(e.element.merchandise);
});
doc.addEventListener('ctxmenu:motion', perform (e) {
console.log(e.element.merchandise);
});
doc.addEventListener('ctxmenu:error', perform (e) {
console.error(e.element.error);
});
doc.addEventListener('ctxmenu:beforeclose', perform (e) {
console.log(e.element.cause);
});
doc.addEventListener('ctxmenu:shut', perform (e) {
console.log(e.element.cause);
});
jQuery code can hearken to these occasions by .on():
$('.record-row').on('ctxmenu:open', perform (e) {
console.log(e.originalEvent.element.meta);
});
Alternate options and Associated Sources
This superior jQuery plugin is developed by OzanKurt. For extra Superior Usages, please test the demo web page or go to the official web site.

