20 CSS Hamburger Menus 09 / 20

Accessible Hamburger Menu (WCAG Compliant)

Full audit-ready pattern: aria-expanded, aria-controls, aria-label, skip link, visible focus rings, focus trap, Escape-to-close, and focus return.

CSS + JS MIT licensed
Live Demo Open in tab
Open in playground

The code

<div class="chm-09">
  <div class="chm-09__scene">
    <a class="chm-09__skip" href="#chm-09-main">Skip to content</a>

    <button class="chm-09__trigger" id="chm-09-trigger" aria-expanded="false" aria-controls="chm-09-menu" aria-label="Toggle navigation menu">
      <span class="chm-09__bars" aria-hidden="true"><i></i><i></i><i></i></span>
      <span class="chm-09__lbl-open">Menu</span><span class="chm-09__lbl-close">Close</span>
    </button>

    <div class="chm-09__scrim" id="chm-09-scrim"></div>
    <nav id="chm-09-menu" class="chm-09__menu" data-open="false" aria-label="Primary" aria-hidden="true">
      <p class="chm-09__grp">Primary navigation</p>
      <a href="#" aria-current="page">Home</a>
      <a href="#">Services</a>
      <a href="#">Case studies</a>
      <a href="#">Journal</a>
      <a href="#">Contact</a>
    </nav>

    <main id="chm-09-main" class="chm-09__wrap">
      <span class="chm-09__eyebrow">WCAG 2.2 AA</span>
      <h1>Navigation everyone can actually use.</h1>
      <p class="chm-09__lead">Full keyboard support, focus trapping, Escape to close, and screen-reader semantics baked in. Try it: <strong>Tab</strong> to the button, <strong>Enter</strong> to open, <strong>Esc</strong> to close.</p>
      <div class="chm-09__specs">
        <code>aria-expanded="false → true"</code>
        <code>aria-controls="menu"</code>
        <code>aria-label="Toggle navigation menu"</code>
        <code>Esc key + focus return + scrim dismiss</code>
      </div>
    </main>
    <div class="chm-09__tag">ACCESSIBLE // 2030</div>
  </div>
</div>
@import url('https://fonts.googleapis.com/css2?family=Newsreader:opsz,[email protected],400;6..72,500&family=IBM+Plex+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap');
.chm-09, .chm-09 *, .chm-09 *::before, .chm-09 *::after { box-sizing: border-box; margin: 0; padding: 0; }
.chm-09{
  --bg:#fcfbf8;--ink:#1b1b1b;--blue:#1d4ed8;--green:#15803d;--line:#e4e1d8;
  min-height:460px;display:grid;place-items:stretch;
  font-family:'IBM Plex Sans',sans-serif;background:var(--bg);color:var(--ink);
  position:relative;
}
.chm-09__scene{position:relative;width:100%;min-height:460px;height:100%;overflow:hidden}
.chm-09 a:focus-visible,.chm-09 button:focus-visible{outline:3px solid var(--blue);outline-offset:3px;border-radius:6px}
.chm-09__skip{position:absolute;left:-999px;top:12px;z-index:99;background:var(--ink);color:#fff;padding:8px 14px;border-radius:8px;text-decoration:none;font-weight:600;font-size:.85rem}
.chm-09__skip:focus{left:12px}
.chm-09__trigger{position:absolute;top:22px;right:22px;z-index:50;display:flex;align-items:center;gap:10px;
  background:#fff;border:2px solid var(--ink);border-radius:14px;padding:10px 14px;cursor:pointer;font-family:inherit;font-weight:600;font-size:.8rem;color:var(--ink);
  transition:transform .3s,box-shadow .3s}
.chm-09__trigger:hover{transform:translateY(-2px);box-shadow:4px 4px 0 var(--ink)}
.chm-09__bars{width:20px;height:13px;position:relative;display:inline-block}
.chm-09__bars i{position:absolute;left:0;height:2.4px;width:100%;background:var(--ink);border-radius:3px;transition:transform .45s cubic-bezier(.7,0,.2,1),opacity .3s}
.chm-09__bars i:nth-child(1){top:0}.chm-09__bars i:nth-child(2){top:5.5px}.chm-09__bars i:nth-child(3){top:11px}
.chm-09__trigger[aria-expanded="true"] .chm-09__bars i:nth-child(1){transform:translateY(5.5px) rotate(45deg)}
.chm-09__trigger[aria-expanded="true"] .chm-09__bars i:nth-child(2){opacity:0}
.chm-09__trigger[aria-expanded="true"] .chm-09__bars i:nth-child(3){transform:translateY(-5.5px) rotate(-45deg)}
.chm-09__lbl-open{display:inline}.chm-09__lbl-close{display:none}
.chm-09__trigger[aria-expanded="true"] .chm-09__lbl-open{display:none}
.chm-09__trigger[aria-expanded="true"] .chm-09__lbl-close{display:inline}
.chm-09__menu{position:absolute;top:0;right:0;height:100%;width:min(320px,84%);z-index:45;background:#fff;border-left:2px solid var(--ink);
  transform:translateX(100%);transition:transform .5s cubic-bezier(.16,1,.3,1);padding:88px 24px 24px;display:flex;flex-direction:column}
.chm-09__menu[data-open="true"]{transform:translateX(0)}
.chm-09__grp{font-family:'IBM Plex Mono',monospace;font-size:10px;letter-spacing:.2em;text-transform:uppercase;color:#777;margin-bottom:12px}
.chm-09__menu a{display:block;font-family:'Newsreader',serif;font-size:1.3rem;color:var(--ink);text-decoration:none;padding:10px 8px;border-radius:8px;transition:background .2s,color .2s}
.chm-09__menu a:hover{background:#eef2ff;color:var(--blue)}
.chm-09__menu a[aria-current="page"]{color:var(--green);font-weight:500}
.chm-09__scrim{position:absolute;inset:0;z-index:40;background:rgba(27,27,27,.35);opacity:0;pointer-events:none;transition:.4s}
.chm-09__scrim[data-show="true"]{opacity:1;pointer-events:auto}
.chm-09__wrap{max-width:700px;margin:0 auto;padding:80px 24px 60px}
.chm-09__eyebrow{display:inline-flex;align-items:center;gap:8px;font-family:'IBM Plex Mono',monospace;font-size:11px;letter-spacing:.2em;text-transform:uppercase;color:var(--green);font-weight:500;margin-bottom:14px}
.chm-09__eyebrow::before{content:"✓";display:grid;place-items:center;width:20px;height:20px;border-radius:50%;background:var(--green);color:#fff;font-size:11px}
.chm-09 h1{font-family:'Newsreader',serif;font-weight:400;font-size:clamp(1.8rem,5vw,3rem);line-height:1.04;letter-spacing:-.01em}
.chm-09__lead{margin-top:16px;font-size:.95rem;line-height:1.65;color:#444;max-width:540px}
.chm-09__specs{margin-top:24px;display:grid;gap:8px;font-family:'IBM Plex Mono',monospace;font-size:11px}
.chm-09__specs code{background:#f0eee7;border:1px solid var(--line);border-radius:6px;padding:7px 11px;color:var(--blue);display:block}
.chm-09__tag{position:absolute;bottom:18px;left:22px;font-family:'IBM Plex Mono',monospace;font-size:10px;letter-spacing:.3em;color:#999;z-index:5}

@media (prefers-reduced-motion: reduce){
  .chm-09__bars i,.chm-09__menu,.chm-09__scrim,.chm-09__trigger{transition:none !important}
}
(() => {
  const root = document.querySelector('.chm-09');
  if (!root) return;
  const trg = root.querySelector('#chm-09-trigger');
  const menu = root.querySelector('#chm-09-menu');
  const scrim = root.querySelector('#chm-09-scrim');
  let open = false;
  function setState(s){
    open = s;
    trg.setAttribute('aria-expanded', s);
    menu.dataset.open = s;
    menu.setAttribute('aria-hidden', !s);
    scrim.dataset.show = s;
    if (s) { menu.querySelector('a').focus(); }
    else { trg.focus(); }
  }
  function toggle(){ setState(!open); }
  trg.addEventListener('click', toggle);
  scrim.addEventListener('click', toggle);
  root.addEventListener('keydown', e => {
    if (e.key === 'Escape' && open) toggle();
    if (e.key === 'Tab' && open) {
      const f = [...menu.querySelectorAll('a')];
      const first = f[0], last = f[f.length - 1];
      if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }
      else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
    }
  });
})();

Search CodeFronts

Loading…