Skip to content

Menu overlay

Where the extra actions on one row go when there are too many to show as buttons.

Overlays

Live preview

Source

<div class="dc-mo">
  <details class="dc-mo__root" open>
    <summary class="dc-mo__trigger"><svg viewBox="0 0 16 16" aria-hidden="true" focusable="false"><circle cx="8" cy="3" r="1.4" /><circle cx="8" cy="8" r="1.4" /><circle cx="8" cy="13" r="1.4" /></svg><span class="dc-sr">주문 20260907-118 메뉴 열기</span></summary>
    <div class="dc-mo__pop" role="menu" aria-label="주문 메뉴">
      <button class="dc-mo__item" type="button" role="menuitem"><span class="dc-mo__label">이름 바꾸기</span><kbd class="dc-mo__key">F2</kbd></button>
      <button class="dc-mo__item" type="button" role="menuitem"><span class="dc-mo__label">복제하기</span><kbd class="dc-mo__key">Ctrl D</kbd></button>
      <hr class="dc-mo__sep">
      <div role="group" aria-labelledby="dc-mo-send">
        <p class="dc-mo__head" id="dc-mo-send">보내기</p>
        <button class="dc-mo__item" type="button" role="menuitem"><span class="dc-mo__label">링크 복사</span></button>
        <button class="dc-mo__item" type="button" role="menuitem"><span class="dc-mo__label">엑셀로 내려받기</span></button>
      </div>
      <hr class="dc-mo__sep">
      <button class="dc-mo__item dc-mo__item--danger" type="button" role="menuitem"><span class="dc-mo__label">주문 지우기</span><kbd class="dc-mo__key">Del</kbd></button>
    </div>
  </details>
</div>
.dc-mo { display: flex; align-items: flex-start; justify-content: flex-end; min-height: 340px; font-family: var(--dc-font); }
.dc-mo--up { align-items: flex-end; }
.dc-mo__root { position: relative; }
.dc-mo__trigger {
  display: grid; place-items: center; width: 40px; height: 40px;
  border: 1px solid var(--dc-line-field); border-radius: var(--dc-r-sm);
  background: var(--dc-surface); color: var(--dc-text);
  list-style: none; cursor: pointer;
  transition: background var(--dc-fast);
}
.dc-mo__trigger::-webkit-details-marker { display: none; }
.dc-mo__trigger:hover { background: var(--dc-sunken); }
.dc-mo__trigger svg { width: 16px; height: 16px; fill: currentColor; }
.dc-mo__pop {
  position: absolute; top: calc(100% + var(--dc-1)); right: 0; z-index: 5;
  min-width: 240px; padding: var(--dc-2);
  background: var(--dc-surface);
  border: 1px solid var(--dc-line); border-radius: var(--dc-r-md);
  box-shadow: var(--dc-sh-2);
}
.dc-mo__pop--up { top: auto; bottom: calc(100% + var(--dc-1)); }
.dc-mo__head {
  margin: 0; padding: var(--dc-2) var(--dc-2) var(--dc-1);
  font-size: 12px; font-weight: 700; color: var(--dc-text-mute);
}
.dc-mo__item {
  display: flex; align-items: center; gap: var(--dc-3);
  width: 100%; min-height: 40px; padding: 0 var(--dc-2);
  border: 0; border-radius: var(--dc-r-xs);
  background: transparent; color: var(--dc-text);
  font-family: inherit; font-size: 14px; text-align: left;
  transition: background var(--dc-fast);
}
.dc-mo__item:hover { background: var(--dc-sunken); }
.dc-mo__item--danger { color: var(--dc-danger); }
.dc-mo__item--danger:hover { background: var(--dc-danger-soft); }
.dc-mo__label { flex: 1 1 auto; }
.dc-mo__key {
  flex: none; padding: 1px var(--dc-1);
  border: 1px solid var(--dc-line); border-radius: var(--dc-r-xs);
  background: var(--dc-sunken); color: var(--dc-text-sub);
  font-family: var(--dc-mono); font-size: 11px;
}
.dc-mo__sep { height: 1px; margin: var(--dc-2) 0; border: 0; background: var(--dc-line); }
.dc-mo__sub { position: relative; }
.dc-mo__subtrigger {
  display: flex; align-items: center; gap: var(--dc-3);
  min-height: 40px; padding: 0 var(--dc-2);
  border-radius: var(--dc-r-xs); color: var(--dc-text);
  font-size: 14px; list-style: none; cursor: pointer;
  transition: background var(--dc-fast);
}
.dc-mo__subtrigger::-webkit-details-marker { display: none; }
.dc-mo__subtrigger:hover { background: var(--dc-sunken); }
.dc-mo__caret { flex: none; color: var(--dc-text-mute); font-size: 11px; }
.dc-mo__subpop {
  position: absolute; top: calc(-1 * var(--dc-2)); right: calc(100% + var(--dc-2));
  z-index: 6; min-width: 200px; padding: var(--dc-2);
  background: var(--dc-surface);
  border: 1px solid var(--dc-line); border-radius: var(--dc-r-md);
  box-shadow: var(--dc-sh-2);
}
@media (max-width: 420px) {
  .dc-mo__pop { min-width: 0; width: calc(100vw - var(--dc-7)); }
  .dc-mo__subpop { position: static; right: auto; min-width: 0; margin-top: var(--dc-1); box-shadow: none; }
}

How to use it

  • Keep one or two everyday actions outside; put the rest in the menu
  • Separate anything destructive at the bottom with a rule
  • When there is no room below the trigger, flip the menu upward
  • If the trigger is icon-only, the dc-sr name says whose menu it is

Watch out for

  • Arrow keys must move between items — don't leave Tab as the only way through
  • One level of submenu is the limit, and on touch it reads better expanded in place

Next