Component Library

← Back to gallery

Mobile menu

Category: Navigation · Tags: hamburger, disclosure, menu, mobile, navigation

How to use it

Use on narrow screens to tuck a site's primary links behind one button; on wide screens show the links directly with the navigation bar instead. Replace the hrefs and labels and keep the list short. If you paste it twice, give each list its own id and update aria-controls; the script itself is scoped per copy.

Accessibility

The toggle is a native <button> with visible text "Menu" plus a decorative SVG icon, aria-controls pointing at the link list and aria-expanded kept in sync, so screen readers announce "Menu, collapsed/expanded". The list starts with the hidden attribute (kept hidden by .dcl-mobile-menu__list[hidden] despite its flex layout); Escape closes it and returns focus to the button. The button is #ffffff on #0f172a (17.85:1) and the links #0f172a on #ffffff (17.85:1), with 2px focus outlines on both.

Source (copy-ready)

<nav class="dcl-mobile-menu" aria-label="Main">
  <div class="dcl-mobile-menu__bar">
    <span class="dcl-mobile-menu__brand">Harbor Library</span>
    <button type="button" class="dcl-mobile-menu__toggle" aria-expanded="false" aria-controls="dcl-mobile-menu-list">
      <svg class="dcl-mobile-menu__icon" viewBox="0 0 20 20" width="20" height="20" aria-hidden="true" focusable="false"><path d="M3 5h14M3 10h14M3 15h14" stroke="#ffffff" stroke-width="2" fill="none" stroke-linecap="round"/></svg>
      <span class="dcl-mobile-menu__label">Menu</span>
    </button>
  </div>
  <ul class="dcl-mobile-menu__list" id="dcl-mobile-menu-list" hidden>
    <li><a class="dcl-mobile-menu__link" href="../index.html">Catalogue</a></li>
    <li><a class="dcl-mobile-menu__link" href="../index.html">Opening hours</a></li>
    <li><a class="dcl-mobile-menu__link" href="../about.html">About us</a></li>
  </ul>
</nav>
<style>
.dcl-mobile-menu { max-width: 22rem; border: 1px solid #e2e8f0; font: inherit; color: #0f172a; background-color: #ffffff; }
.dcl-mobile-menu__bar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 0.5rem; padding: 0.5rem 0.75rem; color: #0f172a; background-color: #ffffff; }
.dcl-mobile-menu__brand { font-weight: 700; color: #0f172a; background-color: #ffffff; }
.dcl-mobile-menu__toggle { display: inline-flex; align-items: center; gap: 0.5rem; min-height: 2.75rem; padding: 0.5rem 0.875rem; border: none; border-radius: 0.375rem; font: inherit; font-weight: 600; cursor: pointer; color: #ffffff; background-color: #0f172a; }
.dcl-mobile-menu__toggle:hover { color: #ffffff; background-color: #1e293b; }
.dcl-mobile-menu__toggle:focus-visible { outline: 2px solid #1d4ed8; outline-offset: 2px; }
.dcl-mobile-menu__label { color: #ffffff; background-color: #0f172a; }
.dcl-mobile-menu__icon { flex: none; }
.dcl-mobile-menu__list { display: flex; flex-direction: column; margin: 0; padding: 0.25rem 0; list-style: none; border-top: 1px solid #e2e8f0; }
.dcl-mobile-menu__list[hidden] { display: none; }
.dcl-mobile-menu__link { display: block; padding: 0.75rem 1rem; text-decoration: none; color: #0f172a; background-color: #ffffff; }
.dcl-mobile-menu__link:hover { text-decoration: underline; color: #0f172a; background-color: #f1f5f9; }
.dcl-mobile-menu__link:focus-visible { outline: 2px solid #1d4ed8; outline-offset: -2px; }
</style>
<script>
(function () {
  document.querySelectorAll(".dcl-mobile-menu").forEach(function (root) {
    if (root.dataset.dclBound) { return; }
    root.dataset.dclBound = "true";
    var toggle = root.querySelector(".dcl-mobile-menu__toggle");
    var list = root.querySelector(".dcl-mobile-menu__list");
    if (!toggle || !list) { return; }
    function setOpen(open) {
      toggle.setAttribute("aria-expanded", open ? "true" : "false");
      list.hidden = !open;
    }
    toggle.addEventListener("click", function () {
      setOpen(toggle.getAttribute("aria-expanded") !== "true");
    });
    root.addEventListener("keydown", function (event) {
      if (event.key === "Escape" && !list.hidden) {
        setOpen(false);
        toggle.focus();
      }
    });
  });
})();
</script>

Was this useful? Send feedback.