Zero-dependency dropdown action menus with the full menu-button keyboard pattern — arrows, typeahead, submenus, context menus. Try the keyboard: focus a trigger and press ArrowDown.
<script src="https://cdn.jsdelivr.net/gh/vanilla-ui-kit/components/menu/menu.js"></script>
A trigger button and a list of actions. Esc closes, outside click closes.
new Menu('#m-basic', {
items: [
{ label: 'Rename', onSelect: fn },
{ label: 'Duplicate', onSelect: fn },
{ label: 'Move to…', onSelect: fn }
]
})
Trusted SVG icons, right-aligned hints, red destructive actions; disabled items are skipped by the arrow keys.
{ label: 'Copy', icon: svg, hint: '⌘C' }
{ label: 'Paste', disabled: true }
{ label: 'Delete', danger: true,
hint: '⌫', onSelect: fn }
{ type: 'separator' } groups related actions.
items: [
{ label: 'New file' },
{ label: 'New window' },
{ type: 'separator' },
{ label: 'Close' }
]
One level of flyout — hover or ArrowRight to open, ArrowLeft to come back.
{ label: 'Share',
items: [
{ label: 'Copy link' },
{ label: 'Email' },
{ label: 'Embed' }
] }
Right-click (or long-press) the area — Menu.open(x, y, items)
flips inward at viewport edges.
el.addEventListener('contextmenu', e => {
e.preventDefault()
Menu.open(e.clientX, e.clientY, items)
})
data-vmn points at a template; no JS needed.
Selections fire a menu:select event.
<button data-vmn="#tpl-view">View</button> <template id="tpl-view"> <button>Zoom in</button> <hr> <button data-hint="⌘0">Reset zoom</button> </template>