메뉴바

Menubar

파일·편집·보기처럼 가로로 늘어선 최상위 항목마다 하위 명령 메뉴를 펼치는, 데스크톱 앱 상단의 막대.

다른 이름: Application menu barMenu bar
···
html
<div class="mbw">
  <div class="mbinner">
    <div class="mbbar" role="menubar" aria-label="애플리케이션 메뉴" id="mbbar">
      <button role="menuitem" aria-haspopup="menu" aria-expanded="true" tabindex="0" id="mb1">파일</button>
      <button role="menuitem" aria-haspopup="menu" aria-expanded="false" tabindex="-1" id="mb2">편집</button>
      <button role="menuitem" aria-haspopup="menu" aria-expanded="false" tabindex="-1" id="mb3">보기</button>
    </div>
    <ul class="mbmenu" role="menu" aria-labelledby="mb1" id="mbmenu">
      <li role="menuitem" tabindex="-1">새 파일</li>
      <li role="menuitem" tabindex="-1">열기…</li>
      <li role="menuitem" tabindex="-1">저장</li>
    </ul>
  </div>
</div>
css
.mbw{position:absolute;inset:0;display:flex;align-items:center;justify-content:center}
.mbinner{position:relative}
.mbbar{display:flex;background:var(--surface);border:1px solid var(--line);border-radius:9px 9px 0 0;overflow:hidden}
.mbbar button{border:0;background:none;color:var(--fg);font-size:11px;font-weight:600;padding:8px 14px;cursor:default}
.mbbar button[aria-expanded=true]{background:var(--accent);color:#fff}
.mbmenu{position:absolute;top:100%;left:0;list-style:none;margin:0;padding:5px;width:130px;background:var(--surface);border:1px solid var(--line);border-radius:0 8px 8px 8px;box-shadow:0 14px 30px rgba(0,0,0,.2)}
.mbmenu li{padding:7px 9px;border-radius:6px;font-size:11px;color:var(--fg)}
js
const items = [document.getElementById('mb1'), document.getElementById('mb2'), document.getElementById('mb3')];
const menu = document.getElementById('mbmenu');
const CONTENT = {
  mb1: ['새 파일', '열기…', '저장'],
  mb2: ['실행 취소', '다시 실행', '전체 선택'],
  mb3: ['확대', '축소', '전체 화면'],
};
function openIdx(i) {
  items.forEach((it, idx) => { it.setAttribute('aria-expanded', String(idx === i)); it.tabIndex = idx === i ? 0 : -1; });
  menu.setAttribute('aria-labelledby', items[i].id);
  menu.innerHTML = CONTENT[items[i].id].map((label) => '<li role="menuitem" tabindex="-1">' + label + '</li>').join('');
}
let auto = true, cur = 0;
openIdx(0);
items.forEach((it, i) => it.addEventListener('click', () => { auto = false; cur = i; openIdx(i); }));
document.getElementById('mbbar').addEventListener('keydown', (e) => {
  if (e.key !== 'ArrowRight' && e.key !== 'ArrowLeft') return;
  auto = false; e.preventDefault();
  cur = (cur + (e.key === 'ArrowRight' ? 1 : -1) + items.length) % items.length;
  openIdx(cur); items[cur].focus();
});
setInterval(() => { if (!auto) return; cur = (cur + 1) % items.length; openIdx(cur); }, 1400);

컨테이너는 role="menubar", 각 최상위 항목은 role="menuitem"이며 aria-haspopup="menu"와 aria-expanded로 자신이 펼치는 하위 메뉴 상태를 알립니다. 좌우 방향키로 최상위 항목 사이를 이동하는데, 이미 메뉴 하나가 열려 있는 상태에서 좌우로 이동하면 다음 항목의 메뉴가 자동으로 열리는 것이 관례입니다(다시 눌러서 열 필요가 없음). 아래 방향키는 열린 메뉴 안으로 들어가고, Esc는 메뉴를 닫고 포커스를 메뉴바 항목으로 되돌립니다.

툴바와 다른 점은 눌렀을 때 무슨 일이 일어나느냐입니다 — 툴바의 버튼은 눌리는 즉시 실행되거나 상태를 토글하지만, 메뉴바 항목은 그 자체로는 아무것도 실행하지 않고 항상 하위 메뉴부터 펼칩니다. 드롭다운 메뉴와 다른 점은 개수입니다 — 드롭다운 메뉴는 트리거 버튼 하나에 메뉴 하나가 붙지만, 메뉴바는 한 랜드마크 안에 여러 트리거가 나란히 있고 그 사이를 이동하는 규칙까지 포함합니다.

웹 앱에서는 잘 안 쓰이다가 코드 에디터, 디자인 툴처럼 데스크톱 애플리케이션을 웹으로 재현하는 제품에서 다시 등장하는 패턴입니다.