Open demo

CSS code

HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
  <symbol id="i-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="m5 12.5 4.5 4.5L19 7.5"/></symbol>
  <symbol id="i-pencil" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 20h4L19 9a2.8 2.8 0 0 0-4-4L4 16z"/><path d="m13.5 6.5 4 4"/></symbol>
  <symbol id="i-copy" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="8.5" y="8.5" width="12" height="12" rx="2"/><path d="M15.5 8.5V5a1.5 1.5 0 0 0-1.5-1.5H5A1.5 1.5 0 0 0 3.5 5v9A1.5 1.5 0 0 0 5 15.5h3.5"/></symbol>
  <symbol id="i-trash" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7h16M10 11v6M14 11v6M5.5 7l1 12a1.5 1.5 0 0 0 1.5 1.5h8a1.5 1.5 0 0 0 1.5-1.5l1-12M9 7V4.5h6V7"/></symbol>
  <symbol id="i-mail" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3.5" y="5.5" width="17" height="13" rx="2"/><path d="m4 7 8 6 8-6"/></symbol>
  <symbol id="i-user" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="8" r="4"/><path d="M4.5 20.5c.8-4 3.7-6 7.5-6s6.7 2 7.5 6"/></symbol>
  <symbol id="i-users" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="8.5" r="3.5"/><path d="M2.5 20c.6-3.6 3.2-5.5 6.5-5.5s5.9 1.9 6.5 5.5"/><path d="M16 5.2a3.3 3.3 0 0 1 0 6.6M18 14.8c2 .6 3.2 2.3 3.5 5.2"/></symbol>
  <symbol id="i-help" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="8.5"/><path d="M9.6 9.5a2.5 2.5 0 0 1 4.8 1c0 1.7-2.4 2.2-2.4 3.7"/><path d="M12 17.3h.01"/></symbol>
  <symbol id="i-dots" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="5.5" cy="12" r="1.4" fill="currentColor" stroke="none"/><circle cx="12" cy="12" r="1.4" fill="currentColor" stroke="none"/><circle cx="18.5" cy="12" r="1.4" fill="currentColor" stroke="none"/></symbol>
  <symbol id="i-image" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3.5" y="4.5" width="17" height="15" rx="2.5"/><circle cx="9" cy="10" r="1.8"/><path d="m20.5 16-5-5-9 8.5"/></symbol>
  <symbol id="i-star" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="m12 4 2.5 5 5.5.8-4 3.9.9 5.5L12 16.6l-4.9 2.6.9-5.5-4-3.9L9.5 9z"/></symbol>
  <symbol id="i-eye" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><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"/></symbol>
</svg>
<div class="full"><span class="lbl">Overflow menus on list rows</span>
<div class="stage" style="--stage-h:300px">
<ul class="cx-wrap cx-list" aria-label="Team members">
<li class="cx-row"><span class="cx-av" style="background:linear-gradient(135deg,#7c3aed,#be185d)" aria-hidden="true">LF</span><span class="cx-who"><b>Lena Fischer</b><small>[email protected]</small></span><span class="cx-role">Owner</span><span class="cx-seen">Now</span><div class="cx-dd">
<button class="cx-more" id="cx-b1" type="button" aria-label="Actions for Lena Fischer" aria-haspopup="menu" aria-expanded="false" aria-controls="cx-m1"><svg aria-hidden="true"><use href="#i-dots"/></svg></button>
<div class="cx-menu" role="menu" id="cx-m1" aria-label="Actions for Lena Fischer" hidden>
<a href="#lena" class="cx-item" role="menuitem" tabindex="-1"><svg aria-hidden="true"><use href="#i-user"/></svg>View profile</a>
<button type="button" class="cx-item" role="menuitem" tabindex="-1"><svg aria-hidden="true"><use href="#i-users"/></svg>Change role…</button>
<button type="button" class="cx-item" role="menuitem" tabindex="-1"><svg aria-hidden="true"><use href="#i-copy"/></svg>Copy email</button>
<div class="cx-sep" role="separator"></div>
<button type="button" class="cx-item" role="menuitem" tabindex="-1" aria-disabled="true" aria-labelledby="cx-m1-t4" aria-describedby="cx-m1-d4"><svg aria-hidden="true"><use href="#i-trash"/></svg><span id="cx-m1-t4">Remove from team</span><span class="cx-hint2" id="cx-m1-d4">Owner</span></button>
</div>
</div></li>
<li class="cx-row"><span class="cx-av" style="background:linear-gradient(135deg,#0369a1,#0f766e)" aria-hidden="true">TR</span><span class="cx-who"><b>Tomás Rivera</b><small>[email protected]</small></span><span class="cx-role">Admin</span><span class="cx-seen">2 hours ago</span><div class="cx-dd">
<button class="cx-more" id="cx-b2" type="button" aria-label="Actions for Tomás Rivera" aria-haspopup="menu" aria-expanded="false" aria-controls="cx-m2"><svg aria-hidden="true"><use href="#i-dots"/></svg></button>
<div class="cx-menu" role="menu" id="cx-m2" aria-label="Actions for Tomás Rivera" hidden>
<a href="#tomás" class="cx-item" role="menuitem" tabindex="-1"><svg aria-hidden="true"><use href="#i-user"/></svg>View profile</a>
<button type="button" class="cx-item" role="menuitem" tabindex="-1"><svg aria-hidden="true"><use href="#i-users"/></svg>Change role…</button>
<button type="button" class="cx-item" role="menuitem" tabindex="-1"><svg aria-hidden="true"><use href="#i-copy"/></svg>Copy email</button>
<div class="cx-sep" role="separator"></div>
<button type="button" class="cx-item danger" role="menuitem" tabindex="-1"><svg aria-hidden="true"><use href="#i-trash"/></svg>Remove from team</button>
</div>
</div></li>
<li class="cx-row"><span class="cx-av" style="background:linear-gradient(135deg,#b45309,#9f1239)" aria-hidden="true">AT</span><span class="cx-who"><b>Aiko Tanaka</b><small>[email protected]</small></span><span class="cx-role">Member</span><span class="cx-seen">Yesterday</span><div class="cx-dd">
<button class="cx-more" id="cx-b3" type="button" aria-label="Actions for Aiko Tanaka" aria-haspopup="menu" aria-expanded="false" aria-controls="cx-m3"><svg aria-hidden="true"><use href="#i-dots"/></svg></button>
<div class="cx-menu" role="menu" id="cx-m3" aria-label="Actions for Aiko Tanaka" hidden>
<a href="#aiko" class="cx-item" role="menuitem" tabindex="-1"><svg aria-hidden="true"><use href="#i-user"/></svg>View profile</a>
<button type="button" class="cx-item" role="menuitem" tabindex="-1"><svg aria-hidden="true"><use href="#i-users"/></svg>Change role…</button>
<button type="button" class="cx-item" role="menuitem" tabindex="-1"><svg aria-hidden="true"><use href="#i-copy"/></svg>Copy email</button>
<div class="cx-sep" role="separator"></div>
<button type="button" class="cx-item danger" role="menuitem" tabindex="-1"><svg aria-hidden="true"><use href="#i-trash"/></svg>Remove from team</button>
</div>
</div></li>
<li class="cx-row"><span class="cx-av" style="background:linear-gradient(135deg,#475569,#1e293b)" aria-hidden="true">SO</span><span class="cx-who"><b>Sam Okoro</b><small>[email protected]</small></span><span class="cx-role pending">Invited</span><span class="cx-seen">Pending</span><div class="cx-dd">
<button class="cx-more" id="cx-b4" type="button" aria-label="Actions for Sam Okoro" aria-haspopup="menu" aria-expanded="false" aria-controls="cx-m4"><svg aria-hidden="true"><use href="#i-dots"/></svg></button>
<div class="cx-menu" role="menu" id="cx-m4" aria-label="Actions for Sam Okoro" hidden>
<a href="#sam" class="cx-item" role="menuitem" tabindex="-1"><svg aria-hidden="true"><use href="#i-user"/></svg>View profile</a>
<button type="button" class="cx-item" role="menuitem" tabindex="-1"><svg aria-hidden="true"><use href="#i-users"/></svg>Change role…</button>
<button type="button" class="cx-item" role="menuitem" tabindex="-1"><svg aria-hidden="true"><use href="#i-mail"/></svg>Resend invite</button>
<button type="button" class="cx-item" role="menuitem" tabindex="-1"><svg aria-hidden="true"><use href="#i-copy"/></svg>Copy email</button>
<div class="cx-sep" role="separator"></div>
<button type="button" class="cx-item danger" role="menuitem" tabindex="-1"><svg aria-hidden="true"><use href="#i-trash"/></svg>Remove from team</button>
</div>
</div></li>
</ul>
</div></div>
<div class="full"><span class="lbl">Right-click context menu</span>
<div class="stage" style="--stage-h:420px">
<div class="cx-wrap cx-board">
<p class="cx-hint" id="cx-hint"><svg aria-hidden="true"><use href="#i-help"/></svg><span>Right-click a photo, or focus it and press <kbd>Shift</kbd> + <kbd>F10</kbd>.</span></p>
<a class="cx-tile" href="#harbour" data-context aria-haspopup="menu" aria-expanded="false" aria-controls="cx-menu" aria-describedby="cx-hint"><span class="cx-art a1" aria-hidden="true"></span><span class="cx-cap">Harbour at dusk.jpg<small>4.2 MB</small></span></a><a class="cx-tile" href="#lake-bled" data-context aria-haspopup="menu" aria-expanded="false" aria-controls="cx-menu" aria-describedby="cx-hint"><span class="cx-art a2" aria-hidden="true"></span><span class="cx-cap">Lake Bled, morning.jpg<small>3.8 MB</small></span></a><a class="cx-tile" href="#olive-grove" data-context aria-haspopup="menu" aria-expanded="false" aria-controls="cx-menu" aria-describedby="cx-hint"><span class="cx-art a3" aria-hidden="true"></span><span class="cx-cap">Olive grove.jpg<small>5.1 MB</small></span></a>
<div class="cx-menu" role="menu" id="cx-menu" aria-label="Photo actions" hidden>
<button type="button" class="cx-item" role="menuitem" tabindex="-1" aria-keyshortcuts="Enter"><svg aria-hidden="true"><use href="#i-eye"/></svg><span>Open</span><kbd class="cx-kbd" aria-hidden="true">↵</kbd></button>
<button type="button" class="cx-item" role="menuitem" tabindex="-1" aria-keyshortcuts="F2"><svg aria-hidden="true"><use href="#i-pencil"/></svg><span>Rename</span><kbd class="cx-kbd" aria-hidden="true">F2</kbd></button>
<button type="button" class="cx-item" role="menuitem" tabindex="-1" aria-keyshortcuts="Meta+D"><svg aria-hidden="true"><use href="#i-copy"/></svg><span>Duplicate</span><kbd class="cx-kbd" aria-hidden="true">⌘D</kbd></button>
<div class="cx-sep" role="separator"></div>
<button type="button" class="cx-item" role="menuitemcheckbox" tabindex="-1" aria-checked="true"><svg aria-hidden="true"><use href="#i-star"/></svg><span>Favourite</span><svg class="cx-chk" aria-hidden="true"><use href="#i-check"/></svg></button>
<button type="button" class="cx-item" role="menuitem" tabindex="-1"><svg aria-hidden="true"><use href="#i-image"/></svg>Set as album cover</button>
<div class="cx-sep" role="separator"></div>
<button type="button" class="cx-item danger" role="menuitem" tabindex="-1" aria-keyshortcuts="Delete"><svg aria-hidden="true"><use href="#i-trash"/></svg><span>Move to trash</span><kbd class="cx-kbd" aria-hidden="true">⌫</kbd></button>
</div>
</div>
</div></div>
CSS
/* Row "more" buttons are menu buttons (aria-haspopup="menu"); the photo board opens one shared menu at the pointer on right-click. */
.cx-wrap{--cx-ink:#1f1d24;--cx-muted:#5d5965;--cx-line:#e8e5ec;--cx-hover:#f6f2f7;--cx-accent:#be185d;--cx-danger:#b42318;--cx-danger-bg:#fef3f2;font-size:.875rem}
.cx-more{display:grid;place-items:center;width:34px;height:34px;padding:0;border:0;border-radius:9px;background:none;color:var(--cx-muted);cursor:pointer;transition:background-color .15s}
.cx-more:hover,.cx-more[aria-expanded="true"]{background:var(--cx-hover);color:var(--cx-ink)}
.cx-more svg{width:18px;height:18px}
.cx-more:focus-visible{outline:2px solid var(--cx-accent);outline-offset:2px}
.cx-menu{position:absolute;z-index:20;top:calc(100% + 4px);right:0;min-width:212px;padding:6px;background:#fff;border:1px solid var(--cx-line);border-radius:12px;
  box-shadow:0 2px 6px rgba(31,29,36,.06),0 18px 40px -14px rgba(31,29,36,.3);animation:cx-in .12s ease-out;transform-origin:top right}
.cx-board>.cx-menu{right:auto;transform-origin:top left}
.cx-menu[hidden]{display:none}
@keyframes cx-in{from{opacity:0;scale:.96}}
.cx-item{display:flex;align-items:center;gap:10px;width:100%;padding:7px 10px;border:0;border-radius:8px;background:none;color:var(--cx-ink);font:inherit;font-weight:500;
  line-height:1.4;text-align:left;text-decoration:none;white-space:nowrap;cursor:pointer}
.cx-item>svg{width:16px;height:16px;flex:none;color:var(--cx-muted)}
.cx-item:hover,.cx-item:focus{background:var(--cx-hover);outline:none}
.cx-item:focus-visible{outline:2px solid var(--cx-accent);outline-offset:-2px}
.cx-item.danger,.cx-item.danger>svg{color:var(--cx-danger)}
.cx-item.danger:hover,.cx-item.danger:focus{background:var(--cx-danger-bg)}
.cx-item[aria-disabled="true"]{color:#8d8894;cursor:not-allowed}
.cx-item[aria-disabled="true"]:hover{background:none}
.cx-kbd,.cx-hint2{margin-left:auto;padding-left:18px;color:var(--cx-muted);font:500 .75rem ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
.cx-hint2{font-family:inherit}
.cx-sep{height:1px;margin:6px -6px;background:var(--cx-line)}

.cx-list{margin:0;padding:6px;list-style:none;background:#fff;border:1px solid var(--cx-line);border-radius:14px;box-shadow:0 1px 2px rgba(31,29,36,.05)}
.cx-row{display:flex;align-items:center;gap:12px;padding:10px 8px 10px 10px;border-radius:10px}
.cx-row+.cx-row{box-shadow:0 -1px 0 var(--cx-line)}
.cx-av{flex:none;display:grid;place-items:center;width:36px;height:36px;border-radius:50%;color:#fff;font-size:.75rem;font-weight:700}
.cx-who{flex:1;min-width:0}
.cx-who b{display:block;color:var(--cx-ink);font-weight:600;line-height:1.35}
.cx-who small{display:block;overflow:hidden;color:var(--cx-muted);font-size:.8rem;text-overflow:ellipsis;white-space:nowrap}
.cx-role{flex:none;padding:2px 9px;border-radius:999px;background:#f1eef4;color:#48434f;font-size:.72rem;font-weight:600}
.cx-role.pending{background:#fef3c7;color:#854d0e}
.cx-seen{flex:none;width:88px;color:var(--cx-muted);font-size:.8rem;text-align:right}
.cx-dd{position:relative;flex:none}
.cx-board{position:relative;display:grid;grid-template-columns:repeat(3,1fr);gap:16px;padding:16px;background:#fff;border:1px solid var(--cx-line);border-radius:14px;
  box-shadow:0 1px 2px rgba(31,29,36,.05)}
.cx-tile{display:block;border-radius:12px;color:var(--cx-ink);text-decoration:none}
.cx-art{display:block;aspect-ratio:4/3;border-radius:10px;transition:box-shadow .15s}
.cx-tile[aria-expanded="true"] .cx-art{box-shadow:0 0 0 2px #fff,0 0 0 4px var(--cx-accent)}
.cx-tile:focus-visible{outline:2px solid var(--cx-accent);outline-offset:4px}
.cx-cap{display:block;margin-top:8px;font-size:.8rem;font-weight:600;line-height:1.35}
.cx-cap small{display:block;color:var(--cx-muted);font-weight:400}
.cx-hint{grid-column:1/-1;display:flex;align-items:center;gap:8px;margin:0;color:var(--cx-muted);font-size:.8rem}
.cx-hint svg{width:16px;height:16px;flex:none}
.cx-hint kbd{padding:1px 6px;border:1px solid var(--cx-line);border-radius:5px;background:#faf9fb;color:var(--cx-ink);font:600 .72rem ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
.cx-item>.cx-chk{width:16px;height:16px;margin-left:auto;flex:none;color:var(--cx-accent);visibility:hidden}
.cx-item[aria-checked="true"] .cx-chk{visibility:visible}
.cx-board>.cx-menu{top:0;left:0;margin:0}
.a1{background:radial-gradient(circle at 68% 50%,#fde68a 0 7%,transparent 7.5%),linear-gradient(180deg,#312e81,#9d174d 48%,#fb923c 58%,#1e3a8a 58.5%,#0f172a)}
.a2{background:radial-gradient(ellipse at 30% 100%,#134e4a 0 30%,transparent 30.5%),linear-gradient(170deg,#e0f2fe,#7dd3fc 40%,#94a3b8 41%,#475569 62%,#0f766e 63%,#115e59)}
.a3{background:radial-gradient(circle at 25% 28%,#fef9c3 0 9%,transparent 9.5%),radial-gradient(circle at 75% 85%,#365314 0 22%,transparent 22.5%),linear-gradient(180deg,#fde68a,#bef264 55%,#4d7c0f)}
@media (max-width:600px){
  .cx-seen{display:none}
  .cx-board{grid-template-columns:1fr 1fr}
}

@media (prefers-reduced-motion:reduce){.cx-menu{animation:none}.cx-more,.cx-art{transition:none}}
JavaScript
// Menu button (WAI-ARIA APG pattern). A trigger has aria-haspopup="menu", aria-expanded and aria-controls="<menu id>".
// Click, Enter, Space or ArrowDown opens the menu (ArrowUp opens it on the last item). Inside: ArrowUp/ArrowDown move,
// Home/End jump, typing a letter jumps to the next item that starts with it, Esc closes and returns focus to the trigger,
// Tab or a click outside closes. Choosing an item closes the menu; checkbox items stay open so you can tick several.
(() => {
  const OPEN = '[aria-haspopup="menu"][aria-expanded="true"]';
  const menuOf = t => document.getElementById(t.getAttribute('aria-controls'));
  const ownerOf = m => m && document.querySelector(`[aria-controls="${m.id}"][aria-expanded="true"]`);
  const itemsOf = m => [...m.querySelectorAll('[role^="menuitem"]')]
    .filter(i => i.closest('[role="menu"]') === m && i.getAttribute('aria-disabled') !== 'true');

  function close(t, focus) {
    const m = menuOf(t);
    m.querySelectorAll(OPEN).forEach(s => close(s));
    t.setAttribute('aria-expanded', 'false');
    m.hidden = true;
    if (focus) t.focus();
  }
  function open(t, at) {
    const level = t.closest('[role="menu"]');
    document.querySelectorAll(OPEN).forEach(o => { if (o !== t && o.closest('[role="menu"]') === level) close(o); });
    t.setAttribute('aria-expanded', 'true');
    menuOf(t).hidden = false;
    if (at !== undefined) itemsOf(menuOf(t)).at(at)?.focus();
  }
  const closeAll = target => document.querySelectorAll(OPEN).forEach(t => {
    if (!t.closest('[role="menu"]') && !t.contains(target) && !menuOf(t).contains(target)) close(t);
  });

  document.addEventListener('click', e => {
    const t = e.target.closest('[aria-haspopup="menu"][aria-controls]:not([data-context])');
    if (t) {
      if (!t.closest('[role="menu"]') && t.getAttribute('aria-expanded') === 'true') close(t);
      else open(t, e.detail === 0 ? 0 : undefined); // keyboard clicks (Enter/Space) move focus into the menu
      return;
    }
    const item = e.target.closest('[role^="menuitem"]');
    if (item && item.getAttribute('aria-disabled') !== 'true') {
      const role = item.getAttribute('role');
      if (role === 'menuitemcheckbox') { item.setAttribute('aria-checked', item.getAttribute('aria-checked') !== 'true'); return; }
      if (role === 'menuitemradio') {
        const set = item.closest('[role="group"]') || item.closest('[role="menu"]');
        set.querySelectorAll('[role="menuitemradio"]').forEach(r => r.setAttribute('aria-checked', r === item));
      }
      let t = ownerOf(item.closest('[role="menu"]'));
      while (t && t.closest('[role="menu"]')) t = ownerOf(t.closest('[role="menu"]'));
      if (t) close(t, true);
      return;
    }
    closeAll(e.target);
  });

  document.addEventListener('keydown', e => {
    const t = e.target.closest('[aria-haspopup="menu"][aria-controls]:not([data-context])');
    if (t && !t.closest('[role="menu"]')) {
      if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { e.preventDefault(); open(t, e.key === 'ArrowDown' ? 0 : -1); }
      else if (e.key === 'Escape' && t.getAttribute('aria-expanded') === 'true') close(t);
      return;
    }
    const m = e.target.closest('[role="menu"]');
    if (!m) return;
    const items = itemsOf(m), i = items.indexOf(e.target), owner = ownerOf(m);
    let next;
    switch (e.key) {
      case 'ArrowDown': next = items[(i + 1) % items.length]; break;
      case 'ArrowUp': next = items.at(i <= 0 ? -1 : i - 1); break;
      case 'Home': next = items[0]; break;
      case 'End': next = items.at(-1); break;
      case 'Escape': if (owner) { e.preventDefault(); close(owner, true); } return;
      case 'Tab': closeAll(null); return;
      case ' ': if (e.target.tagName === 'A') { e.preventDefault(); e.target.click(); } return;
      default:
        if (e.key.length !== 1 || e.ctrlKey || e.metaKey || e.altKey) return;
        next = [...items.slice(i + 1), ...items.slice(0, i + 1)]
          .find(x => x.textContent.trim().toLowerCase().startsWith(e.key.toLowerCase()));
    }
    e.preventDefault();
    next?.focus();
  });

  // Context menu: right-click, Shift+F10 or the Menu key on a [data-context] element opens its menu at the pointer.
  document.addEventListener('contextmenu', e => {
    const t = e.target.closest('[data-context]');
    if (!t) return;
    e.preventDefault();
    const m = menuOf(t), box = m.parentElement.getBoundingClientRect(), r = t.getBoundingClientRect();
    const x = e.clientX || r.left + 24, y = e.clientY || r.top + 24;
    open(t);
    m.style.left = Math.max(8, Math.min(x - box.left, box.width - m.offsetWidth - 8)) + 'px';
    m.style.top = Math.max(8, Math.min(y - box.top, box.height - m.offsetHeight - 8)) + 'px';
    itemsOf(m)[0]?.focus();
  });
})();

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS, Bootstrap 5.3.8 JS bundle

HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
  <symbol id="i-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="m5 12.5 4.5 4.5L19 7.5"/></symbol>
  <symbol id="i-pencil" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 20h4L19 9a2.8 2.8 0 0 0-4-4L4 16z"/><path d="m13.5 6.5 4 4"/></symbol>
  <symbol id="i-copy" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="8.5" y="8.5" width="12" height="12" rx="2"/><path d="M15.5 8.5V5a1.5 1.5 0 0 0-1.5-1.5H5A1.5 1.5 0 0 0 3.5 5v9A1.5 1.5 0 0 0 5 15.5h3.5"/></symbol>
  <symbol id="i-trash" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7h16M10 11v6M14 11v6M5.5 7l1 12a1.5 1.5 0 0 0 1.5 1.5h8a1.5 1.5 0 0 0 1.5-1.5l1-12M9 7V4.5h6V7"/></symbol>
  <symbol id="i-mail" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3.5" y="5.5" width="17" height="13" rx="2"/><path d="m4 7 8 6 8-6"/></symbol>
  <symbol id="i-user" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="8" r="4"/><path d="M4.5 20.5c.8-4 3.7-6 7.5-6s6.7 2 7.5 6"/></symbol>
  <symbol id="i-users" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="8.5" r="3.5"/><path d="M2.5 20c.6-3.6 3.2-5.5 6.5-5.5s5.9 1.9 6.5 5.5"/><path d="M16 5.2a3.3 3.3 0 0 1 0 6.6M18 14.8c2 .6 3.2 2.3 3.5 5.2"/></symbol>
  <symbol id="i-help" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="8.5"/><path d="M9.6 9.5a2.5 2.5 0 0 1 4.8 1c0 1.7-2.4 2.2-2.4 3.7"/><path d="M12 17.3h.01"/></symbol>
  <symbol id="i-dots" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="5.5" cy="12" r="1.4" fill="currentColor" stroke="none"/><circle cx="12" cy="12" r="1.4" fill="currentColor" stroke="none"/><circle cx="18.5" cy="12" r="1.4" fill="currentColor" stroke="none"/></symbol>
  <symbol id="i-image" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3.5" y="4.5" width="17" height="15" rx="2.5"/><circle cx="9" cy="10" r="1.8"/><path d="m20.5 16-5-5-9 8.5"/></symbol>
  <symbol id="i-star" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="m12 4 2.5 5 5.5.8-4 3.9.9 5.5L12 16.6l-4.9 2.6.9-5.5-4-3.9L9.5 9z"/></symbol>
  <symbol id="i-eye" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><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"/></symbol>
</svg>
<div class="full"><span class="lbl">Overflow menus on list rows</span>
<div class="stage" style="--stage-h:300px">
<ul class="cx-wrap cx-list" aria-label="Team members">
<li class="cx-row"><span class="cx-av" style="background:linear-gradient(135deg,#7c3aed,#be185d)" aria-hidden="true">LF</span><span class="cx-who"><b>Lena Fischer</b><small>[email protected]</small></span><span class="cx-role">Owner</span><span class="cx-seen">Now</span><div class="dropdown cx-dd">
<button class="btn cx-more" id="cx-b1" type="button" data-bs-toggle="dropdown" data-bs-offset="0,4" aria-label="Actions for Lena Fischer" aria-haspopup="menu" aria-expanded="false" aria-controls="cx-m1"><svg aria-hidden="true"><use href="#i-dots"/></svg></button>
<div class="dropdown-menu cx-menu dropdown-menu-end" role="menu" id="cx-m1" aria-label="Actions for Lena Fischer">
<a href="#lena" class="dropdown-item cx-item" role="menuitem" tabindex="-1"><svg aria-hidden="true"><use href="#i-user"/></svg>View profile</a>
<button type="button" class="dropdown-item cx-item" role="menuitem" tabindex="-1"><svg aria-hidden="true"><use href="#i-users"/></svg>Change role…</button>
<button type="button" class="dropdown-item cx-item" role="menuitem" tabindex="-1"><svg aria-hidden="true"><use href="#i-copy"/></svg>Copy email</button>
<hr class="dropdown-divider">
<button type="button" class="dropdown-item cx-item disabled" role="menuitem" tabindex="-1" aria-disabled="true" aria-labelledby="cx-m1-t4" aria-describedby="cx-m1-d4"><svg aria-hidden="true"><use href="#i-trash"/></svg><span id="cx-m1-t4">Remove from team</span><span class="cx-hint2" id="cx-m1-d4">Owner</span></button>
</div>
</div></li>
<li class="cx-row"><span class="cx-av" style="background:linear-gradient(135deg,#0369a1,#0f766e)" aria-hidden="true">TR</span><span class="cx-who"><b>Tomás Rivera</b><small>[email protected]</small></span><span class="cx-role">Admin</span><span class="cx-seen">2 hours ago</span><div class="dropdown cx-dd">
<button class="btn cx-more" id="cx-b2" type="button" data-bs-toggle="dropdown" data-bs-offset="0,4" aria-label="Actions for Tomás Rivera" aria-haspopup="menu" aria-expanded="false" aria-controls="cx-m2"><svg aria-hidden="true"><use href="#i-dots"/></svg></button>
<div class="dropdown-menu cx-menu dropdown-menu-end" role="menu" id="cx-m2" aria-label="Actions for Tomás Rivera">
<a href="#tomás" class="dropdown-item cx-item" role="menuitem" tabindex="-1"><svg aria-hidden="true"><use href="#i-user"/></svg>View profile</a>
<button type="button" class="dropdown-item cx-item" role="menuitem" tabindex="-1"><svg aria-hidden="true"><use href="#i-users"/></svg>Change role…</button>
<button type="button" class="dropdown-item cx-item" role="menuitem" tabindex="-1"><svg aria-hidden="true"><use href="#i-copy"/></svg>Copy email</button>
<hr class="dropdown-divider">
<button type="button" class="dropdown-item cx-item danger" role="menuitem" tabindex="-1"><svg aria-hidden="true"><use href="#i-trash"/></svg>Remove from team</button>
</div>
</div></li>
<li class="cx-row"><span class="cx-av" style="background:linear-gradient(135deg,#b45309,#9f1239)" aria-hidden="true">AT</span><span class="cx-who"><b>Aiko Tanaka</b><small>[email protected]</small></span><span class="cx-role">Member</span><span class="cx-seen">Yesterday</span><div class="dropdown cx-dd">
<button class="btn cx-more" id="cx-b3" type="button" data-bs-toggle="dropdown" data-bs-offset="0,4" aria-label="Actions for Aiko Tanaka" aria-haspopup="menu" aria-expanded="false" aria-controls="cx-m3"><svg aria-hidden="true"><use href="#i-dots"/></svg></button>
<div class="dropdown-menu cx-menu dropdown-menu-end" role="menu" id="cx-m3" aria-label="Actions for Aiko Tanaka">
<a href="#aiko" class="dropdown-item cx-item" role="menuitem" tabindex="-1"><svg aria-hidden="true"><use href="#i-user"/></svg>View profile</a>
<button type="button" class="dropdown-item cx-item" role="menuitem" tabindex="-1"><svg aria-hidden="true"><use href="#i-users"/></svg>Change role…</button>
<button type="button" class="dropdown-item cx-item" role="menuitem" tabindex="-1"><svg aria-hidden="true"><use href="#i-copy"/></svg>Copy email</button>
<hr class="dropdown-divider">
<button type="button" class="dropdown-item cx-item danger" role="menuitem" tabindex="-1"><svg aria-hidden="true"><use href="#i-trash"/></svg>Remove from team</button>
</div>
</div></li>
<li class="cx-row"><span class="cx-av" style="background:linear-gradient(135deg,#475569,#1e293b)" aria-hidden="true">SO</span><span class="cx-who"><b>Sam Okoro</b><small>[email protected]</small></span><span class="cx-role pending">Invited</span><span class="cx-seen">Pending</span><div class="dropdown cx-dd">
<button class="btn cx-more" id="cx-b4" type="button" data-bs-toggle="dropdown" data-bs-offset="0,4" aria-label="Actions for Sam Okoro" aria-haspopup="menu" aria-expanded="false" aria-controls="cx-m4"><svg aria-hidden="true"><use href="#i-dots"/></svg></button>
<div class="dropdown-menu cx-menu dropdown-menu-end" role="menu" id="cx-m4" aria-label="Actions for Sam Okoro">
<a href="#sam" class="dropdown-item cx-item" role="menuitem" tabindex="-1"><svg aria-hidden="true"><use href="#i-user"/></svg>View profile</a>
<button type="button" class="dropdown-item cx-item" role="menuitem" tabindex="-1"><svg aria-hidden="true"><use href="#i-users"/></svg>Change role…</button>
<button type="button" class="dropdown-item cx-item" role="menuitem" tabindex="-1"><svg aria-hidden="true"><use href="#i-mail"/></svg>Resend invite</button>
<button type="button" class="dropdown-item cx-item" role="menuitem" tabindex="-1"><svg aria-hidden="true"><use href="#i-copy"/></svg>Copy email</button>
<hr class="dropdown-divider">
<button type="button" class="dropdown-item cx-item danger" role="menuitem" tabindex="-1"><svg aria-hidden="true"><use href="#i-trash"/></svg>Remove from team</button>
</div>
</div></li>
</ul>
</div></div>
<div class="full"><span class="lbl">Right-click context menu</span>
<div class="stage" style="--stage-h:420px">
<div class="cx-wrap cx-board">
<p class="cx-hint" id="cx-hint"><svg aria-hidden="true"><use href="#i-help"/></svg><span>Right-click a photo, or focus it and press <kbd>Shift</kbd> + <kbd>F10</kbd>.</span></p>
<a class="cx-tile" href="#harbour" data-context aria-haspopup="menu" aria-expanded="false" aria-controls="cx-menu" aria-describedby="cx-hint"><span class="cx-art a1" aria-hidden="true"></span><span class="cx-cap">Harbour at dusk.jpg<small>4.2 MB</small></span></a><a class="cx-tile" href="#lake-bled" data-context aria-haspopup="menu" aria-expanded="false" aria-controls="cx-menu" aria-describedby="cx-hint"><span class="cx-art a2" aria-hidden="true"></span><span class="cx-cap">Lake Bled, morning.jpg<small>3.8 MB</small></span></a><a class="cx-tile" href="#olive-grove" data-context aria-haspopup="menu" aria-expanded="false" aria-controls="cx-menu" aria-describedby="cx-hint"><span class="cx-art a3" aria-hidden="true"></span><span class="cx-cap">Olive grove.jpg<small>5.1 MB</small></span></a>
<div class="dropdown-menu cx-menu" role="menu" id="cx-menu" aria-label="Photo actions">
<button type="button" class="dropdown-item cx-item" role="menuitem" tabindex="-1" aria-keyshortcuts="Enter"><svg aria-hidden="true"><use href="#i-eye"/></svg><span>Open</span><kbd class="cx-kbd" aria-hidden="true">↵</kbd></button>
<button type="button" class="dropdown-item cx-item" role="menuitem" tabindex="-1" aria-keyshortcuts="F2"><svg aria-hidden="true"><use href="#i-pencil"/></svg><span>Rename</span><kbd class="cx-kbd" aria-hidden="true">F2</kbd></button>
<button type="button" class="dropdown-item cx-item" role="menuitem" tabindex="-1" aria-keyshortcuts="Meta+D"><svg aria-hidden="true"><use href="#i-copy"/></svg><span>Duplicate</span><kbd class="cx-kbd" aria-hidden="true">⌘D</kbd></button>
<hr class="dropdown-divider">
<button type="button" class="dropdown-item cx-item" role="menuitemcheckbox" tabindex="-1" aria-checked="true"><svg aria-hidden="true"><use href="#i-star"/></svg><span>Favourite</span><svg class="cx-chk" aria-hidden="true"><use href="#i-check"/></svg></button>
<button type="button" class="dropdown-item cx-item" role="menuitem" tabindex="-1"><svg aria-hidden="true"><use href="#i-image"/></svg>Set as album cover</button>
<hr class="dropdown-divider">
<button type="button" class="dropdown-item cx-item danger" role="menuitem" tabindex="-1" aria-keyshortcuts="Delete"><svg aria-hidden="true"><use href="#i-trash"/></svg><span>Move to trash</span><kbd class="cx-kbd" aria-hidden="true">⌫</kbd></button>
</div>
</div>
</div></div>
CSS
/* Row menus use Bootstrap's Dropdown plugin; the context menu reuses .dropdown-menu styles with a small script. */
.cx-wrap{--cx-ink:#1f1d24;--cx-muted:#5d5965;--cx-line:#e8e5ec;--cx-hover:#f6f2f7;--cx-accent:#be185d;--cx-danger:#b42318;--cx-danger-bg:#fef3f2;font-size:.875rem}
.cx-more{--bs-btn-padding-x:0;--bs-btn-padding-y:0;--bs-btn-border-width:0;--bs-btn-border-radius:9px;--bs-btn-color:var(--cx-muted);--bs-btn-hover-color:var(--cx-ink);
  --bs-btn-hover-bg:var(--cx-hover);--bs-btn-active-color:var(--cx-ink);--bs-btn-active-bg:var(--cx-hover);display:grid;place-items:center;width:34px;height:34px}
.cx-more svg{width:18px;height:18px}
.cx-more:focus-visible{outline:2px solid var(--cx-accent);outline-offset:2px;box-shadow:none}
.cx-menu{--bs-dropdown-min-width:212px;--bs-dropdown-padding-x:6px;--bs-dropdown-padding-y:6px;--bs-dropdown-font-size:.875rem;--bs-dropdown-color:var(--cx-ink);
  --bs-dropdown-border-color:var(--cx-line);--bs-dropdown-border-radius:12px;--bs-dropdown-divider-bg:var(--cx-line);--bs-dropdown-divider-margin-y:6px;
  --bs-dropdown-link-color:var(--cx-ink);--bs-dropdown-link-hover-color:var(--cx-ink);--bs-dropdown-link-hover-bg:var(--cx-hover);--bs-dropdown-link-active-color:var(--cx-ink);
  --bs-dropdown-link-active-bg:var(--cx-hover);--bs-dropdown-link-disabled-color:#8d8894;--bs-dropdown-item-padding-x:10px;--bs-dropdown-item-padding-y:7px;
  --bs-dropdown-item-border-radius:8px;box-shadow:0 2px 6px rgba(31,29,36,.06),0 18px 40px -14px rgba(31,29,36,.3)}
.cx-menu.show{animation:cx-in .12s ease-out}
@keyframes cx-in{from{opacity:0;scale:.96}}
.cx-menu .dropdown-divider{margin-inline:-6px;opacity:1}
.cx-item{display:flex;align-items:center;gap:10px;font-weight:500;line-height:1.4}
.cx-item>svg{width:16px;height:16px;flex:none;color:var(--cx-muted)}
.cx-item:focus{outline:none}
.cx-item:focus-visible{outline:2px solid var(--cx-accent);outline-offset:-2px}
.cx-item.danger,.cx-item.danger>svg{color:var(--cx-danger)}
.cx-item.danger:hover,.cx-item.danger:focus{background:var(--cx-danger-bg);color:var(--cx-danger)}
.cx-item.disabled>svg{color:inherit}
.cx-kbd,.cx-hint2{margin-left:auto;padding:0 0 0 18px;background:none;color:var(--cx-muted);font:500 .75rem ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
.cx-hint2{font-family:inherit}
.cx-board>.cx-menu{position:absolute}

.cx-list{margin:0;padding:6px;list-style:none;background:#fff;border:1px solid var(--cx-line);border-radius:14px;box-shadow:0 1px 2px rgba(31,29,36,.05)}
.cx-row{display:flex;align-items:center;gap:12px;padding:10px 8px 10px 10px;border-radius:10px}
.cx-row+.cx-row{box-shadow:0 -1px 0 var(--cx-line)}
.cx-av{flex:none;display:grid;place-items:center;width:36px;height:36px;border-radius:50%;color:#fff;font-size:.75rem;font-weight:700}
.cx-who{flex:1;min-width:0}
.cx-who b{display:block;color:var(--cx-ink);font-weight:600;line-height:1.35}
.cx-who small{display:block;overflow:hidden;color:var(--cx-muted);font-size:.8rem;text-overflow:ellipsis;white-space:nowrap}
.cx-role{flex:none;padding:2px 9px;border-radius:999px;background:#f1eef4;color:#48434f;font-size:.72rem;font-weight:600}
.cx-role.pending{background:#fef3c7;color:#854d0e}
.cx-seen{flex:none;width:88px;color:var(--cx-muted);font-size:.8rem;text-align:right}
.cx-dd{position:relative;flex:none}
.cx-board{position:relative;display:grid;grid-template-columns:repeat(3,1fr);gap:16px;padding:16px;background:#fff;border:1px solid var(--cx-line);border-radius:14px;
  box-shadow:0 1px 2px rgba(31,29,36,.05)}
.cx-tile{display:block;border-radius:12px;color:var(--cx-ink);text-decoration:none}
.cx-art{display:block;aspect-ratio:4/3;border-radius:10px;transition:box-shadow .15s}
.cx-tile[aria-expanded="true"] .cx-art{box-shadow:0 0 0 2px #fff,0 0 0 4px var(--cx-accent)}
.cx-tile:focus-visible{outline:2px solid var(--cx-accent);outline-offset:4px}
.cx-cap{display:block;margin-top:8px;font-size:.8rem;font-weight:600;line-height:1.35}
.cx-cap small{display:block;color:var(--cx-muted);font-weight:400}
.cx-hint{grid-column:1/-1;display:flex;align-items:center;gap:8px;margin:0;color:var(--cx-muted);font-size:.8rem}
.cx-hint svg{width:16px;height:16px;flex:none}
.cx-hint kbd{padding:1px 6px;border:1px solid var(--cx-line);border-radius:5px;background:#faf9fb;color:var(--cx-ink);font:600 .72rem ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
.cx-item>.cx-chk{width:16px;height:16px;margin-left:auto;flex:none;color:var(--cx-accent);visibility:hidden}
.cx-item[aria-checked="true"] .cx-chk{visibility:visible}
.cx-board>.cx-menu{top:0;left:0;margin:0}
.a1{background:radial-gradient(circle at 68% 50%,#fde68a 0 7%,transparent 7.5%),linear-gradient(180deg,#312e81,#9d174d 48%,#fb923c 58%,#1e3a8a 58.5%,#0f172a)}
.a2{background:radial-gradient(ellipse at 30% 100%,#134e4a 0 30%,transparent 30.5%),linear-gradient(170deg,#e0f2fe,#7dd3fc 40%,#94a3b8 41%,#475569 62%,#0f766e 63%,#115e59)}
.a3{background:radial-gradient(circle at 25% 28%,#fef9c3 0 9%,transparent 9.5%),radial-gradient(circle at 75% 85%,#365314 0 22%,transparent 22.5%),linear-gradient(180deg,#fde68a,#bef264 55%,#4d7c0f)}
@media (max-width:600px){
  .cx-seen{display:none}
  .cx-board{grid-template-columns:1fr 1fr}
}

@media (prefers-reduced-motion:reduce){.cx-menu.show{animation:none}.cx-art{transition:none}}
JavaScript
// Context menu drawn with Bootstrap's .dropdown-menu styles. The Dropdown plugin needs a toggle button, so this script opens it:
// right-click, Shift+F10 or the Menu key on a [data-context] element shows the menu at the pointer. Keys work as in a menu:
// arrows, Home/End, Esc returns focus to the photo, Tab or a click outside closes it.
(() => {
  const menu = document.getElementById('cx-menu');
  const owner = () => document.querySelector(`[data-context][aria-controls="${menu.id}"][aria-expanded="true"]`);
  const items = () => [...menu.querySelectorAll('.dropdown-item:not(.disabled)')];
  const close = focus => {
    const t = owner();
    if (!t) return;
    menu.classList.remove('show');
    t.setAttribute('aria-expanded', 'false');
    if (focus) t.focus();
  };
  document.addEventListener('contextmenu', e => {
    const t = e.target.closest('[data-context]');
    if (!t) return;
    e.preventDefault();
    close();
    const box = menu.parentElement.getBoundingClientRect(), r = t.getBoundingClientRect();
    const x = e.clientX || r.left + 24, y = e.clientY || r.top + 24;
    t.setAttribute('aria-expanded', 'true');
    menu.classList.add('show');
    menu.style.left = Math.max(8, Math.min(x - box.left, box.width - menu.offsetWidth - 8)) + 'px';
    menu.style.top = Math.max(8, Math.min(y - box.top, box.height - menu.offsetHeight - 8)) + 'px';
    items()[0].focus();
  });
  menu.addEventListener('click', e => {
    const item = e.target.closest('.dropdown-item');
    if (!item || item.classList.contains('disabled')) return;
    if (item.getAttribute('role') === 'menuitemcheckbox') { item.setAttribute('aria-checked', item.getAttribute('aria-checked') !== 'true'); return; }
    close(true);
  });
  document.addEventListener('click', e => { if (!menu.contains(e.target)) close(); });
  // window + capture: runs before Bootstrap's own document-level key handler, which only knows toggle-based menus
  window.addEventListener('keydown', e => {
    if (!owner() || !menu.contains(e.target)) return;
    if (e.key === 'Tab') return close();
    const list = items(), i = list.indexOf(e.target);
    const next = { ArrowDown: list[(i + 1) % list.length], ArrowUp: list.at(i <= 0 ? -1 : i - 1), Home: list[0], End: list.at(-1) }[e.key];
    if (!next && e.key !== 'Escape') return;
    e.preventDefault();
    e.stopPropagation();
    if (e.key === 'Escape') close(true); else next.focus();
  }, true);
})();

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
  <symbol id="i-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="m5 12.5 4.5 4.5L19 7.5"/></symbol>
  <symbol id="i-pencil" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 20h4L19 9a2.8 2.8 0 0 0-4-4L4 16z"/><path d="m13.5 6.5 4 4"/></symbol>
  <symbol id="i-copy" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="8.5" y="8.5" width="12" height="12" rx="2"/><path d="M15.5 8.5V5a1.5 1.5 0 0 0-1.5-1.5H5A1.5 1.5 0 0 0 3.5 5v9A1.5 1.5 0 0 0 5 15.5h3.5"/></symbol>
  <symbol id="i-trash" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7h16M10 11v6M14 11v6M5.5 7l1 12a1.5 1.5 0 0 0 1.5 1.5h8a1.5 1.5 0 0 0 1.5-1.5l1-12M9 7V4.5h6V7"/></symbol>
  <symbol id="i-mail" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3.5" y="5.5" width="17" height="13" rx="2"/><path d="m4 7 8 6 8-6"/></symbol>
  <symbol id="i-user" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="8" r="4"/><path d="M4.5 20.5c.8-4 3.7-6 7.5-6s6.7 2 7.5 6"/></symbol>
  <symbol id="i-users" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="8.5" r="3.5"/><path d="M2.5 20c.6-3.6 3.2-5.5 6.5-5.5s5.9 1.9 6.5 5.5"/><path d="M16 5.2a3.3 3.3 0 0 1 0 6.6M18 14.8c2 .6 3.2 2.3 3.5 5.2"/></symbol>
  <symbol id="i-help" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="8.5"/><path d="M9.6 9.5a2.5 2.5 0 0 1 4.8 1c0 1.7-2.4 2.2-2.4 3.7"/><path d="M12 17.3h.01"/></symbol>
  <symbol id="i-dots" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="5.5" cy="12" r="1.4" fill="currentColor" stroke="none"/><circle cx="12" cy="12" r="1.4" fill="currentColor" stroke="none"/><circle cx="18.5" cy="12" r="1.4" fill="currentColor" stroke="none"/></symbol>
  <symbol id="i-image" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3.5" y="4.5" width="17" height="15" rx="2.5"/><circle cx="9" cy="10" r="1.8"/><path d="m20.5 16-5-5-9 8.5"/></symbol>
  <symbol id="i-star" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="m12 4 2.5 5 5.5.8-4 3.9.9 5.5L12 16.6l-4.9 2.6.9-5.5-4-3.9L9.5 9z"/></symbol>
  <symbol id="i-eye" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><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"/></symbol>
</svg>
<div class="col-span-full"><span class="mb-3.5 block text-[.72rem] tracking-[.12em] text-[#5b6474] uppercase">Overflow menus on list rows</span>
<div class="relative min-h-[300px] rounded-[18px] border border-[#dfe3eb] bg-[#f8f9fc] bg-[radial-gradient(#d5dae4_1px,transparent_1px)] bg-size-[18px_18px] p-7 max-[600px]:p-5">
<ul class="m-0 list-none rounded-[14px] border border-[#e8e5ec] bg-white p-1.5 text-[.875rem] shadow-[0_1px_2px_rgba(31,29,36,.05)]" aria-label="Team members">
<li class="flex items-center gap-3 rounded-[10px] py-2.5 pr-2 pl-2.5"><span class="grid size-9 flex-none place-items-center rounded-full bg-[linear-gradient(135deg,#7c3aed,#be185d)] text-[.75rem] font-bold text-white" aria-hidden="true">LF</span><span class="min-w-0 flex-1"><b class="block leading-[1.35] font-semibold text-[#1f1d24]">Lena Fischer</b><small class="block truncate text-[.8rem] text-[#5d5965]">[email protected]</small></span><span class="flex-none rounded-full px-[9px] py-0.5 text-[.72rem] font-semibold bg-[#f1eef4] text-[#48434f]">Owner</span><span class="w-[88px] flex-none text-right text-[.8rem] text-[#5d5965] max-[600px]:hidden">Now</span><div class="relative flex-none">
<button class="grid size-[34px] cursor-pointer place-items-center rounded-[9px] border-0 bg-transparent p-0 text-[#5d5965] transition-colors duration-150 hover:bg-[#f6f2f7] hover:text-[#1f1d24] aria-expanded:bg-[#f6f2f7] aria-expanded:text-[#1f1d24] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#be185d] motion-reduce:transition-none" id="cx-b1" type="button" aria-label="Actions for Lena Fischer" aria-haspopup="menu" aria-expanded="false" aria-controls="cx-m1"><svg class="size-[18px]" aria-hidden="true"><use href="#i-dots"/></svg></button>
<div class="absolute top-[calc(100%+4px)] right-0 origin-top-right z-20 min-w-[212px] rounded-xl border border-[#e8e5ec] bg-white p-1.5 shadow-[0_2px_6px_rgba(31,29,36,.06),0_18px_40px_-14px_rgba(31,29,36,.3)] animate-menu-in motion-reduce:animate-none" role="menu" id="cx-m1" aria-label="Actions for Lena Fischer" hidden>
<a href="#lena" class="flex w-full cursor-pointer items-center gap-2.5 rounded-lg border-0 bg-transparent px-2.5 py-[7px] text-left leading-[1.4] font-medium whitespace-nowrap no-underline focus:outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#be185d] group text-[#1f1d24] hover:bg-[#f6f2f7] focus:bg-[#f6f2f7] aria-disabled:cursor-not-allowed aria-disabled:text-[#8d8894] aria-disabled:hover:bg-transparent" role="menuitem" tabindex="-1"><svg class="size-4 flex-none text-[#5d5965] group-aria-disabled:text-current" aria-hidden="true"><use href="#i-user"/></svg>View profile</a>
<button type="button" class="flex w-full cursor-pointer items-center gap-2.5 rounded-lg border-0 bg-transparent px-2.5 py-[7px] text-left leading-[1.4] font-medium whitespace-nowrap no-underline focus:outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#be185d] group text-[#1f1d24] hover:bg-[#f6f2f7] focus:bg-[#f6f2f7] aria-disabled:cursor-not-allowed aria-disabled:text-[#8d8894] aria-disabled:hover:bg-transparent" role="menuitem" tabindex="-1"><svg class="size-4 flex-none text-[#5d5965] group-aria-disabled:text-current" aria-hidden="true"><use href="#i-users"/></svg>Change role…</button>
<button type="button" class="flex w-full cursor-pointer items-center gap-2.5 rounded-lg border-0 bg-transparent px-2.5 py-[7px] text-left leading-[1.4] font-medium whitespace-nowrap no-underline focus:outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#be185d] group text-[#1f1d24] hover:bg-[#f6f2f7] focus:bg-[#f6f2f7] aria-disabled:cursor-not-allowed aria-disabled:text-[#8d8894] aria-disabled:hover:bg-transparent" role="menuitem" tabindex="-1"><svg class="size-4 flex-none text-[#5d5965] group-aria-disabled:text-current" aria-hidden="true"><use href="#i-copy"/></svg>Copy email</button>
<div class="-mx-1.5 my-1.5 h-px bg-[#e8e5ec]" role="separator"></div>
<button type="button" class="flex w-full cursor-pointer items-center gap-2.5 rounded-lg border-0 bg-transparent px-2.5 py-[7px] text-left leading-[1.4] font-medium whitespace-nowrap no-underline focus:outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#be185d] group text-[#1f1d24] hover:bg-[#f6f2f7] focus:bg-[#f6f2f7] aria-disabled:cursor-not-allowed aria-disabled:text-[#8d8894] aria-disabled:hover:bg-transparent" role="menuitem" tabindex="-1" aria-disabled="true" aria-labelledby="cx-m1-t4" aria-describedby="cx-m1-d4"><svg class="size-4 flex-none text-[#5d5965] group-aria-disabled:text-current" aria-hidden="true"><use href="#i-trash"/></svg><span id="cx-m1-t4">Remove from team</span><span class="ml-auto pl-[18px] text-[.75rem] text-[#5d5965]" id="cx-m1-d4">Owner</span></button>
</div>
</div></li>
<li class="flex items-center gap-3 rounded-[10px] py-2.5 pr-2 pl-2.5 shadow-[0_-1px_0_#e8e5ec]"><span class="grid size-9 flex-none place-items-center rounded-full bg-[linear-gradient(135deg,#0369a1,#0f766e)] text-[.75rem] font-bold text-white" aria-hidden="true">TR</span><span class="min-w-0 flex-1"><b class="block leading-[1.35] font-semibold text-[#1f1d24]">Tomás Rivera</b><small class="block truncate text-[.8rem] text-[#5d5965]">[email protected]</small></span><span class="flex-none rounded-full px-[9px] py-0.5 text-[.72rem] font-semibold bg-[#f1eef4] text-[#48434f]">Admin</span><span class="w-[88px] flex-none text-right text-[.8rem] text-[#5d5965] max-[600px]:hidden">2 hours ago</span><div class="relative flex-none">
<button class="grid size-[34px] cursor-pointer place-items-center rounded-[9px] border-0 bg-transparent p-0 text-[#5d5965] transition-colors duration-150 hover:bg-[#f6f2f7] hover:text-[#1f1d24] aria-expanded:bg-[#f6f2f7] aria-expanded:text-[#1f1d24] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#be185d] motion-reduce:transition-none" id="cx-b2" type="button" aria-label="Actions for Tomás Rivera" aria-haspopup="menu" aria-expanded="false" aria-controls="cx-m2"><svg class="size-[18px]" aria-hidden="true"><use href="#i-dots"/></svg></button>
<div class="absolute top-[calc(100%+4px)] right-0 origin-top-right z-20 min-w-[212px] rounded-xl border border-[#e8e5ec] bg-white p-1.5 shadow-[0_2px_6px_rgba(31,29,36,.06),0_18px_40px_-14px_rgba(31,29,36,.3)] animate-menu-in motion-reduce:animate-none" role="menu" id="cx-m2" aria-label="Actions for Tomás Rivera" hidden>
<a href="#tomás" class="flex w-full cursor-pointer items-center gap-2.5 rounded-lg border-0 bg-transparent px-2.5 py-[7px] text-left leading-[1.4] font-medium whitespace-nowrap no-underline focus:outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#be185d] group text-[#1f1d24] hover:bg-[#f6f2f7] focus:bg-[#f6f2f7] aria-disabled:cursor-not-allowed aria-disabled:text-[#8d8894] aria-disabled:hover:bg-transparent" role="menuitem" tabindex="-1"><svg class="size-4 flex-none text-[#5d5965] group-aria-disabled:text-current" aria-hidden="true"><use href="#i-user"/></svg>View profile</a>
<button type="button" class="flex w-full cursor-pointer items-center gap-2.5 rounded-lg border-0 bg-transparent px-2.5 py-[7px] text-left leading-[1.4] font-medium whitespace-nowrap no-underline focus:outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#be185d] group text-[#1f1d24] hover:bg-[#f6f2f7] focus:bg-[#f6f2f7] aria-disabled:cursor-not-allowed aria-disabled:text-[#8d8894] aria-disabled:hover:bg-transparent" role="menuitem" tabindex="-1"><svg class="size-4 flex-none text-[#5d5965] group-aria-disabled:text-current" aria-hidden="true"><use href="#i-users"/></svg>Change role…</button>
<button type="button" class="flex w-full cursor-pointer items-center gap-2.5 rounded-lg border-0 bg-transparent px-2.5 py-[7px] text-left leading-[1.4] font-medium whitespace-nowrap no-underline focus:outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#be185d] group text-[#1f1d24] hover:bg-[#f6f2f7] focus:bg-[#f6f2f7] aria-disabled:cursor-not-allowed aria-disabled:text-[#8d8894] aria-disabled:hover:bg-transparent" role="menuitem" tabindex="-1"><svg class="size-4 flex-none text-[#5d5965] group-aria-disabled:text-current" aria-hidden="true"><use href="#i-copy"/></svg>Copy email</button>
<div class="-mx-1.5 my-1.5 h-px bg-[#e8e5ec]" role="separator"></div>
<button type="button" class="flex w-full cursor-pointer items-center gap-2.5 rounded-lg border-0 bg-transparent px-2.5 py-[7px] text-left leading-[1.4] font-medium whitespace-nowrap no-underline focus:outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#be185d] text-[#b42318] hover:bg-[#fef3f2] focus:bg-[#fef3f2]" role="menuitem" tabindex="-1"><svg class="size-4 flex-none" aria-hidden="true"><use href="#i-trash"/></svg>Remove from team</button>
</div>
</div></li>
<li class="flex items-center gap-3 rounded-[10px] py-2.5 pr-2 pl-2.5 shadow-[0_-1px_0_#e8e5ec]"><span class="grid size-9 flex-none place-items-center rounded-full bg-[linear-gradient(135deg,#b45309,#9f1239)] text-[.75rem] font-bold text-white" aria-hidden="true">AT</span><span class="min-w-0 flex-1"><b class="block leading-[1.35] font-semibold text-[#1f1d24]">Aiko Tanaka</b><small class="block truncate text-[.8rem] text-[#5d5965]">[email protected]</small></span><span class="flex-none rounded-full px-[9px] py-0.5 text-[.72rem] font-semibold bg-[#f1eef4] text-[#48434f]">Member</span><span class="w-[88px] flex-none text-right text-[.8rem] text-[#5d5965] max-[600px]:hidden">Yesterday</span><div class="relative flex-none">
<button class="grid size-[34px] cursor-pointer place-items-center rounded-[9px] border-0 bg-transparent p-0 text-[#5d5965] transition-colors duration-150 hover:bg-[#f6f2f7] hover:text-[#1f1d24] aria-expanded:bg-[#f6f2f7] aria-expanded:text-[#1f1d24] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#be185d] motion-reduce:transition-none" id="cx-b3" type="button" aria-label="Actions for Aiko Tanaka" aria-haspopup="menu" aria-expanded="false" aria-controls="cx-m3"><svg class="size-[18px]" aria-hidden="true"><use href="#i-dots"/></svg></button>
<div class="absolute top-[calc(100%+4px)] right-0 origin-top-right z-20 min-w-[212px] rounded-xl border border-[#e8e5ec] bg-white p-1.5 shadow-[0_2px_6px_rgba(31,29,36,.06),0_18px_40px_-14px_rgba(31,29,36,.3)] animate-menu-in motion-reduce:animate-none" role="menu" id="cx-m3" aria-label="Actions for Aiko Tanaka" hidden>
<a href="#aiko" class="flex w-full cursor-pointer items-center gap-2.5 rounded-lg border-0 bg-transparent px-2.5 py-[7px] text-left leading-[1.4] font-medium whitespace-nowrap no-underline focus:outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#be185d] group text-[#1f1d24] hover:bg-[#f6f2f7] focus:bg-[#f6f2f7] aria-disabled:cursor-not-allowed aria-disabled:text-[#8d8894] aria-disabled:hover:bg-transparent" role="menuitem" tabindex="-1"><svg class="size-4 flex-none text-[#5d5965] group-aria-disabled:text-current" aria-hidden="true"><use href="#i-user"/></svg>View profile</a>
<button type="button" class="flex w-full cursor-pointer items-center gap-2.5 rounded-lg border-0 bg-transparent px-2.5 py-[7px] text-left leading-[1.4] font-medium whitespace-nowrap no-underline focus:outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#be185d] group text-[#1f1d24] hover:bg-[#f6f2f7] focus:bg-[#f6f2f7] aria-disabled:cursor-not-allowed aria-disabled:text-[#8d8894] aria-disabled:hover:bg-transparent" role="menuitem" tabindex="-1"><svg class="size-4 flex-none text-[#5d5965] group-aria-disabled:text-current" aria-hidden="true"><use href="#i-users"/></svg>Change role…</button>
<button type="button" class="flex w-full cursor-pointer items-center gap-2.5 rounded-lg border-0 bg-transparent px-2.5 py-[7px] text-left leading-[1.4] font-medium whitespace-nowrap no-underline focus:outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#be185d] group text-[#1f1d24] hover:bg-[#f6f2f7] focus:bg-[#f6f2f7] aria-disabled:cursor-not-allowed aria-disabled:text-[#8d8894] aria-disabled:hover:bg-transparent" role="menuitem" tabindex="-1"><svg class="size-4 flex-none text-[#5d5965] group-aria-disabled:text-current" aria-hidden="true"><use href="#i-copy"/></svg>Copy email</button>
<div class="-mx-1.5 my-1.5 h-px bg-[#e8e5ec]" role="separator"></div>
<button type="button" class="flex w-full cursor-pointer items-center gap-2.5 rounded-lg border-0 bg-transparent px-2.5 py-[7px] text-left leading-[1.4] font-medium whitespace-nowrap no-underline focus:outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#be185d] text-[#b42318] hover:bg-[#fef3f2] focus:bg-[#fef3f2]" role="menuitem" tabindex="-1"><svg class="size-4 flex-none" aria-hidden="true"><use href="#i-trash"/></svg>Remove from team</button>
</div>
</div></li>
<li class="flex items-center gap-3 rounded-[10px] py-2.5 pr-2 pl-2.5 shadow-[0_-1px_0_#e8e5ec]"><span class="grid size-9 flex-none place-items-center rounded-full bg-[linear-gradient(135deg,#475569,#1e293b)] text-[.75rem] font-bold text-white" aria-hidden="true">SO</span><span class="min-w-0 flex-1"><b class="block leading-[1.35] font-semibold text-[#1f1d24]">Sam Okoro</b><small class="block truncate text-[.8rem] text-[#5d5965]">[email protected]</small></span><span class="flex-none rounded-full px-[9px] py-0.5 text-[.72rem] font-semibold bg-[#fef3c7] text-[#854d0e]">Invited</span><span class="w-[88px] flex-none text-right text-[.8rem] text-[#5d5965] max-[600px]:hidden">Pending</span><div class="relative flex-none">
<button class="grid size-[34px] cursor-pointer place-items-center rounded-[9px] border-0 bg-transparent p-0 text-[#5d5965] transition-colors duration-150 hover:bg-[#f6f2f7] hover:text-[#1f1d24] aria-expanded:bg-[#f6f2f7] aria-expanded:text-[#1f1d24] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#be185d] motion-reduce:transition-none" id="cx-b4" type="button" aria-label="Actions for Sam Okoro" aria-haspopup="menu" aria-expanded="false" aria-controls="cx-m4"><svg class="size-[18px]" aria-hidden="true"><use href="#i-dots"/></svg></button>
<div class="absolute top-[calc(100%+4px)] right-0 origin-top-right z-20 min-w-[212px] rounded-xl border border-[#e8e5ec] bg-white p-1.5 shadow-[0_2px_6px_rgba(31,29,36,.06),0_18px_40px_-14px_rgba(31,29,36,.3)] animate-menu-in motion-reduce:animate-none" role="menu" id="cx-m4" aria-label="Actions for Sam Okoro" hidden>
<a href="#sam" class="flex w-full cursor-pointer items-center gap-2.5 rounded-lg border-0 bg-transparent px-2.5 py-[7px] text-left leading-[1.4] font-medium whitespace-nowrap no-underline focus:outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#be185d] group text-[#1f1d24] hover:bg-[#f6f2f7] focus:bg-[#f6f2f7] aria-disabled:cursor-not-allowed aria-disabled:text-[#8d8894] aria-disabled:hover:bg-transparent" role="menuitem" tabindex="-1"><svg class="size-4 flex-none text-[#5d5965] group-aria-disabled:text-current" aria-hidden="true"><use href="#i-user"/></svg>View profile</a>
<button type="button" class="flex w-full cursor-pointer items-center gap-2.5 rounded-lg border-0 bg-transparent px-2.5 py-[7px] text-left leading-[1.4] font-medium whitespace-nowrap no-underline focus:outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#be185d] group text-[#1f1d24] hover:bg-[#f6f2f7] focus:bg-[#f6f2f7] aria-disabled:cursor-not-allowed aria-disabled:text-[#8d8894] aria-disabled:hover:bg-transparent" role="menuitem" tabindex="-1"><svg class="size-4 flex-none text-[#5d5965] group-aria-disabled:text-current" aria-hidden="true"><use href="#i-users"/></svg>Change role…</button>
<button type="button" class="flex w-full cursor-pointer items-center gap-2.5 rounded-lg border-0 bg-transparent px-2.5 py-[7px] text-left leading-[1.4] font-medium whitespace-nowrap no-underline focus:outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#be185d] group text-[#1f1d24] hover:bg-[#f6f2f7] focus:bg-[#f6f2f7] aria-disabled:cursor-not-allowed aria-disabled:text-[#8d8894] aria-disabled:hover:bg-transparent" role="menuitem" tabindex="-1"><svg class="size-4 flex-none text-[#5d5965] group-aria-disabled:text-current" aria-hidden="true"><use href="#i-mail"/></svg>Resend invite</button>
<button type="button" class="flex w-full cursor-pointer items-center gap-2.5 rounded-lg border-0 bg-transparent px-2.5 py-[7px] text-left leading-[1.4] font-medium whitespace-nowrap no-underline focus:outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#be185d] group text-[#1f1d24] hover:bg-[#f6f2f7] focus:bg-[#f6f2f7] aria-disabled:cursor-not-allowed aria-disabled:text-[#8d8894] aria-disabled:hover:bg-transparent" role="menuitem" tabindex="-1"><svg class="size-4 flex-none text-[#5d5965] group-aria-disabled:text-current" aria-hidden="true"><use href="#i-copy"/></svg>Copy email</button>
<div class="-mx-1.5 my-1.5 h-px bg-[#e8e5ec]" role="separator"></div>
<button type="button" class="flex w-full cursor-pointer items-center gap-2.5 rounded-lg border-0 bg-transparent px-2.5 py-[7px] text-left leading-[1.4] font-medium whitespace-nowrap no-underline focus:outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#be185d] text-[#b42318] hover:bg-[#fef3f2] focus:bg-[#fef3f2]" role="menuitem" tabindex="-1"><svg class="size-4 flex-none" aria-hidden="true"><use href="#i-trash"/></svg>Remove from team</button>
</div>
</div></li>
</ul>
</div></div>
<div class="col-span-full"><span class="mb-3.5 block text-[.72rem] tracking-[.12em] text-[#5b6474] uppercase">Right-click context menu</span>
<div class="relative min-h-[420px] rounded-[18px] border border-[#dfe3eb] bg-[#f8f9fc] bg-[radial-gradient(#d5dae4_1px,transparent_1px)] bg-size-[18px_18px] p-7 max-[600px]:p-5">
<div class="relative grid grid-cols-3 gap-4 rounded-[14px] border border-[#e8e5ec] bg-white p-4 text-[.875rem] shadow-[0_1px_2px_rgba(31,29,36,.05)] max-[600px]:grid-cols-2">
<p class="col-span-full m-0 flex items-center gap-2 text-[.8rem] text-[#5d5965]" id="cx-hint"><svg class="size-4 flex-none" aria-hidden="true"><use href="#i-help"/></svg><span>Right-click a photo, or focus it and press <kbd class="rounded-[5px] border border-[#e8e5ec] bg-[#faf9fb] px-1.5 py-px font-mono text-[.72rem] font-semibold text-[#1f1d24]">Shift</kbd> + <kbd class="rounded-[5px] border border-[#e8e5ec] bg-[#faf9fb] px-1.5 py-px font-mono text-[.72rem] font-semibold text-[#1f1d24]">F10</kbd>.</span></p>
<a class="group block rounded-xl text-[#1f1d24] no-underline focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-[#be185d]" href="#harbour" data-context aria-haspopup="menu" aria-expanded="false" aria-controls="cx-menu" aria-describedby="cx-hint"><span class="block aspect-[4/3] rounded-[10px] bg-[radial-gradient(circle_at_68%_50%,#fde68a_0_7%,transparent_7.5%),linear-gradient(180deg,#312e81,#9d174d_48%,#fb923c_58%,#1e3a8a_58.5%,#0f172a)] transition-shadow duration-150 group-aria-expanded:shadow-[0_0_0_2px_#fff,0_0_0_4px_#be185d] motion-reduce:transition-none" aria-hidden="true"></span><span class="mt-2 block text-[.8rem] leading-[1.35] font-semibold">Harbour at dusk.jpg<small class="block font-normal text-[#5d5965]">4.2 MB</small></span></a><a class="group block rounded-xl text-[#1f1d24] no-underline focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-[#be185d]" href="#lake-bled" data-context aria-haspopup="menu" aria-expanded="false" aria-controls="cx-menu" aria-describedby="cx-hint"><span class="block aspect-[4/3] rounded-[10px] bg-[radial-gradient(ellipse_at_30%_100%,#134e4a_0_30%,transparent_30.5%),linear-gradient(170deg,#e0f2fe,#7dd3fc_40%,#94a3b8_41%,#475569_62%,#0f766e_63%,#115e59)] transition-shadow duration-150 group-aria-expanded:shadow-[0_0_0_2px_#fff,0_0_0_4px_#be185d] motion-reduce:transition-none" aria-hidden="true"></span><span class="mt-2 block text-[.8rem] leading-[1.35] font-semibold">Lake Bled, morning.jpg<small class="block font-normal text-[#5d5965]">3.8 MB</small></span></a><a class="group block rounded-xl text-[#1f1d24] no-underline focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-[#be185d]" href="#olive-grove" data-context aria-haspopup="menu" aria-expanded="false" aria-controls="cx-menu" aria-describedby="cx-hint"><span class="block aspect-[4/3] rounded-[10px] bg-[radial-gradient(circle_at_25%_28%,#fef9c3_0_9%,transparent_9.5%),radial-gradient(circle_at_75%_85%,#365314_0_22%,transparent_22.5%),linear-gradient(180deg,#fde68a,#bef264_55%,#4d7c0f)] transition-shadow duration-150 group-aria-expanded:shadow-[0_0_0_2px_#fff,0_0_0_4px_#be185d] motion-reduce:transition-none" aria-hidden="true"></span><span class="mt-2 block text-[.8rem] leading-[1.35] font-semibold">Olive grove.jpg<small class="block font-normal text-[#5d5965]">5.1 MB</small></span></a>
<div class="absolute top-0 left-0 origin-top-left z-20 min-w-[212px] rounded-xl border border-[#e8e5ec] bg-white p-1.5 shadow-[0_2px_6px_rgba(31,29,36,.06),0_18px_40px_-14px_rgba(31,29,36,.3)] animate-menu-in motion-reduce:animate-none" role="menu" id="cx-menu" aria-label="Photo actions" hidden>
<button type="button" class="flex w-full cursor-pointer items-center gap-2.5 rounded-lg border-0 bg-transparent px-2.5 py-[7px] text-left leading-[1.4] font-medium whitespace-nowrap no-underline focus:outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#be185d] group text-[#1f1d24] hover:bg-[#f6f2f7] focus:bg-[#f6f2f7] aria-disabled:cursor-not-allowed aria-disabled:text-[#8d8894] aria-disabled:hover:bg-transparent" role="menuitem" tabindex="-1" aria-keyshortcuts="Enter"><svg class="size-4 flex-none text-[#5d5965] group-aria-disabled:text-current" aria-hidden="true"><use href="#i-eye"/></svg><span>Open</span><kbd class="ml-auto pl-[18px] font-mono text-[.75rem] font-medium text-[#5d5965]" aria-hidden="true">↵</kbd></button>
<button type="button" class="flex w-full cursor-pointer items-center gap-2.5 rounded-lg border-0 bg-transparent px-2.5 py-[7px] text-left leading-[1.4] font-medium whitespace-nowrap no-underline focus:outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#be185d] group text-[#1f1d24] hover:bg-[#f6f2f7] focus:bg-[#f6f2f7] aria-disabled:cursor-not-allowed aria-disabled:text-[#8d8894] aria-disabled:hover:bg-transparent" role="menuitem" tabindex="-1" aria-keyshortcuts="F2"><svg class="size-4 flex-none text-[#5d5965] group-aria-disabled:text-current" aria-hidden="true"><use href="#i-pencil"/></svg><span>Rename</span><kbd class="ml-auto pl-[18px] font-mono text-[.75rem] font-medium text-[#5d5965]" aria-hidden="true">F2</kbd></button>
<button type="button" class="flex w-full cursor-pointer items-center gap-2.5 rounded-lg border-0 bg-transparent px-2.5 py-[7px] text-left leading-[1.4] font-medium whitespace-nowrap no-underline focus:outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#be185d] group text-[#1f1d24] hover:bg-[#f6f2f7] focus:bg-[#f6f2f7] aria-disabled:cursor-not-allowed aria-disabled:text-[#8d8894] aria-disabled:hover:bg-transparent" role="menuitem" tabindex="-1" aria-keyshortcuts="Meta+D"><svg class="size-4 flex-none text-[#5d5965] group-aria-disabled:text-current" aria-hidden="true"><use href="#i-copy"/></svg><span>Duplicate</span><kbd class="ml-auto pl-[18px] font-mono text-[.75rem] font-medium text-[#5d5965]" aria-hidden="true">⌘D</kbd></button>
<div class="-mx-1.5 my-1.5 h-px bg-[#e8e5ec]" role="separator"></div>
<button type="button" class="flex w-full cursor-pointer items-center gap-2.5 rounded-lg border-0 bg-transparent px-2.5 py-[7px] text-left leading-[1.4] font-medium whitespace-nowrap no-underline focus:outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#be185d] group text-[#1f1d24] hover:bg-[#f6f2f7] focus:bg-[#f6f2f7] aria-disabled:cursor-not-allowed aria-disabled:text-[#8d8894] aria-disabled:hover:bg-transparent" role="menuitemcheckbox" tabindex="-1" aria-checked="true"><svg class="size-4 flex-none text-[#5d5965] group-aria-disabled:text-current" aria-hidden="true"><use href="#i-star"/></svg><span>Favourite</span><svg class="invisible ml-auto size-4 flex-none text-[#be185d] group-aria-checked:visible" aria-hidden="true"><use href="#i-check"/></svg></button>
<button type="button" class="flex w-full cursor-pointer items-center gap-2.5 rounded-lg border-0 bg-transparent px-2.5 py-[7px] text-left leading-[1.4] font-medium whitespace-nowrap no-underline focus:outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#be185d] group text-[#1f1d24] hover:bg-[#f6f2f7] focus:bg-[#f6f2f7] aria-disabled:cursor-not-allowed aria-disabled:text-[#8d8894] aria-disabled:hover:bg-transparent" role="menuitem" tabindex="-1"><svg class="size-4 flex-none text-[#5d5965] group-aria-disabled:text-current" aria-hidden="true"><use href="#i-image"/></svg>Set as album cover</button>
<div class="-mx-1.5 my-1.5 h-px bg-[#e8e5ec]" role="separator"></div>
<button type="button" class="flex w-full cursor-pointer items-center gap-2.5 rounded-lg border-0 bg-transparent px-2.5 py-[7px] text-left leading-[1.4] font-medium whitespace-nowrap no-underline focus:outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#be185d] text-[#b42318] hover:bg-[#fef3f2] focus:bg-[#fef3f2]" role="menuitem" tabindex="-1" aria-keyshortcuts="Delete"><svg class="size-4 flex-none" aria-hidden="true"><use href="#i-trash"/></svg><span>Move to trash</span><kbd class="ml-auto pl-[18px] font-mono text-[.75rem] font-medium text-[#5d5965]" aria-hidden="true">⌫</kbd></button>
</div>
</div>
</div></div>
CSS
@theme {
  --animate-menu-in: menu-in .12s ease-out;
  @keyframes menu-in { from { opacity: 0; scale: .96; } }
}
JavaScript
// Menu button (WAI-ARIA APG pattern). A trigger has aria-haspopup="menu", aria-expanded and aria-controls="<menu id>".
// Click, Enter, Space or ArrowDown opens the menu (ArrowUp opens it on the last item). Inside: ArrowUp/ArrowDown move,
// Home/End jump, typing a letter jumps to the next item that starts with it, Esc closes and returns focus to the trigger,
// Tab or a click outside closes. Choosing an item closes the menu; checkbox items stay open so you can tick several.
(() => {
  const OPEN = '[aria-haspopup="menu"][aria-expanded="true"]';
  const menuOf = t => document.getElementById(t.getAttribute('aria-controls'));
  const ownerOf = m => m && document.querySelector(`[aria-controls="${m.id}"][aria-expanded="true"]`);
  const itemsOf = m => [...m.querySelectorAll('[role^="menuitem"]')]
    .filter(i => i.closest('[role="menu"]') === m && i.getAttribute('aria-disabled') !== 'true');

  function close(t, focus) {
    const m = menuOf(t);
    m.querySelectorAll(OPEN).forEach(s => close(s));
    t.setAttribute('aria-expanded', 'false');
    m.hidden = true;
    if (focus) t.focus();
  }
  function open(t, at) {
    const level = t.closest('[role="menu"]');
    document.querySelectorAll(OPEN).forEach(o => { if (o !== t && o.closest('[role="menu"]') === level) close(o); });
    t.setAttribute('aria-expanded', 'true');
    menuOf(t).hidden = false;
    if (at !== undefined) itemsOf(menuOf(t)).at(at)?.focus();
  }
  const closeAll = target => document.querySelectorAll(OPEN).forEach(t => {
    if (!t.closest('[role="menu"]') && !t.contains(target) && !menuOf(t).contains(target)) close(t);
  });

  document.addEventListener('click', e => {
    const t = e.target.closest('[aria-haspopup="menu"][aria-controls]:not([data-context])');
    if (t) {
      if (!t.closest('[role="menu"]') && t.getAttribute('aria-expanded') === 'true') close(t);
      else open(t, e.detail === 0 ? 0 : undefined); // keyboard clicks (Enter/Space) move focus into the menu
      return;
    }
    const item = e.target.closest('[role^="menuitem"]');
    if (item && item.getAttribute('aria-disabled') !== 'true') {
      const role = item.getAttribute('role');
      if (role === 'menuitemcheckbox') { item.setAttribute('aria-checked', item.getAttribute('aria-checked') !== 'true'); return; }
      if (role === 'menuitemradio') {
        const set = item.closest('[role="group"]') || item.closest('[role="menu"]');
        set.querySelectorAll('[role="menuitemradio"]').forEach(r => r.setAttribute('aria-checked', r === item));
      }
      let t = ownerOf(item.closest('[role="menu"]'));
      while (t && t.closest('[role="menu"]')) t = ownerOf(t.closest('[role="menu"]'));
      if (t) close(t, true);
      return;
    }
    closeAll(e.target);
  });

  document.addEventListener('keydown', e => {
    const t = e.target.closest('[aria-haspopup="menu"][aria-controls]:not([data-context])');
    if (t && !t.closest('[role="menu"]')) {
      if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { e.preventDefault(); open(t, e.key === 'ArrowDown' ? 0 : -1); }
      else if (e.key === 'Escape' && t.getAttribute('aria-expanded') === 'true') close(t);
      return;
    }
    const m = e.target.closest('[role="menu"]');
    if (!m) return;
    const items = itemsOf(m), i = items.indexOf(e.target), owner = ownerOf(m);
    let next;
    switch (e.key) {
      case 'ArrowDown': next = items[(i + 1) % items.length]; break;
      case 'ArrowUp': next = items.at(i <= 0 ? -1 : i - 1); break;
      case 'Home': next = items[0]; break;
      case 'End': next = items.at(-1); break;
      case 'Escape': if (owner) { e.preventDefault(); close(owner, true); } return;
      case 'Tab': closeAll(null); return;
      case ' ': if (e.target.tagName === 'A') { e.preventDefault(); e.target.click(); } return;
      default:
        if (e.key.length !== 1 || e.ctrlKey || e.metaKey || e.altKey) return;
        next = [...items.slice(i + 1), ...items.slice(0, i + 1)]
          .find(x => x.textContent.trim().toLowerCase().startsWith(e.key.toLowerCase()));
    }
    e.preventDefault();
    next?.focus();
  });

  // Context menu: right-click, Shift+F10 or the Menu key on a [data-context] element opens its menu at the pointer.
  document.addEventListener('contextmenu', e => {
    const t = e.target.closest('[data-context]');
    if (!t) return;
    e.preventDefault();
    const m = menuOf(t), box = m.parentElement.getBoundingClientRect(), r = t.getBoundingClientRect();
    const x = e.clientX || r.left + 24, y = e.clientY || r.top + 24;
    open(t);
    m.style.left = Math.max(8, Math.min(x - box.left, box.width - m.offsetWidth - 8)) + 'px';
    m.style.top = Math.max(8, Math.min(y - box.top, box.height - m.offsetHeight - 8)) + 'px';
    itemsOf(m)[0]?.focus();
  });
})();

About this set

Menus that belong to a single item on the page. Overflow menus on list rows is a team members list where each row ends in a more button that opens actions for that person: View profile, Change role, Copy email and Remove from team, plus Resend invite for the pending invite. For the owner, Remove from team is disabled and says Owner. Right-click context menu is a photo board: right-click a photo, or focus it and press Shift+F10 or the Menu key, and a menu opens at the pointer with Open, Rename, Duplicate, a Favourite checkbox item, Set as album cover and Move to trash. The context menu is placed inside the board and nudged back from its edges, and choosing an item or pressing Esc returns focus to the photo. Each more button has a name that includes the person, so a list of identical icons is still distinguishable by ear. The Bootstrap version uses the Dropdown plugin for the rows and Bootstrap’s dropdown styles with a short script for the context menu, since the plugin needs a toggle button.

What’s included

  • More buttons named for their row, such as Actions for Tomás Rivera
  • Disabled item that shows why it is unavailable
  • Context menu at the pointer on right-click, Shift+F10 or Menu key
  • Checkbox item that stays open when toggled
  • Focus returns to the photo when the menu closes

Accessibility

Row buttons carry aria-label, aria-haspopup, aria-expanded and aria-controls, and each menu is labelled with the person it acts on. Photos are links described by a visible hint explaining how to open the context menu from the keyboard, and they expose aria-expanded while it is open. Favourite is a menuitemcheckbox with aria-checked. Focus outlines are visible throughout and animations stop under prefers-reduced-motion.

Customise it

Colours are the --cx-* custom properties on .cx-wrap. The photo artwork is the .a1, .a2 and .a3 gradients; replace them with images. In Bootstrap the menus also use --bs-dropdown-* on .cx-menu; in Tailwind edit the hex values in the classes.