<a href="#main" class="skip-link sr-only sr-only-focusable">Skip to main content</a>
<header>
  <nav class="header-nav" aria-label="Main">
    <button class="header-nav__open" type="button" aria-expanded="false" aria-controls="menu">Menu</button>
    <div class="header-nav__container" id="menu">
      <button class="header-nav__close" type="button">Close</button>
      <ul>
        <li><a href="#">Lorem</a></li>
        <li><a href="#">Architecto</a></li>
        <li><a href="#">Quas</a></li>
        <li><a href="#">Nobis</a></li>
        <li><a href="#">Eligendi</a></li>
        <li><a href="#">Quaerat</a></li>
      </ul>
    </div>
    <div class="header-nav__curtain"></div>
  </nav>
</header>
<main id="main" tabindex="-1">
  <h1>AFTER Example Page</h1>
  <p>
    Header nav on Desktop, Slideout nav on mobile! Part of my
    <a href="https://stevenwoodson.com/blog/solving-animation-layout-flickering-caused-by-css-transitions/">
      "Solving Animation Layout Flickering Caused by CSS Transitions"</a> article. 
  </p>
  <p>
    This example fixes the 
    flash of menu styles when resizing from desktop to mobile. 
    <a href="https://codepen.io/stevenwoodson/pen/abjGjrr">Check this
      pen out if you'd like to see the before example</a>.
  </p>
  <p>
    This example is intentionally left lean on styles so you can take and
    adapt it to your needs more easily. Would love to see what you come up
    with, <a href="https://stevenwoodson.com/">send me a message</a>! 
  </p>
</main>
<footer>&copy; 2023 Awesome Site</footer>
$bp_mobile_max: 479px;
$bp_tablet_min: 480px;

$global-spacing: 20px;
$menu-width: 240px;

/* ==========================================================================
   Relevant Menu Styles
   ========================================================================== */
.header-nav {
  #menu {
    background: white;
    
    ul {
      list-style: none;
      margin: 0;
      padding: 0;
    }
    
    li {
      padding: $global-spacing;
    }
    
    @media (max-width: $bp_mobile_max) {
      bottom: 0;
      box-shadow: 0 0 12px 1px #00000075;
      height: calc(100% - 40px);
      left: 0;
      margin: 0;
      min-width: $menu-width;
      opacity: 0;
      overflow: auto;
      padding: 20px;
      position: absolute;
      transform: translateX(-100%);
      visibility: hidden;
      z-index: 100;
    }
    @media (min-width: $bp_tablet_min) {
      li {
        display: inline-block;
      }
    }
  }
  
  &__close {
    padding: 0 $global-spacing;
    
    @media (min-width: $bp_tablet_min) {
      display: none;
    }
  }
  
  &__open {
    padding: 0 $global-spacing;
    
    @media (min-width: $bp_tablet_min) {
      display: none;
    }
  }

  &__curtain {
    background: rgba(0, 0, 0, 0.6);
    height: 100vh;
    left: 0;
    opacity: 0;
    position: absolute;
    top: 0;
    transition: all 0.5s ease;
    visibility: hidden;
    width: 100%;
    z-index: 3;
  }

  #menu.nav-opening {
    transition: all 0.5s ease;
  }

  #menu.nav-opened {
    opacity: 1;
    transform: translateX(0);
    visibility: visible;
    
    & + .header-nav__curtain {
      opacity: 1;
      visibility: visible;
    }
  }
}

/* ==========================================================================
   Other styles for this example page
   ========================================================================== */

body {
  margin: 0 auto;
  max-width: 800px;
}

header, main, footer {
  padding: $global-spacing;
}

main {
  outline: 0;
  padding-bottom: 100px;

  &:focus {
    outline: 0;
  }
}

:focus,
a:focus,
button:focus,
input:not([type='checkbox']):not([type='radio']):focus,
select:focus,
textarea:focus {
  outline: 4px dashed #dc3542;
  outline-offset: 4px;
}

a.skip-link {
  transition: none;

  &:active,
  &:focus {
    left: 50%;
    padding: 1em;
    position: absolute;
    top: 10px;
    transform: translateX(-50%);
  }
}

.sr-only {
  border: 0;
  clip: rect(0, 0, 0, 0);
  height: 1px;
  margin: -1px;
  overflow: hidden;
  padding: 0;
  position: absolute;
  width: 1px;
}

.sr-only-focusable {
  &:active,
  &:focus {
    clip: auto;
    height: auto;
    margin: 0;
    overflow: visible;
    position: static;
    width: auto;
  }
}
View Compiled
(function () {
  'use strict';

  function SlideOutMenu(id) {
    this.el = document.querySelector(id);
    this.curtain = document.getElementsByClassName('header-nav__curtain')[0];
    this.closeButton = document.getElementsByClassName(
      'header-nav__close'
    )[0];
    this.openButton = document.getElementsByClassName(
      'header-nav__open'
    )[0];
    this.main = document.querySelector('main');
    
    // Find all focusable children
    const focusableElementsString =
      'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, [tabindex="0"], [contenteditable]';
    let focusableElements = this.el.querySelectorAll(focusableElementsString);
    focusableElements = Array.prototype.slice.call(focusableElements);
    this.firstTabStop = focusableElements[0];
    this.lastTabStop = focusableElements[focusableElements.length - 1];

    // Add event listeners
    this.closeButton.addEventListener('click', this.menuClose.bind(this));
    this.curtain.addEventListener('click', this.menuClose.bind(this));
    this.openButton.addEventListener('click', this.menuOpen.bind(this));
    this.el.addEventListener('transitionend', this.setFocus.bind(this));
    this.el.addEventListener('keydown', this.keydownEvent.bind(this));
  }

  SlideOutMenu.prototype.menuOpen = function (e) {
    e.preventDefault();
    this.el.classList.add('nav-opening');
    this.el.classList.add('nav-opened');
    this.openButton.setAttribute("aria-hidden","true");
    this.openButton.setAttribute("aria-expanded","true");
    this.main.setAttribute("aria-hidden","true");
  };

  SlideOutMenu.prototype.menuClose = function (e) {
    e.preventDefault();
    this.el.classList.remove('nav-opened');
    this.main.removeAttribute("aria-hidden");
    this.openButton.removeAttribute("aria-hidden");
  };

  SlideOutMenu.prototype.setFocus = function (e) {
    if (e.target == this.el && e.propertyName == 'opacity') {
      const compStyles = window.getComputedStyle(this.el);
      if (compStyles.opacity == 1) {
        this.firstTabStop.focus();
      } else {
        this.openButton.focus();
        this.el.classList.remove('nav-opening');
      }
    }
  };

  SlideOutMenu.prototype.keydownEvent = function (e) {
    // Escape key should close the menu
    if (e.key === 'Escape') {
      this.menuClose(e);
    }
    
    // Tab key check for first or last tab stop
    if (e.key === 'Tab') {
      // Tab + Shift (reverse tabbing)
      if (e.shiftKey) {
        // If the current item is the first tab stop, or it shares a name with it (for example a set of radio buttons)
        if (
          document.activeElement === this.firstTabStop ||
          (document.activeElement.name !== '' &&
           document.activeElement.name === this.firstTabStop.name)
        ) {
          e.preventDefault();
          this.lastTabStop.focus();
        }
      } else if (document.activeElement === this.lastTabStop) {
        e.preventDefault();
        this.firstTabStop.focus();
      }
    }
  };

  const menu = new SlideOutMenu('#menu');
})();

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.