Composite
Menu Requires JS
A dropdown list of actions, anchored to a trigger. Use it for "More options" buttons, account menus, and contextual action lists. Each item runs a command — unlike Select, which holds state.
About
Overview
A Menu is a transient floating list opened by a trigger button (or a right-click for the context-menu variant). Each item is an action: clicking runs it and closes the menu. Items can be grouped with separators, labelled, or marked as destructive.
Menu vs Select. Menu = "do something". Select = "choose something". A menu item disappears the moment you click it; a select option stays as the chosen state.
Parts
Anatomy
Three layers: the trigger that opens the menu, the menu itself, and the items inside (with optional icons, shortcuts, descriptions, and dangerous-action styling).
Assembled
Live
Demo
<button data-menu-trigger="actions">More</button>
<ren-menu id="actions">
<div class="ren-menu-label">Project</div>
<button class="ren-menu-item" role="menuitem">
<span class="ren-menu-item-icon">…</span>
<span>Rename</span>
<span class="ren-menu-item-shortcut">⌘R</span>
</button>
<hr class="ren-menu-separator">
<button class="ren-menu-item ren-menu-item-danger" role="menuitem">
Delete project
</button>
</ren-menu>
Item types
Variants
.ren-menu-item — basic action. Click runs the action and closes the menu.
Add .ren-menu-item-icon at the start and .ren-menu-item-shortcut at the end. The icon renders muted; the shortcut uses mono type.
.ren-menu-item-danger tints the item red on hover/highlight. Pair with a confirmation Dialog for irreversible actions.
.ren-menu-label — non-interactive section header (uppercase, muted). Use to group related items.
.ren-menu-separator or a plain <hr> with that class — divides groups visually.
Add aria-disabled="true" on a menuitem to gray it out and skip it during keyboard navigation.
Reference
API
CSS classes
| Class | Effect |
|---|---|
.ren-menu | The floating menu container. Use with role="menu". Designed to live inside a popover (or be positioned by the component). |
.ren-menu-item | One action. role="menuitem". Best as a <button type="button">. |
.ren-menu-item-icon | Leading icon inside an item. 16 px square, muted color. |
.ren-menu-item-shortcut | Trailing keyboard shortcut text. Mono font, muted. |
.ren-menu-item-description | Optional second line under the item label. Smaller, muted. |
.ren-menu-item-danger | Destructive styling. Red text + red hover background. |
.ren-menu-label | Section heading inside the menu. Uppercase, muted, non-interactive. |
.ren-menu-separator | Thin divider between groups. |
Web Component attributes
| Attribute | Type | Default | Notes |
|---|---|---|---|
data-menu-trigger | string (id) | — | On any element. Click opens the matching <ren-menu>. |
placement | "bottom-start" | "bottom-end" | "top-start" | "top-end" | "right-start" | "right-end" | "left-start" | "left-end" | "bottom-start" | Where the menu floats relative to the trigger. Reflected to data-side and data-align. |
Events
| Event | Detail |
|---|---|
ren-menu-select | An item was activated. { item, value }. |
ren-menu-open / ren-menu-close | Menu opened / closed. |
Inclusive by default
Accessibility
Implements the WAI-ARIA Menu Button pattern.
Keyboard
Pair destructive items with a confirmation. A menu item that deletes data should open a Dialog with explicit confirm/cancel — never delete on first click.
Patterns
Examples
Account menu
<button class="ren-btn ren-btn-ghost" data-menu-trigger="account">
<img src="/avatar.jpg" class="ren-avatar">
</button>
<ren-menu id="account" placement="bottom-end">
<div class="ren-menu-label">Signed in as ren@example.com</div>
<button class="ren-menu-item">Profile</button>
<button class="ren-menu-item">Settings</button>
<button class="ren-menu-item">Help & feedback</button>
<hr class="ren-menu-separator">
<button class="ren-menu-item">Sign out</button>
</ren-menu>
Run an action
document.querySelector('ren-menu').addEventListener('ren-menu-select', (e) => {
const action = e.detail.item.dataset.action;
switch (action) {
case 'rename': openRenameDialog(); break;
case 'duplicate': duplicateProject(); break;
case 'delete': confirmDelete(); break;
}
});