78 UI Kit
Components

Menu

A trigger and a panel. Click to open; outside click, Escape or choosing an item closes it; the arrow keys move between items. The menu / menuitem ARIA and focus return are handled for you.

The markup contract

A _78-menu-wrap containing a trigger with aria-haspopup="true" and a _78-menu panel. Give the trigger an id and point the panel at it with aria-labelledby, so the menu is announced with the name of the thing that opened it.

dropdown menu
<div class="_78-menu-wrap">
  <button class="_78-btn" id="m-1" type="button" aria-haspopup="true">
    Actions
    <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M6 9l6 6 6-6"/></svg>
  </button>
  <div class="_78-menu" aria-labelledby="m-1">
    <a class="_78-menu-item" href="#basics">Duplicate</a>
    <a class="_78-menu-item" href="#basics">Move to&hellip;</a>
    <div class="_78-menu-sep"></div>
    <a class="_78-menu-item" href="#basics">Archive</a>
  </div>
</div>

Items can be <a> or <button>. Use a link when it navigates and a button when it acts — the menu treats both the same.

Item variants

An inline <svg> as the item's first child is picked up as an icon slot: sized to 16px and dimmed, so a menu of icons stays quiet until hovered. _78-danger on an item colors it and its icon with --danger, for the one destructive entry. _78-menu-sep is a hairline divider.

menu items with icons
<div class="_78-menu-wrap">
  <button class="_78-btn" id="m-2" type="button" aria-haspopup="true">
    File
    <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M6 9l6 6 6-6"/></svg>
  </button>
  <div class="_78-menu" aria-labelledby="m-2">
    <button class="_78-menu-item" type="button">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg>
      New file
    </button>
    <button class="_78-menu-item" type="button">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5h10"/></svg>
      Duplicate
    </button>
    <div class="_78-menu-sep"></div>
    <button class="_78-menu-item _78-danger" type="button">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M3 6h18M8 6V4h8v2M19 6l-1 14H6L5 6"/></svg>
      Delete
    </button>
  </div>
</div>

Alignment

A menu opens right-aligned to its trigger, which is what you want for a control near the right edge of a topbar. _78-menu-left flips it to open leftward, for a trigger on the left of the screen where the default would run off.

_78-menu-left
<div class="_78-menu-wrap">
  <button class="_78-btn" id="m-3" type="button" aria-haspopup="true">Opens leftward</button>
  <div class="_78-menu _78-menu-left" aria-labelledby="m-3">
    <a class="_78-menu-item" href="#alignment">Rename</a>
    <a class="_78-menu-item" href="#alignment">Export</a>
  </div>
</div>

JavaScript API

Menus present at load mount themselves. Mount one you built later, and close any that are open — useful before navigating, or when a keyboard shortcut takes over.

_78.shell menu API
// Menus live on _78.shell, alongside the rest of the app-shell behavior.
_78.shell.mountMenu(wrapEl);      // mount one ._78-menu-wrap
_78.shell.mountMenus(scope);      // mount every unmounted menu inside scope
_78.shell.closeMenus();           // close whatever is open
MemberWhat it is
_78.shell.mountMenu(wrap)Wire one _78-menu-wrap.
_78.shell.mountMenus(scope)Wire every unmounted menu inside scope (default document).
_78.shell.closeMenus()Close all open menus.
The menu is not shell-only — it lives in _78.shell because that is where the account menu in the topbar needed it, but it is a general dropdown you can use anywhere.