16 CSS Side Menu Designs 15 / 16

Collapsible Sidebar Menu with CSS Transitions

A sidebar that fluidly animates between full expanded width and icon-only collapsed state using transition:width, with text labels and badges fading out in sync.

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

The code

<div class="sm-15">
  <input type="checkbox" class="sm-15__toggle" id="sm-15-toggle">
  <div class="sm-15__layout">
    <nav class="sm-15__nav">
      <div class="sm-15__brand">
        <div class="sm-15__logo">R</div>
        <span class="sm-15__brand-name">Recto</span>
      </div>
      <div class="sm-15__links">
        <a class="sm-15__link sm-15__link--active" href="#">
          <span class="sm-15__link-icon">⬡</span>
          <span class="sm-15__link-text">Dashboard</span>
        </a>
        <a class="sm-15__link" href="#">
          <span class="sm-15__link-icon">◈</span>
          <span class="sm-15__link-text">Analytics</span>
          <span class="sm-15__badge">9</span>
        </a>
        <a class="sm-15__link" href="#">
          <span class="sm-15__link-icon">▦</span>
          <span class="sm-15__link-text">Products</span>
        </a>
        <a class="sm-15__link" href="#">
          <span class="sm-15__link-icon">◉</span>
          <span class="sm-15__link-text">Orders</span>
          <span class="sm-15__badge">3</span>
        </a>
        <a class="sm-15__link" href="#">
          <span class="sm-15__link-icon">◬</span>
          <span class="sm-15__link-text">Customers</span>
        </a>
        <a class="sm-15__link" href="#">
          <span class="sm-15__link-icon">⬙</span>
          <span class="sm-15__link-text">Settings</span>
        </a>
      </div>
      <label class="sm-15__collapse-btn" for="sm-15-toggle" title="Collapse sidebar">‹</label>
      <div class="sm-15__user">
        <div class="sm-15__user-avatar">WP</div>
        <div class="sm-15__user-info">
          <div class="sm-15__user-name">W. Park</div>
          <div class="sm-15__user-role">Admin</div>
        </div>
      </div>
    </nav>
    <div class="sm-15__main">
      <div class="sm-15__heading">Dashboard</div>
      <div class="sm-15__sub">Click ‹ to collapse the sidebar from full width to icon-only. Driven by <code>transition: width</code> — zero JS.</div>
      <div class="sm-15__grid">
        <div class="sm-15__card"><div class="sm-15__card-val">$82k</div><div class="sm-15__card-lbl">Revenue</div></div>
        <div class="sm-15__card"><div class="sm-15__card-val">1,247</div><div class="sm-15__card-lbl">Orders</div></div>
        <div class="sm-15__card"><div class="sm-15__card-val">98%</div><div class="sm-15__card-lbl">Uptime</div></div>
        <div class="sm-15__card"><div class="sm-15__card-val">4.9★</div><div class="sm-15__card-lbl">Rating</div></div>
      </div>
    </div>
  </div>
</div>
.sm-15, .sm-15 *, .sm-15 *::before, .sm-15 *::after {
  box-sizing: border-box; margin: 0; padding: 0;
}
.sm-15 ::selection { background: #e11d48; color: #fff; }
.sm-15 {
  --bg: #09090b;
  --surface: #141418;
  --nav-bg: #0f0f12;
  --accent: #e11d48;
  --accent2: #fb7185;
  --text: #f1f5f9;
  --muted: #52525b;
  --border: rgba(225,29,72,0.12);
  --font: 'Sora', system-ui, sans-serif;
  --full-w: 240px;
  --collapsed-w: 64px;
  --dur: 0.35s;
  --ease: cubic-bezier(0.4, 0, 0.2, 1);
  font-family: var(--font);
  background: var(--bg);
  color: var(--text);
  display: flex;
  min-height: 100vh;
  border-radius: 12px;
  overflow: hidden;
}
/* Toggle */
.sm-15__toggle { display: none; }
/* Sidebar */
.sm-15__nav {
  width: var(--full-w);
  background: var(--nav-bg);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  flex-shrink: 0;
  overflow: hidden;
  transition: width var(--dur) var(--ease);
  position: relative;
}
.sm-15__toggle:checked ~ .sm-15__layout .sm-15__nav {
  width: var(--collapsed-w);
}
/* Shell wrapper to allow :checked targeting */
.sm-15__layout {
  display: flex;
  flex: 1;
  min-width: 0;
}
/* Brand */
.sm-15__brand {
  display: flex; align-items: center;
  padding: 20px 10px 18px 10px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 12px;
  overflow: hidden;
  white-space: nowrap;
  gap: 0;
  min-width: 0;
}
.sm-15__logo {
  width: 36px; height: 36px;
  background: linear-gradient(135deg, var(--accent), var(--accent2));
  border-radius: 9px;
  display: flex; align-items: center; justify-content: center;
  font-weight: 800; font-size: 16px; color: #fff;
  flex-shrink: 0;
}
.sm-15__brand-name {
  font-size: 15px; font-weight: 800;
  margin-left: 10px;
  overflow: hidden;
  opacity: 1;
  transition: opacity var(--dur) var(--ease), max-width var(--dur) var(--ease);
  max-width: 160px;
}
.sm-15__toggle:checked ~ .sm-15__layout .sm-15__brand-name {
  opacity: 0;
  max-width: 0;
}
/* Collapse toggle button */
.sm-15__collapse-btn {
  align-self: flex-end;
  margin: 8px 12px;
  width: 28px; height: 28px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  font-size: 12px;
  color: var(--muted);
  flex-shrink: 0;
  transition: transform var(--dur) var(--ease), align-self var(--dur) var(--ease), margin var(--dur) var(--ease), background 0.2s, color 0.2s;
}
.sm-15__toggle:checked ~ .sm-15__layout .sm-15__collapse-btn {
  align-self: center;
  margin: 8px 0;
  transform: rotate(180deg);
}
.sm-15__collapse-btn:hover { background: var(--accent); color: #fff; border-color: var(--accent); }
/* Links */
.sm-15__links { flex: 1; padding: 0 6px; }
.sm-15__link {
  display: flex; align-items: center;
  padding: 11px 7px;
  border-radius: 9px;
  color: var(--muted);
  font-size: 13px; font-weight: 600;
  cursor: pointer; text-decoration: none;
  transition: all 0.2s;
  margin-bottom: 4px;
  white-space: nowrap;
  overflow: hidden;
  gap: 0;
}
.sm-15__link:hover { color: var(--text); background: rgba(225,29,72,0.08); }
.sm-15__link--active { color: var(--accent2); background: rgba(225,29,72,0.1); }
.sm-15__link-icon {
  width: 36px; height: 36px;
  display: flex; align-items: center; justify-content: center;
  font-size: 17px;
  flex-shrink: 0;
  border-radius: 8px;
  transition: background 0.2s;
}
.sm-15__link:hover .sm-15__link-icon { background: rgba(225,29,72,0.15); }
.sm-15__link--active .sm-15__link-icon { background: rgba(225,29,72,0.18); }
.sm-15__link-text {
  margin-left: 6px;
  opacity: 1;
  transition: opacity var(--dur) var(--ease), max-width var(--dur) var(--ease);
  max-width: 160px;
  overflow: hidden;
}
.sm-15__toggle:checked ~ .sm-15__layout .sm-15__link-text {
  opacity: 0;
  max-width: 0;
  margin-left: 0;
}
.sm-15__badge {
  margin-left: auto;
  background: rgba(225,29,72,0.2);
  color: var(--accent2);
  font-size: 10px;
  font-weight: 700;
  padding: 2px 7px;
  border-radius: 99px;
  transition: opacity var(--dur);
}
.sm-15__toggle:checked ~ .sm-15__layout .sm-15__badge { opacity: 0; }
/* Footer user */
.sm-15__user {
  padding: 14px 8px 18px;
  border-top: 1px solid var(--border);
  display: flex; align-items: center; overflow: hidden;
  transition: justify-content var(--dur) var(--ease);
}
.sm-15__toggle:checked ~ .sm-15__layout .sm-15__user { justify-content: center; padding: 14px 0 18px; }
.sm-15__user-avatar {
  width: 36px; height: 36px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--accent), #8b5cf6);
  display: flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 700; color: #fff;
  flex-shrink: 0;
}
.sm-15__user-info {
  margin-left: 10px;
  opacity: 1;
  transition: opacity var(--dur), max-width var(--dur) var(--ease);
  max-width: 160px; white-space: nowrap; overflow: hidden;
}
.sm-15__toggle:checked ~ .sm-15__layout .sm-15__user-info { opacity: 0; max-width: 0; margin-left: 0; }
.sm-15__user-name { font-size: 12px; font-weight: 700; }
.sm-15__user-role { font-size: 10px; color: var(--muted); }
/* Main */
.sm-15__main { flex: 1; padding: 24px; min-width: 0; }
.sm-15__heading { font-size: 20px; font-weight: 800; margin-bottom: 6px; }
.sm-15__sub { font-size: 13px; color: var(--muted); line-height: 1.7; margin-bottom: 22px; }
.sm-15__grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.sm-15__card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 9px;
  padding: 14px;
}
.sm-15__card-val { font-size: 22px; font-weight: 800; color: var(--accent2); }
.sm-15__card-lbl { font-size: 11px; color: var(--muted); margin-top: 3px; }
@media (prefers-reduced-motion: reduce) {
  .sm-15__nav, .sm-15__brand-name, .sm-15__link-text,
  .sm-15__badge, .sm-15__user-info, .sm-15__collapse-btn { transition: none; }
}

How this works

The nav element's width transitions between var(--full-w) (240px) and var(--collapsed-w) (56px) on a checkbox toggle. Text labels use max-width transitioning from their natural value to 0 combined with opacity: 0 — this prevents wrapping inside the narrowed container. overflow: hidden on the nav clips all content during the transition.

A circular collapse toggle button at the nav's right edge uses transform: rotate(180deg) on :checked to flip its arrow direction. Positioned at right: -14px it overlaps the main content border slightly for a clean pivot-point appearance. All transitions share the same var(--dur) and var(--ease) variables for perfect synchronisation.

Customize

  • Change the collapsed width to 44px for an ultra-thin icon strip, or 72px for larger touch targets on tablet interfaces.
  • Add a localStorage-backed JS snippet to persist collapsed state across page loads — read saved value and set checkbox checked on DOMContentLoaded.
  • Replace the ‹ arrow with a custom SVG that morphs between hamburger and X using SMIL or CSS Houdini path animation.
  • Show a tooltip with the link name on collapsed icons using a CSS ::after on each link that appears only when the sidebar is narrow.
  • Add a keyboard shortcut using a JS event listener that programmatically toggles the checkbox, mirroring VS Code's sidebar toggle (⌘ + ).

Watch out for

  • transition: width triggers layout reflow on every frame — for complex sidebar contents consider transform: scaleX() with a counter-scale on children.
  • Text with white-space: nowrap inside a width-transitioning container extends beyond the edge unless overflow: hidden is set — verify the clip works in both directions.
  • The collapse button at position: absolute; right: -14px can be clipped by the parent's overflow: hidden — use a separate overflow wrapper if needed.

Browser support

ChromeSafariFirefoxEdge
36+ 9+ 41+ 36+

Width transitions and opacity/max-width animations are supported in all modern browsers. IE10+ with vendor prefixes.

Search CodeFronts

Loading…