Navigation · Complete runnable example

Responsive disclosure navigation

A navigation bar that folds neatly into a keyboard-friendly disclosure on small screens.

Live example

This preview runs in an isolated document. Clipboard writes and saved preferences may be blocked, and printing requires the standalone file. A dialog affects only the example. Reset reloads the original example state.

When to use it

Use this pattern for a small set of ordinary site links that should remain easy to reach on a narrow screen. It keeps navigation as a semantic nav landmark and uses a button to reveal its links.

Try the important details

  1. Choose a mobile preview width and open the navigation with Enter or Space.
  2. Move into the links, then press Escape. The disclosure closes and focus returns to its button.
  3. Resize between mobile and desktop and follow a section link to check the sticky-header clearance.

How it works

  • The button identifies its controlled element with aria-controls. Its aria-expanded state changes with actual link visibility.
  • Desktop and mobile states are reconciled on media-query changes, including focus when an element becomes hidden.
  • Ordinary site navigation uses links and a disclosure button. It does not use the ARIA menu role.

The complete source

The preview, copied text, and downloaded file use this same document.

HTML

<!doctype html>
<!--
MIT License

Copyright (c) 2026 MRHTML

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-->
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src 'none'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; img-src data:; connect-src 'none'; form-action 'none'; object-src 'none'; base-uri 'none'"
    />
    <title>MRHTML Studio | Responsive navigation</title>
    <style>
      :root {
        color-scheme: light;
        --ink: #172334;
        --muted: #536276;
        --blue: #164bbb;
        --pale: #e8effe;
        --paper: #f7f6f1;
        --line: #d8dee7;
        --header-clearance: 110px;
        font-family: Arial, Helvetica, sans-serif;
      }
      * {
        box-sizing: border-box;
      }
      html {
        scroll-padding-top: var(--header-clearance);
      }
      body {
        margin: 0;
        background: var(--paper);
        color: var(--ink);
        font-size: 16px;
        line-height: 1.6;
      }
      button {
        font: inherit;
      }
      a {
        color: var(--blue);
        text-underline-offset: 4px;
      }
      h1,
      h2,
      h3,
      p {
        margin: 0;
      }
      h1,
      h2,
      h3 {
        line-height: 1.12;
        letter-spacing: -0.045em;
      }
      h1 {
        font-size: clamp(37px, 6vw, 66px);
      }
      h2 {
        font-size: clamp(27px, 4vw, 39px);
      }
      h3 {
        font-size: 20px;
      }
      button,
      a,
      summary {
        -webkit-tap-highlight-color: transparent;
      }
      :focus-visible {
        outline: 3px solid var(--blue);
        outline-offset: 5px;
      }
      [hidden] {
        display: none !important;
      }
      ::selection {
        color: #fff;
        background: var(--blue);
      }
      .wrap {
        width: min(1040px, 100%);
        margin-inline: auto;
        padding-inline: clamp(22px, 5vw, 60px);
      }
      .eyebrow {
        color: var(--blue);
        font-size: 11px;
        letter-spacing: 0.12em;
        font-weight: 700;
        text-transform: uppercase;
      }
      .muted {
        color: var(--muted);
      }
      .button {
        display: inline-flex;
        align-items: center;
        justify-content: space-between;
        gap: 25px;
        min-height: 46px;
        padding: 11px 18px;
        border: 1px solid var(--blue);
        border-radius: 7px;
        background: var(--blue);
        color: white;
        text-decoration: none;
        font-size: 13px;
        font-weight: 700;
        transition:
          background 0.18s,
          transform 0.18s;
      }
      .button:hover {
        background: #10398e;
        transform: translateY(-2px);
      }
      .button span {
        font-size: 19px;
        line-height: 1;
      }
      .brand {
        display: inline-flex;
        gap: 10px;
        align-items: center;
        text-decoration: none;
        color: var(--ink);
        font-size: 18px;
        font-weight: 800;
        letter-spacing: -0.7px;
      }
      .brand-mark {
        display: grid;
        place-items: center;
        background: var(--blue);
        color: white;
        border-radius: 8px;
        width: 32px;
        height: 32px;
        font-size: 17px;
        letter-spacing: -2px;
        padding-right: 2px;
      }
      .skip-link {
        position: fixed;
        top: 12px;
        left: 12px;
        z-index: 100;
        background: var(--blue);
        color: white;
        padding: 12px 18px;
        border: 2px solid white;
        border-radius: 7px;
        box-shadow: 0 4px 20px #17233430;
        transform: translateY(-160%);
        font-weight: 700;
        font-size: 14px;
      }
      .skip-link:focus {
        transform: translateY(0);
      }
      .footer {
        font-size: 11px;
        color: var(--muted);
        display: flex;
        justify-content: space-between;
        gap: 15px;
        padding-block: 24px;
        border-top: 1px solid var(--line);
      }
      main:focus-visible,
      section[tabindex='-1']:focus-visible {
        outline: 3px solid var(--blue);
        outline-offset: -3px;
        border-radius: 8px;
      }
      @media (prefers-reduced-motion: reduce) {
        *,
        *::before,
        *::after {
          animation: none !important;
          transition: none !important;
          scroll-behavior: auto !important;
        }
      }

      .site-header {
        position: sticky;
        top: 0;
        z-index: 10;
        background: #fff;
        border-bottom: 1px solid var(--line);
      }
      .header-row {
        min-height: 82px;
        display: flex;
        align-items: center;
        gap: 25px;
      }
      .site-nav {
        margin-left: auto;
      }
      .site-nav ul {
        display: flex;
        gap: 8px;
        list-style: none;
        margin: 0;
        padding: 0;
      }
      .site-nav a {
        display: block;
        padding: 12px 14px;
        font-size: 13px;
        color: var(--ink);
        text-decoration: none;
        border-radius: 6px;
      }
      .site-nav a:hover {
        background: var(--pale);
        color: var(--blue);
      }
      .site-nav li:last-child a {
        background: var(--ink);
        color: #fff;
        margin-left: 5px;
      }
      .menu-toggle {
        margin-left: auto;
        display: flex;
        align-items: center;
        justify-content: center;
        gap: 14px;
        min-height: 44px;
        padding: 9px 14px;
        border: 1px solid var(--line);
        border-radius: 6px;
        background: #fff;
        color: var(--ink);
        font-size: 13px;
        cursor: pointer;
      }
      .menu-icon {
        width: 16px;
        height: 14px;
        position: relative;
        background: linear-gradient(currentColor, currentColor) center / 100%
          2px no-repeat;
      }
      .menu-icon::before,
      .menu-icon::after {
        content: '';
        position: absolute;
        left: 0;
        right: 0;
        height: 2px;
        background: currentColor;
        transition: transform 0.18s;
      }
      .menu-icon::before {
        top: 0;
      }
      .menu-icon::after {
        bottom: 0;
      }
      .menu-toggle[aria-expanded='true'] .menu-icon {
        background: none;
      }
      .menu-toggle[aria-expanded='true'] .menu-icon::before {
        transform: translateY(6px) rotate(45deg);
      }
      .menu-toggle[aria-expanded='true'] .menu-icon::after {
        transform: translateY(-6px) rotate(-45deg);
      }
      .hero {
        padding-block: 65px 45px;
        display: grid;
        grid-template-columns: 1.45fr 1fr;
        gap: 40px;
        align-items: center;
      }
      .hero h1 {
        margin-top: 16px;
      }
      .hero h1 span {
        color: var(--blue);
      }
      .hero p:not(.eyebrow) {
        max-width: 400px;
        font-size: 15px;
        color: var(--muted);
        margin-top: 20px;
      }
      .hero .button {
        margin-top: 26px;
      }
      .hero-art {
        aspect-ratio: 1;
        background: var(--pale);
        border-radius: 24px;
        position: relative;
        overflow: hidden;
      }
      .shape-a {
        position: absolute;
        left: 18%;
        top: 17%;
        width: 50%;
        height: 62%;
        background: var(--blue);
        border-radius: 50% 50% 12px 12px;
        transform: rotate(-12deg);
        box-shadow: 12px 20px 0 #b1c5f1;
      }
      .shape-b {
        position: absolute;
        width: 36%;
        aspect-ratio: 1;
        background: #f5bc64;
        border-radius: 50%;
        right: 11%;
        bottom: 12%;
        border: 7px solid var(--pale);
      }
      .art-label {
        position: absolute;
        left: 20px;
        top: 17px;
        font-size: 9px;
        color: #304b80;
        font-weight: 700;
        letter-spacing: 1px;
      }
      .section {
        padding-block: 35px 45px;
      }
      .section-heading {
        display: flex;
        align-items: end;
        justify-content: space-between;
        gap: 20px;
        margin-bottom: 22px;
      }
      .section-heading > p {
        font-size: 12px;
        color: var(--muted);
        max-width: 195px;
      }
      .project-grid {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 20px;
      }
      .project {
        padding: 28px;
        background: white;
        border: 1px solid var(--line);
        border-radius: 12px;
      }
      .project-number {
        font-size: 11px;
        color: var(--blue);
        font-weight: 700;
        display: block;
        margin-bottom: 27px;
      }
      .project p {
        color: var(--muted);
        font-size: 14px;
        margin-top: 12px;
      }
      .process {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 35px;
        border-top: 1px solid var(--line);
      }
      .process p {
        font-size: 14px;
        color: var(--muted);
        margin-top: 14px;
        max-width: 430px;
      }
      .process ol {
        margin: 0;
        padding-left: 23px;
        font-size: 14px;
      }
      .process li {
        padding: 9px 0 9px 9px;
      }
      .contact {
        background: var(--ink);
        color: #fff;
        border-radius: 14px;
        padding: 32px;
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 30px;
        margin-bottom: 45px;
      }
      .contact h2 {
        font-size: 29px;
      }
      .contact p {
        color: #c4cfdf;
        font-size: 13px;
        margin-top: 12px;
        max-width: 340px;
      }
      .contact .button {
        background: #fff;
        color: var(--ink);
        border-color: white;
        flex-shrink: 0;
      }
      .contact .button:focus-visible {
        outline-color: #f5bc64;
      }
      .contact:focus-visible {
        outline-color: #f5bc64;
      }
      @media (max-width: 680px) {
        :root {
          --header-clearance: 235px;
        }
        .header-row {
          min-height: 76px;
          flex-wrap: wrap;
          gap: 0;
          padding-block: 16px;
        }
        .site-nav {
          width: 100%;
          margin: 12px 0 0;
        }
        .site-nav ul {
          flex-direction: column;
          gap: 2px;
        }
        .site-nav a {
          padding: 10px 13px;
        }
        .site-nav li:last-child a {
          margin: 3px 0 0;
        }
        .hero {
          grid-template-columns: 1fr;
          gap: 28px;
          padding-top: 40px;
        }
        .hero-art {
          aspect-ratio: 1.7;
          max-width: 440px;
        }
        .shape-a {
          top: 14%;
          height: 67%;
          width: 32%;
          left: 28%;
        }
        .shape-b {
          width: 25%;
          bottom: 11%;
          right: 22%;
        }
        .project-grid,
        .process {
          grid-template-columns: 1fr;
        }
        .project {
          padding: 23px;
        }
        .project-number {
          margin-bottom: 18px;
        }
        .section-heading {
          align-items: start;
          flex-direction: column;
          gap: 13px;
        }
        .section-heading > p {
          max-width: none;
        }
        .process {
          gap: 15px;
        }
        .contact {
          padding: 25px;
          align-items: start;
          flex-direction: column;
          gap: 22px;
        }
        .footer {
          flex-wrap: wrap;
        }
        .section {
          padding-top: 25px;
        }
      }
    </style>
  </head>
  <body>
    <a class="skip-link" href="#main">Skip to main content</a>
    <header class="site-header">
      <div class="wrap header-row">
        <a class="brand" href="#main" aria-label="MRHTML Studio home"
          ><span class="brand-mark" aria-hidden="true">m.</span>MRHTML Studio</a
        ><button
          class="menu-toggle"
          id="menu-toggle"
          type="button"
          aria-expanded="false"
          aria-controls="site-navigation"
          hidden
        >
          Menu <span class="menu-icon" aria-hidden="true"></span>
        </button>
        <nav class="site-nav" id="site-navigation" aria-label="Main navigation">
          <ul>
            <li><a href="#work">Our work</a></li>
            <li><a href="#process">Our process</a></li>
            <li>
              <a href="#contact"
                >Start a project <span aria-hidden="true">↗</span></a
              >
            </li>
          </ul>
        </nav>
      </div>
    </header>
    <main class="wrap" id="main" tabindex="-1">
      <section class="hero" aria-labelledby="hero-title">
        <div>
          <p class="eyebrow">Small studio. Clear ideas.</p>
          <h1 id="hero-title">
            Good design<br />starts with<br /><span>a little clarity.</span>
          </h1>
          <p>
            Thoughtful websites for people making something useful. A simple
            studio page, with navigation that fits the screen.
          </p>
          <a class="button" href="#work"
            >Explore our work <span aria-hidden="true">↘</span></a
          >
        </div>
        <div
          class="hero-art"
          role="img"
          aria-label="An abstract blue arch and golden circle"
        >
          <span class="art-label">FORM + PURPOSE</span
          ><span class="shape-a"></span><span class="shape-b"></span>
        </div>
      </section>
      <section
        class="section"
        id="work"
        tabindex="-1"
        aria-labelledby="work-title"
      >
        <div class="section-heading">
          <h2 id="work-title">A considered approach.</h2>
          <p>Two sample directions, made to be your own.</p>
        </div>
        <div class="project-grid">
          <article class="project">
            <span class="project-number">01 / DIGITAL EXPERIENCES</span>
            <h3>A more useful first impression</h3>
            <p>
              Clear structure, thoughtful details and a path that helps visitors
              find what matters.
            </p>
          </article>
          <article class="project">
            <span class="project-number">02 / BRAND EXPRESSION</span>
            <h3>A point of view, made visible</h3>
            <p>
              Simple visual systems that give good ideas a distinct and
              consistent presence.
            </p>
          </article>
        </div>
      </section>
      <section
        class="section process"
        id="process"
        tabindex="-1"
        aria-labelledby="process-title"
      >
        <div>
          <p class="eyebrow">How ideas take shape</p>
          <h2 id="process-title">Listen. Explore. Refine.</h2>
          <p>
            A practical starting point for a small studio's process, written as
            sample content for this MRHTML example.
          </p>
        </div>
        <ol>
          <li>Understand the people and the purpose.</li>
          <li>Make a few promising directions tangible.</li>
          <li>Test the details and simplify the experience.</li>
        </ol>
      </section>
      <section
        class="contact"
        id="contact"
        tabindex="-1"
        aria-labelledby="contact-title"
      >
        <div>
          <h2 id="contact-title">Make room for your next idea.</h2>
          <p>
            This is a static demo contact section. Add your own verified contact
            destination when adapting it.
          </p>
        </div>
        <a class="button" href="#process"
          >See the process <span aria-hidden="true">↑</span></a
        >
      </section>
    </main>
    <footer class="wrap footer">
      <span>MRHTML / Original navigation example</span
      ><span>Built around real links and a simple disclosure.</span>
    </footer>
    <script>
      // MRHTML: start with visible links, then enhance narrow-screen navigation.
      const toggle = document.getElementById('menu-toggle');
      const nav = document.getElementById('site-navigation');
      const header = document.querySelector('.site-header');
      const compact = window.matchMedia('(max-width: 680px)');
      const updateClearance = () => {
        document.documentElement.style.setProperty(
          '--header-clearance',
          `${header.getBoundingClientRect().height + 20}px`,
        );
      };
      const setOpen = (open) => {
        const visible = !compact.matches || open;
        nav.hidden = !visible;
        toggle.setAttribute('aria-expanded', String(visible));
        updateClearance();
      };
      const syncViewport = () => {
        const active = document.activeElement;
        if (compact.matches) {
          toggle.hidden = false;
          // Move focus before hiding the navigation that contains it.
          if (nav.contains(active)) toggle.focus({ preventScroll: true });
          setOpen(false);
        } else {
          setOpen(true);
          // The disclosure disappears on wide screens; keep focus on a visible link.
          if (active === toggle)
            nav.querySelector('a').focus({ preventScroll: true });
          toggle.hidden = true;
          updateClearance();
        }
      };
      toggle.addEventListener('click', () =>
        setOpen(toggle.getAttribute('aria-expanded') !== 'true'),
      );
      header.addEventListener('keydown', (event) => {
        if (event.key === 'Escape' && compact.matches && !nav.hidden) {
          event.preventDefault();
          toggle.focus({ preventScroll: true });
          setOpen(false);
        }
      });
      document.addEventListener('click', (event) => {
        const link = event.target.closest('a[href^="#"]');
        if (!link) return;
        const destination = document.getElementById(
          link.getAttribute('href').slice(1),
        );
        if (!destination) return;
        event.preventDefault();
        if (compact.matches) setOpen(false);
        destination.focus({ preventScroll: true });
        destination.scrollIntoView({ block: 'start', behavior: 'auto' });
      });
      compact.addEventListener('change', syncViewport);
      new ResizeObserver(updateClearance).observe(header);
      syncViewport();
    </script>
  </body>
</html>