16 CSS Side Menu Designs 03 / 16

Off-Canvas Push Sidebar

A side navigation that physically shifts the entire main content area horizontally when it opens rather than overlapping it, creating a genuine off-canvas push effect.

Pure CSS MIT licensed
Live Demo Open in tab
Open in playground

The code

<div class="sm-03">
  <input type="checkbox" class="sm-03__toggle" id="sm-03-toggle">
  <nav class="sm-03__nav">
    <div class="sm-03__nav-header">
      <div class="sm-03__logo">G</div>
      <div class="sm-03__brand">Grove App</div>
    </div>
    <div class="sm-03__links">
      <a class="sm-03__link sm-03__link--active" href="#"><span class="sm-03__link-icon">▦</span> Home</a>
      <a class="sm-03__link" href="#"><span class="sm-03__link-icon">◉</span> Analytics</a>
      <a class="sm-03__link" href="#"><span class="sm-03__link-icon">◈</span> Reports</a>
      <a class="sm-03__link" href="#"><span class="sm-03__link-icon">◬</span> Team</a>
      <a class="sm-03__link" href="#"><span class="sm-03__link-icon">⬡</span> Settings</a>
    </div>
  </nav>
  <div class="sm-03__shell">
    <div class="sm-03__main">
      <div class="sm-03__topbar">
        <label class="sm-03__burger" for="sm-03-toggle">
          <span></span><span></span><span></span>
        </label>
        <div class="sm-03__page-title">Dashboard</div>
      </div>
      <div class="sm-03__body">
        <p class="sm-03__desc">The entire content area physically slides right when the menu opens — true off-canvas push behaviour with zero JS.</p>
        <div class="sm-03__stats">
          <div class="sm-03__stat"><div class="sm-03__stat-val">7,291</div><div class="sm-03__stat-lbl">Visitors</div></div>
          <div class="sm-03__stat"><div class="sm-03__stat-val">89%</div><div class="sm-03__stat-lbl">Retention</div></div>
          <div class="sm-03__stat"><div class="sm-03__stat-val">$9.1k</div><div class="sm-03__stat-lbl">Revenue</div></div>
          <div class="sm-03__stat"><div class="sm-03__stat-val">312</div><div class="sm-03__stat-lbl">Signups</div></div>
        </div>
      </div>
    </div>
  </div>
</div>
.sm-03, .sm-03 *, .sm-03 *::before, .sm-03 *::after {
  box-sizing: border-box; margin: 0; padding: 0;
}
.sm-03 ::selection { background: #10b981; color: #000; }
.sm-03 {
  --bg: #0a0f0d;
  --surface: #131a16;
  --nav-bg: #0d1a13;
  --accent: #10b981;
  --accent2: #34d399;
  --text: #ecfdf5;
  --muted: #6b7280;
  --border: rgba(16,185,129,0.15);
  --w: 260px;
  --dur: 0.4s;
  --ease: cubic-bezier(0.4, 0, 0.2, 1);
  font-family: 'DM Sans', system-ui, sans-serif;
  background: var(--bg);
  color: var(--text);
  min-height: 100vh;
  position: relative;
  overflow: hidden;
  border-radius: 12px;
}
.sm-03__toggle { position: absolute; opacity: 0; width: 0; height: 0; }
/* Layout shell: nav + main side by side */
.sm-03__shell {
  display: flex;
  height: 100%;
  min-height: 440px;
  transform: translateX(0);
  transition: transform var(--dur) var(--ease);
}
.sm-03__toggle:checked ~ .sm-03__shell {
  transform: translateX(var(--w));
}
/* Fixed nav (off-canvas) */
.sm-03__nav {
  position: absolute;
  left: 0; top: 0;
  width: var(--w);
  height: 100%;
  background: var(--nav-bg);
  border-right: 1px solid var(--border);
  transform: translateX(calc(-1 * var(--w)));
  transition: transform var(--dur) var(--ease);
  display: flex;
  flex-direction: column;
  padding: 24px 0;
  z-index: 20;
}
.sm-03__toggle:checked ~ .sm-03__nav {
  transform: translateX(0);
}
.sm-03__nav-header {
  display: flex; align-items: center; gap: 10px;
  padding: 0 20px 20px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 16px;
}
.sm-03__logo {
  width: 32px; height: 32px;
  background: var(--accent);
  border-radius: 8px;
  display: flex; align-items: center; justify-content: center;
  font-weight: 700; font-size: 14px; color: #000;
}
.sm-03__brand { font-weight: 700; font-size: 15px; }
.sm-03__links { flex: 1; padding: 0 10px; }
.sm-03__link {
  display: flex; align-items: center; gap: 12px;
  padding: 11px 14px;
  border-radius: 8px;
  color: var(--muted);
  font-size: 14px; font-weight: 500;
  cursor: pointer;
  transition: all 0.2s;
  margin-bottom: 3px;
  text-decoration: none;
}
.sm-03__link:hover { color: var(--text); background: rgba(16,185,129,0.1); }
.sm-03__link--active { color: var(--accent2); background: rgba(16,185,129,0.12); font-weight: 600; }
.sm-03__link-icon { font-size: 16px; }
/* Main content area */
.sm-03__main {
  flex: 1;
  min-width: 0;
  background: var(--bg);
  position: relative;
  z-index: 10;
}
.sm-03__topbar {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 18px 20px;
  border-bottom: 1px solid var(--border);
}
.sm-03__burger {
  width: 38px; height: 38px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  cursor: pointer;
  flex-shrink: 0;
  transition: background 0.2s;
}
.sm-03__burger:hover { background: rgba(16,185,129,0.2); }
.sm-03__burger span {
  width: 16px; height: 2px;
  background: var(--accent2);
  border-radius: 2px;
  transition: transform var(--dur), opacity var(--dur);
}
.sm-03__toggle:checked ~ .sm-03__shell .sm-03__burger span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.sm-03__toggle:checked ~ .sm-03__shell .sm-03__burger span:nth-child(2) { opacity: 0; }
.sm-03__toggle:checked ~ .sm-03__shell .sm-03__burger span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
.sm-03__page-title { font-size: 17px; font-weight: 700; }
.sm-03__body { padding: 20px; }
.sm-03__desc { font-size: 13px; color: var(--muted); line-height: 1.7; margin-bottom: 20px; }
.sm-03__stats {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}
.sm-03__stat {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 14px;
}
.sm-03__stat-val { font-size: 22px; font-weight: 700; color: var(--accent2); }
.sm-03__stat-lbl { font-size: 11px; color: var(--muted); margin-top: 3px; }
@media (prefers-reduced-motion: reduce) {
  .sm-03__shell, .sm-03__nav, .sm-03__burger span { transition: none; }
}

How this works

When the checkbox is checked, translate(var(--w)) is applied to the entire content shell, moving it right by the same amount as the nav width. Simultaneously the hidden nav slides from translateX(-100%) to translateX(0) — both transitions share the same duration and easing so they move as one unit.

The nav is positioned absolute behind the shell, meaning it becomes visible only as the main content slides far enough to reveal it. Zero overlap occurs at any point during the animation, producing the authentic push-sidebar feel found in native mobile apps.

Customize

  • Match the push distance to the nav width exactly — if you change --w: 260px, ensure the shell transform also uses var(--w).
  • Add a slight scale-down on the main content with scale(0.97) during the push for a perspective depth illusion.
  • Combine the push with a shadow: box-shadow: -20px 0 40px rgba(0,0,0,0.3) on the main block to reinforce layering depth.
  • Make a partial push by translating the shell less than the full nav width — set --w: 300px but shift the shell only 220px for a peeking overlap.
  • Invert to a right-hand push by placing the nav at right: 0 and shifting the shell with translateX(calc(-1 * var(--w))).

Watch out for

  • Any position:fixed children inside the shifted container (e.g. a sticky header) will not move with the push — they remain viewport-relative.
  • Do not apply overflow:hidden to the outer wrapper on the push axis — it clips the nav from appearing during the reveal.
  • On iOS, momentum scrolling inside the pushed container can cause visual artefacts during the transition; test on a real device.

Browser support

ChromeSafariFirefoxEdge
36+ 9+ 41+ 36+

CSS transforms on block-level elements are universally supported in all modern browsers.

Search CodeFronts

Loading…