---
title: "JavaScript Context Menu"
description: "The LemonadeJS Context Menu block for JavaScript: Right-click context menu. Contract-verified, framework-agnostic, zero dependencies, with a live example."
source: https://lemonadejs.com/docs/plugins/contextmenu/
---

<link rel="stylesheet" href="/v6/modal.css">
<link rel="stylesheet" href="/v6/contextmenu.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined">

# JavaScript Context Menu

`@lemonadejs/contextmenu` · ✓ 6 contract checks · framework-agnostic · zero dependencies

`<Contextmenu />` — built ON the Modal primitive, exactly like v5:
every menu level is a headerless, auto-adjusting Modal. Submenus flip
horizontally when out of space (inheriting the parent's direction),
correct vertical overflow, open on a 200ms hover delay — and the full
v5 keyboard system: ArrowUp/Down cursor skipping disabled items and
separators with wrap-around, Home/End jump to the first/last enabled
item, ArrowRight into a submenu (cursor on its first enabled item),
ArrowLeft back out, Enter/Space activates, Escape closes everything —
keyboard closes hand focus back to the invoker (WCAG 2.4.3), and
aria-activedescendant on the focused wrapper tracks the cursor.

v5 → v6 mapping: open(options, x, y) and openAt(x, y | event) keep
their signatures; the per-item render() DOM hook was dropped.

## Example

<!--example-->

```js
import { html, ref } from 'lemonadejs';
import Contextmenu from '@lemonadejs/contextmenu';

const FILES = ['budget-2026.xlsx', 'launch-plan.docx', 'logo-final.svg'];

const App = (props, { state }) => {
    const menu = ref();
    const last = state('');
    let target = '';

    const options = [
        { title: 'Open', shortcut: 'Enter', onclick: () => (last.value = 'Opened ' + target) },
        { title: 'Rename', shortcut: 'F2', onclick: () => (last.value = 'Renamed ' + target) },
        { type: 'line' },
        { title: 'Delete', shortcut: 'Del', onclick: () => (last.value = 'Deleted ' + target) },
    ];

    return html`<div>
        <p style="margin:0 0 6px">Right-click a file:</p>
        ${FILES.map((name) => html`<div oncontextmenu="${(e) => { target = name; menu.current.openAt(e); }}"
            style="padding:8px 12px;border:1px solid #ccc;margin-bottom:4px;cursor:context-menu">${name}</div>`)}
        <p>${last}</p>
        <${Contextmenu} ref="${menu}" options="${options}" />
    </div>`;
};
```

## Installation

```bash
npm install @lemonadejs/contextmenu
```

```js
import Contextmenu from '@lemonadejs/contextmenu';
import '@lemonadejs/contextmenu/style.css';
```

The icons come from Google Material Symbols. Load the font once per page:

```html
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined">
```

Three deployment forms, one component:

```js
html`<${Contextmenu} />`                       // by value (no registration)
setComponents({ Contextmenu });               // then <Contextmenu /> by name anywhere
createWebComponent(Contextmenu);              // <lm-contextmenu> in plain HTML/any framework
```

## Props

Every declared prop arrives as a **live state** — pass a value for a snapshot or a
state for a two-way live wire. Attribute strings are coerced to the declared type.

| Prop | Type | Default | Description |
|---|---|---|---|
| `options` | array | — |  |

## Events

All event names are lowercase (the platform convention — LJS-305 warns otherwise).

- `onopen`
- `onclose`

## API

```js
import { ref } from 'lemonadejs';
const contextmenu = ref();
html`<${Contextmenu} ref="${contextmenu}" />`;
// contextmenu.current.open(...)  ·  contextmenu.current.openAt(...)  ·  contextmenu.current.close(...)
```

- `open()`
- `openAt()`
- `close()`

## Styling

All classes follow the `lm-contextmenu-*` convention; visual variants are `data-*`
attributes on the root. Override freely — there is no styling engine to fight.

## Contract

The machine-readable schema ships with the package:

```js
import contract from '@lemonadejs/contextmenu/contract.json';
```

`verify.json` carries the conformance proof produced by `verify(Contextmenu)`.

Looking for the v5 plugin? See the [archived v5 documentation](/docs/v5/plugins/).