Components / Overlays & Dialogs / Context Menu
Context Menu beta
The right-click menu, written as a component instead of an accident of the browser.
(or press Shift+F10)
<div style="max-width:430px">
<div class="k-ctx-zone" data-ctx-zone tabindex="0">Right-click in this area<br>(or press Shift+F10)</div>
<div class="k-ctx" data-ctx role="menu" hidden>
<button type="button" role="menuitem"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2.5 12S6 5.5 12 5.5 21.5 12 21.5 12 18 18.5 12 18.5 2.5 12 2.5 12Z"/><circle cx="12" cy="12" r="3"/></svg> Open preview <span class="hint">⏎</span></button>
<button type="button" role="menuitem"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="12" height="12" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg> Duplicate <span class="hint">⌘D</span></button>
<button type="button" role="menuitem"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10 14a4 4 0 0 0 6 0l3-3a4 4 0 1 0-6-6l-1.5 1.5"/><path d="M14 10a4 4 0 0 0-6 0l-3 3a4 4 0 1 0 6 6l1.5-1.5"/></svg> Copy link</button>
<hr>
<button type="button" role="menuitem"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3v12m-5-5 5 5 5-5"/><path d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2"/></svg> Download</button>
<button type="button" role="menuitem" class="danger"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 7h16M9 7V4h6v3M6.5 7l1 12a2 2 0 0 0 2 2h5a2 2 0 0 0 2-2l1-12"/><path d="M10 11.5v5.5M14 11.5v5.5"/></svg> Delete <span class="hint">⌫</span></button>
</div>
</div>.k-ctx-zone{border:1.5px dashed var(--k-border-strong);border-radius:14px;min-height:168px;display:grid;place-items:center;font-family:var(--k-font);color:var(--k-ink-3);font-size:13px;text-align:center;padding:16px}
.k-ctx{position:fixed;z-index:90;width:224px;background:var(--k-bg);border:1px solid var(--k-line);border-radius:12px;box-shadow:0 20px 44px -12px rgba(16,24,40,.28);padding:5px;font-family:var(--k-font);text-align:left}
.k-ctx[hidden]{display:none}
.k-ctx button{display:flex;align-items:center;gap:9px;width:100%;padding:8px 10px;border:none;background:transparent;border-radius:8px;font:inherit;font-size:13px;color:var(--k-ink);cursor:pointer}
.k-ctx button:hover,.k-ctx button:focus-visible{background:var(--k-bg-soft);outline:none}
.k-ctx button .hint{margin-left:auto;font-family:var(--k-mono);font-size:11px;color:var(--k-ink-3)}
.k-ctx button.danger{color:var(--k-danger)}
.k-ctx hr{border:none;border-top:1px solid var(--k-line);margin:5px 4px}
const ctxZone = document.querySelector('[data-ctx-zone]');
const ctxMenu = document.querySelector('[data-ctx]');
function ctxOpen(x, y) {
ctxMenu.hidden = false;
const r = ctxMenu.getBoundingClientRect();
ctxMenu.style.left = Math.min(x, window.innerWidth - r.width - 10) + 'px';
ctxMenu.style.top = Math.min(y, window.innerHeight - r.height - 10) + 'px';
const first = ctxMenu.querySelector('button');
if (first) first.focus();
}
function ctxClose() { ctxMenu.hidden = true; }
ctxZone.addEventListener('contextmenu', e => { e.preventDefault(); ctxOpen(e.clientX, e.clientY); });
ctxZone.addEventListener('keydown', e => {
if (e.key === 'F10' && e.shiftKey || e.key === 'ContextMenu') {
e.preventDefault();
const r = ctxZone.getBoundingClientRect();
ctxOpen(r.left + 24, r.top + 24);
}
});
ctxMenu.querySelectorAll('button').forEach(b => b.addEventListener('click', ctxClose));
document.addEventListener('click', e => { if (!ctxMenu.contains(e.target)) ctxClose(); });
document.addEventListener('keydown', e => { if (e.key === 'Escape') { ctxClose(); ctxZone.focus(); } });
window.addEventListener('blur', ctxClose);
Usage
For dense surfaces — file rows, canvas items, table cells — where actions belong to the thing under the cursor. Always mirror the same actions somewhere visible; a context menu is a shortcut, never the only door.
Accessibility
Opens on the contextmenu event and on Shift+F10 / the Menu key for keyboard users, takes focus, and returns it on Esc. Items are real buttons.
Related parts
Alert Dialog beta
The decision that cannot be clicked away — for destructive, irreversible actions.
Command beta
The ⌘K palette: one input that searches actions and pages, driven from the keyboard.
Drawer beta
A panel that slides in from the edge — detail and forms without losing the page.
Dropdown stable
Opens with a soft scale. Headings, dividers and a red danger item included.