material-menu

Vertical MD3 menu. Anchored to a trigger via popovertarget; flips and scrolls inside the viewport.

Basic — leading icons + trailing shortcut text

Click the trigger; the menu opens anchored below it.

Selection — checkbox items keep the menu open

Unread Starred Muted

Long list — clamps to viewport and scrolls

Type-ahead: repeat the same first letter with a pause between presses (e.g. "s", pause, "s", pause, "s") to cycle Saudi ArabiaSingaporeSlovakiaSouth AfricaSouth KoreaSpainSwedenSwitzerland. Space mid-word (e.g. "new", Space, "z" for "New Zealand") stays in the buffer instead of closing the menu.

Edge of viewport — flips above

Selection log

(none)
Sample usage
<!-- Trigger anywhere on the page; popovertarget points at the menu's id. -->
<material-button label="Edit…" trailing-icon="expand_more"
                 popovertarget="my-menu"></material-button>

<material-menu id="my-menu" anchor="#my-trigger">
  <material-menu-item label="Cut"   leading-icon="content_cut"   trailing-text="⌘X" value="cut"></material-menu-item>
  <material-menu-item label="Copy"  leading-icon="content_copy"  trailing-text="⌘C" value="copy"></material-menu-item>
  <material-menu-item label="Paste" leading-icon="content_paste" trailing-text="⌘V" value="paste"></material-menu-item>
  <material-menu-item label="Delete" leading-icon="delete" disabled value="delete"></material-menu-item>
  <material-menu-item label="Settings…" leading-icon="settings" divider="top" value="settings"></material-menu-item>
</material-menu>

<!-- Listen for selections (event bubbles + composed). -->
<script>
  document.getElementById('my-menu').addEventListener('materialMenuSelect', e => {
    console.log('selected', e.detail.value);
  });
</script>

<!-- Checkbox items: keep-open prevents auto-close; click anywhere on the row toggles the box. -->
<material-menu id="filters" anchor="#filter-btn">
  <material-menu-item keep-open value="unread">
    <material-checkbox slot="leading"></material-checkbox>
    Unread
  </material-menu-item>
  <material-menu-item keep-open value="starred">
    <material-checkbox slot="leading" checked></material-checkbox>
    Starred
  </material-menu-item>
  <material-menu-item label="Apply" trailing-icon="check" divider="top" value="apply"></material-menu-item>
</material-menu>

<!-- Long lists: max-height clamps; viewport room is the other ceiling. -->
<material-menu id="picker" anchor="#picker-btn" max-height="320">…</material-menu>

<!-- Placement: bottom-start (default) | bottom-end | top-start | top-end.
     Auto-flips to the opposite side when it would overflow the viewport. -->
<material-menu placement="bottom-end" offset="8">…</material-menu>

<!-- Programmatic open/close. The menu uses the HTML Popover API under the hood. -->
<script>
  const menu = document.getElementById('my-menu');
  menu.show();        // or: menu.open = true
  menu.hide();        // or: menu.open = false
  menu.addEventListener('materialMenuOpen',  () => console.log('opened'));
  menu.addEventListener('materialMenuClose', () => console.log('closed'));
</script>

<!-- Item anatomy -->
<material-menu-item
    label="Notifications"
    leading-icon="notifications"
    trailing-icon="chevron_right"
    supporting-text="3 new this week"
    value="notifications"></material-menu-item>

<!-- Slots override the icon/text props for richer content (avatars, switches, etc.) -->
<material-menu-item value="profile">
  <img slot="leading" src="/avatar.png" class="w-6 h-6 rounded-full" />
  Mikhail
  <span slot="trailing" class="text-xs">Online</span>
</material-menu-item>