Skip to content

Sneaky Slideout

The dropdown was cute. The drawer is cooler.

This chapter upgrades your hamburger from a simple show/hide toggle to a slide-in side menu that feels more like a real app: nav items live off-canvas until summoned.

No scroll-jank. No links falling off the edge. Just a smooth, contained slide.


A slideout drawer shines when:

  • you have more links than a tiny dropdown can comfortably hold
  • you want a stronger spatial metaphor (“the menu lives over here”)
  • your content is tall and you don’t want the nav shoving everything down the page
  • you want something that feels a bit more “app-like” without going full SPA

We keep the same hamburger button and toggle logic — only the layout changes.


We’re still building on the same basic structure:

<header class="site-header">
<div class="brand-lockup">
<span class="site-logo"></span>
<span class="site-title">WebDevTnT</span>
</div>
<button
class="nav-toggle"
type="button"
aria-expanded="false"
aria-controls="site-nav"
>
<span class="visually-hidden">Toggle navigation</span>
<span class="nav-toggle-line"></span>
<span class="nav-toggle-line"></span>
<span class="nav-toggle-line"></span>
</button>
<nav id="site-nav" class="site-nav" aria-label="Main">
<ul class="nav-list">
<li><a href="#">Overview</a></li>
<li><a href="#">Lessons</a></li>
<li><a href="#">Demos</a></li>
<li><a href="#">Assignments</a></li>
<li><a href="#">Resources</a></li>
</ul>
</nav>
</header>

The magic is in how the .site-nav is positioned and animated on small screens.


On mobile, the nav moves off to the left and slides in when open.

/* Mobile drawer */
.site-nav {
position: fixed;
inset: 0 auto 0 0; /* left edge of viewport */
width: min(280px, 80vw);
padding: 1rem 1.25rem 1.5rem;
background: #020617;
box-shadow: 12px 0 40px rgba(15, 23, 42, 0.85);
transform: translateX(-100%); /* start off-screen */
opacity: 0;
pointer-events: none;
transition: transform 0.22s ease-out, opacity 0.22s ease-out;
}
.site-nav.is-open {
transform: translateX(0);
opacity: 1;
pointer-events: auto;
}
.nav-list {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
/* Desktop: regular inline menu, no drawer */
@media (min-width: 960px) {
.site-nav {
position: static;
width: auto;
padding: 0;
box-shadow: none;
transform: none;
opacity: 1;
pointer-events: auto;
}
.nav-list {
flex-direction: row;
gap: 1rem;
justify-content: flex-end;
}
}

You can optionally add a dimming overlay behind the drawer using a sibling element or a ::before pseudo-element. The live demo keeps it simple.


The JS is nearly identical to Bun and Done — the drawer is just using transform instead of display.

const navToggle = document.querySelector('.nav-toggle');
const nav = document.querySelector('#site-nav');
if (navToggle && nav) {
navToggle.addEventListener('click', () => {
const isOpen = nav.classList.toggle('is-open');
navToggle.setAttribute('aria-expanded', String(isOpen));
});
}

One button, one class, one animation.


This demo shows the core drawer behavior inside the lesson itself.

Tap the bun to slide the menu in from the side. Tap again to tuck it back into the shadows.


  • A slideout drawer lives off-canvas until needed.
  • Same toggle logic, different layout: one button, one target, one state.
  • transform: translateX() + transition is the core of the effect.
  • Desktop doesn’t need the drawer — it can still render a regular inline nav using the same HTML.

05 · Overlay Dramatic
The drawer goes big: the nav stops hugging one side and takes over the whole screen for a full-bleed, cinematic overlay.