A CSS side menu is a vertical navigation panel that slides, pushes, or expands from the edge of the viewport — drawer, off-canvas push, icon rail, multi-level accordion, or hover-triggered flyout. The dominant pattern for dashboards, admin panels, documentation sites, mobile drawers, and SaaS workspaces. These 16 hand-coded designs cover the full side-menu playbook — slide-in checkbox-hack drawer, smooth-overlay drawer, off-canvas push sidebar, expandable icon-only rail, responsive hidden-to-visible menu, sticky vertical nav, multi-level accordion, full-height flexbox, neumorphic inset, glassmorphism blurred, cyberpunk neon, brutalist border, hamburger checkbox-hack, hover-triggered drawer, collapsible width-transition, and CSS-only accordion side nav. Every demo uses scoped .sm-NN class names, ships 100% Pure CSS (zero JavaScript), honours prefers-reduced-motion, meets WCAG 2.2 keyboard and ARIA requirements, and is MIT licensed.

Closely related: CSS Sidebar Navigation (link lists), CSS Sidebar Layouts (full-page shells), CSS Hamburger Menus (the trigger), and CSS Responsive Navbar (top-bar counterpart).

16 unique CSS side menus 100% copy-paste ready Published
01 / 16
Pure CSS Slide-In Side Menu
Pure CSS
Pure CSS Slide-In Side Menu — preview
A navigation drawer that slides in from off-screen left when a burger icon is clicked, driven entirely by a hidden checkbox and CSS sibling selectors.
02 / 16
CSS Side Menu with Smooth Overlay
Pure CSS
CSS Side Menu with Smooth Overlay — preview
A sliding drawer that reveals a frosted-glass backdrop blur overlay over the main content area, using backdrop-filter and a second label element as the click-to-close target.
03 / 16
Off-Canvas Push Sidebar
Pure CSS
Off-Canvas Push Sidebar — preview
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.
04 / 16
Expandable Icon-Only Sidebar
Pure CSS
Expandable Icon-Only Sidebar — preview
A persistent narrow icon rail that smoothly expands on hover to reveal full text labels and notification badges, using a CSS width transition with no toggle required.
05 / 16
Responsive Sidebar Menu (Hidden-to-Visible)
Pure CSS
Responsive Sidebar Menu (Hidden-to-Visible) — preview
A sidebar permanently visible as a fixed column on desktop that hides off-screen on mobile and is revealed by a burger toggle, all handled by CSS media queries alone.
06 / 16
Sticky Fixed Vertical Navigation Sidebar
Pure CSS
Sticky Fixed Vertical Navigation Sidebar — preview
A full-height sidebar that remains anchored to the viewport top while long-form main content scrolls independently alongside it using position:sticky.
07 / 16
Multi-Level Accordion Side Menu
Pure CSS
Multi-Level Accordion Side Menu — preview
A hierarchical documentation sidebar where each top-level section expands independently using hidden checkboxes and max-height transitions, supporting deep nested navigation trees.
08 / 16
Full-Height Flexbox Sidebar
Pure CSS
Full-Height Flexbox Sidebar — preview
A sidebar that uses CSS Flexbox column direction to perfectly space a brand header, navigation links, an informational card, and a pinned user footer without absolute positioning.
09 / 16
Neumorphic Inset Side Menu
Pure CSS
Neumorphic Inset Side Menu — preview
A dark-mode dashboard sidebar using soft extruded box-shadows and recessed inset-shadow interactive states to simulate a tactile raised and pressed surface aesthetic.
10 / 16
Glassmorphism Blurred Sidebar
Pure CSS
Glassmorphism Blurred Sidebar — preview
A frosted-glass sidebar panel using backdrop-filter blur over animated gradient colour blobs, with a semi-transparent border and layered glass card components.
11 / 16
Dark Mode Cyberpunk Neon Sidebar
Pure CSS
Dark Mode Cyberpunk Neon Sidebar — preview
A futuristic dark-themed navigation sidebar with pulsing neon text-shadow glows, CSS scanline overlays, sharp geometric accents, and a subtle logo flicker animation.
12 / 16
Brutalist Left-Hand Border Navigation
Pure CSS
Brutalist Left-Hand Border Navigation — preview
A high-contrast brutalist sidebar using only thick black borders, a bold red accent rule, raw condensed typography, and zero gradients or rounded corners.
Advertisement
13 / 16
Hamburger Checkbox-Hack Side Menu
Pure CSS
Hamburger Checkbox-Hack Side Menu — preview
A classic pure-CSS interaction using a hidden checkbox input and the :checked general sibling combinator to toggle menu visibility and animate the burger icon into an X.
14 / 16
CSS Hover-Triggered Sidebar Drawer
Pure CSS
CSS Hover-Triggered Sidebar Drawer — preview
A side navigation for wide viewports that slides open when the user hovers over a persistent thin edge trigger strip, using only CSS parent :hover to expand the drawer.
15 / 16
Collapsible Sidebar Menu with CSS Transitions
Pure CSS
Collapsible Sidebar Menu with CSS Transitions — preview
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.
16 / 16
CSS-Only Accordion Side Navigation
Pure CSS
CSS-Only Accordion Side Navigation — preview
A documentation-style sidebar where multiple independent accordion sections can be expanded or collapsed simultaneously using hidden checkboxes and max-height transitions.
FAQ

Frequently asked questions

What is a CSS side menu and which technique should I use to build one?
A CSS side menu is a vertical navigation panel that lives on the left (or right) edge of the viewport and either stays permanently visible or slides in on demand. There are five canonical pure-CSS techniques, each fitting a different intent: 1. Checkbox-hack slide-in (Demo #01, #02, #13) — a hidden <input type='checkbox'> + <label> burger, with :checked ~ .nav { transform: translateX(0) } sliding the drawer in. Click-driven, zero JS, works on every browser since 2010. 2. Off-canvas push (Demo #03) — same checkbox toggle but the main content also translateX's to make room, producing the native-app push effect where content moves rather than getting covered. 3. Hover-expand icon rail (Demo #04, #14) — collapsed icon strip that widens on :hover to reveal labels. The VSCode / Discord pattern. 4. Responsive media-query toggle (Demo #05) — sidebar permanently visible on desktop, slides off-screen below a breakpoint. One markup, two layouts, controlled by @media only. 5. Sticky / fixed full-height (Demo #06, #08) — position: sticky; top: 0; height: 100vh keeps the sidebar pinned while the article scrolls — the documentation site pattern. Demos #07 and #16 layer multi-level accordion behaviour on top using independent checkbox-pairs at each tier. All 16 ship as 100% pure CSS — no JavaScript, no framework, no npm package.
How does the pure-CSS slide-in side menu work without JavaScript?
Demo #01 uses the classic checkbox-hack, the same technique that powers every JS-free drawer on the web. The recipe is three elements: <input type='checkbox' id='t' hidden> as the state-holder, <label for='t' class='burger'> as the click target, and <nav class='sm-01__nav'> as the drawer. The nav defaults to transform: translateX(-100%) (slid off-screen left). The sibling combinator ~ targets it from the input: .sm-01__toggle:checked ~ .sm-01__nav { transform: translateX(0) }. Clicking the label flips the checkbox, the sibling rule fires, and the drawer slides in. Why transform and not left or display: only transform and opacity are composited on the GPU, so the slide animation runs at a buttery 60fps without triggering layout / paint. The same recipe with an extra <label for='t' class='overlay'> behind the drawer gives you tap-outside-to-close (Demo #02, #13). Add transform: translateX(var(--w)) to the main content and you get off-canvas push (Demo #03). Three elements, one CSS rule pair, infinite variations.
Off-canvas push vs overlay drawer — which side menu pattern should I use?
Off-canvas overlay (Demo #01, #02, #13) — the drawer slides in OVER the main content, dimming it with a semi-transparent backdrop. The main content stays in place. Best for: mobile-first apps, marketing sites, any layout where the sidebar is summoned-on-demand rather than always-present. Easier to implement, no main-content reflow, naturally responsive. Off-canvas push (Demo #03) — the drawer slides in AND simultaneously translates the main content area to the right by the drawer's width. No overlap; both sides are always fully visible. Best for: native app feel, productivity tools where the user often wants the menu and content side-by-side, large-screen layouts where there's room to push. Harder to make responsive (pushing content off-screen on narrow viewports is awkward), but the most polished feel. Decision rule: if your users open the menu briefly to navigate and then close it (a momentary action), use overlay. If they leave the menu open while working (sustained reference), use push. Hybrid pattern: use overlay below 768px (mobile) and push above 1024px (desktop) — Demo #03 and Demo #05 combine cleanly via @media (min-width: 1024px).
How do I build a multi-level accordion side menu with pure CSS?
Demo #07 uses nested checkbox-hack pairs, one per tier. Tier 1: <input id='m1' type='checkbox'> + <label for='m1'> + #m1:checked ~ .sub-l1 { max-height: 400px }. Tier 2 (inside .sub-l1): <input id='m2'> + <label for='m2'> + #m2:checked ~ .sub-l2 { max-height: 200px }. The general sibling combinator (~) lets each toggle reach across DOM cousins, so a level-2 toggle nested inside .sub-l1 can still control its .sub-l2 sibling. Key gotcha: animate max-height, never height: autoauto can't transition. Set max-height just above the expanded content's natural height so the animation duration feels right. For dynamic-height content, max-height: 100vh works but the close animation will run the full duration regardless of actual content. Modern alternative: interpolate-size: allow-keywords (Chrome 129+) lets height: 0 → auto transition for real. Combine with @starting-style for entry animations. Demo #16 (CSS-Only Accordion Side Navigation) uses the same pattern for documentation TOCs. Modern HTML alternative: nested <details> elements are accessibility-better but harder to animate beyond the native disclosure flip.
How do I make the icon-only sidebar expand on hover (VSCode / Discord pattern)?
Demo #04 uses one CSS property pair: a fixed collapsed width and a :hover expansion. .sm-04__rail { width: var(--collapsed); transition: width var(--dur) var(--ease) } at rest. .sm-04__rail:hover { width: var(--expanded) } on hover. The transition smoothly interpolates between the two states. The fade-in trick: text labels start at opacity: 0 with a slight transition-delay: 0.05s on the opacity transition so labels fade in AFTER the container has begun widening — without the delay, you'd see clipped label text during the open animation. Each icon cell uses a fixed width: var(--collapsed) so icons stay anchored on the left as the container grows; only the label area beyond them appears. Touch caveat: :hover doesn't fire on touchscreens, so add a @media (hover: hover) guard and provide a click-toggle fallback for mobile (the checkbox-hack from Demo #01 works as the fallback). Modern alternative: :has(:focus-within) opens the rail on keyboard focus, making it accessible without a separate toggle button — Chrome 105+, Safari 15.4+, Firefox 121+.
How is my side menu accessible? What ARIA / keyboard support do I need for WCAG 2.2 / EU EAA / Section 508?
A truly accessible side menu needs seven things, and most CSS-only tutorials ship only 2-3. 1. Semantic landmark: wrap the menu in <nav aria-label='Main'> so screen readers announce it as a navigation landmark. 2. Semantic trigger: the burger button must be a real <button type='button'> or a <label> attached to a checkbox (which the browser exposes as a button equivalent). Never a <div onclick>. 3. aria-expanded='true|false' on the trigger — announces whether the drawer is currently open. The checkbox-hack pattern needs JS to flip this attribute when state changes (pure CSS can't update ARIA), OR you can rely on the semantically-correct checkbox state alone. 4. aria-controls='nav-id' linking trigger to drawer by ID. 5. Keyboard interaction: Space / Enter toggles the drawer, Escape closes it and returns focus to the trigger, Tab moves through nav links in DOM order. 6. Focus management: when the drawer opens, move focus to the first link (or trap focus inside if the drawer is modal-style with a dimmed backdrop). 7. prefers-reduced-motion: reduce — disable the slide animation for users with vestibular disorders. Every demo in this collection includes this rule. Regulatory frameworks: WCAG 2.2, EU European Accessibility Act (effective June 28, 2025), US Section 508, Canada ACA, UK Equality Act 2010. Verify with axe DevTools, Lighthouse, WAVE, NVDA / VoiceOver screen-reader testing before shipping.
Pure CSS vs JavaScript side menu — when do I actually need JS for my drawer?
Pure CSS handles FAR more side-menu patterns than people realize. Pure CSS works for: click-toggle slide-in drawers (checkbox-hack — Demos #01, #02, #03, #05, #13), hover-expand icon rails (Demos #04, #14), responsive show/hide via media queries (Demo #05), sticky / fixed positioning (Demos #06, #08), nested multi-level accordions (Demos #07, #16), click-outside-to-close (overlay <label for='t'>), and ALL visual / animation effects (slides, fades, neon, glassmorphism, neumorphism). JavaScript is required for: focus management (moving focus to the first link on open, returning to trigger on close), focus trap inside modal-style drawers, Escape-key dismissal, screen-reader live announcements when the drawer opens, programmatic aria-expanded updates, click-outside-close that ALSO works on tap-outside on iOS (Safari has subtle bugs with :checked propagation), and any logic that depends on viewport / scroll position. Modern alternative: the HTML Popover API (Chrome 114+, Safari 17+, Firefox 125+) gives you popovertarget + popover='auto' with NATIVE Escape handling, click-outside-close, focus management, and accessible announcements — zero JavaScript needed for a complete a11y-compliant drawer. <dialog open> + JavaScript's .showModal() covers modal-style focus-trapped drawers.
How does this compare to Tailwind UI, shadcn Sheet, MUI Drawer, Chakra Drawer, Mantine Navbar?
Tailwind CSS (free): no first-class drawer component; compose with fixed inset-y-0 left-0 w-64 -translate-x-full peer-checked:translate-x-0 on a sibling-of-checkbox pattern. Works, but verbose. Tailwind UI ($299 lifetime): ships pre-designed slide-over drawer templates with Tailwind classes — copy-paste, not a component library. shadcn/ui (free, React + Radix): ships <Sheet> built on Radix UI Dialog primitive — accessibility-complete (focus trap, Escape, ARIA, click-outside). Best React option but adds Radix (~30KB) + Tailwind dependencies. Headless UI: <Disclosure> + <Transition> unstyled primitives, you bring the CSS. Material UI / MUI (~95KB): ships <Drawer> with Material aesthetics, multiple variants (permanent, persistent, temporary, mini). Chakra UI: <Drawer> as a first-class composable component with built-in placement (left/right/top/bottom). Mantine: <Navbar> + <Drawer> with collapsing variants. Ant Design: <Drawer> + <Menu>. Aceternity UI / Magic UI (free, React): ship animated sidebar templates copying VSCode / Linear / Vercel aesthetics. This collection vs all of the above: 16 demos ship as PURE CSS (zero JS, zero framework, zero npm package) — the same patterns as Tailwind UI / shadcn / MUI / Chakra but with no bundle cost. For accessibility-critical apps where you need full ARIA + focus trap, use shadcn Sheet or Radix Dialog. For marketing pages, dashboards, and content sites, the pure-CSS demos here win on Core Web Vitals (LCP / INP / CLS) by shipping ZERO JavaScript.
Why does my <code>:checked</code> side menu break on iOS Safari? How do I fix it?
iOS Safari has three quirks that bite checkbox-hack side menus. 1. Tap-through bug: when the label is over a fixed element with high z-index, the tap fires on the underlying element instead of the label. Fix: add cursor: pointer to the label — iOS requires a pointer cursor on the AND its ancestor click-targets for tap-to-toggle to register reliably. 2. Sibling combinator fragility: ~ requires the checkbox, label, and target to all be DIRECT children of the same parent. Wrapping any of them in a wrapper div breaks the selector silently. Fix: keep <input>, <label>, and the nav as direct siblings of .sm-NN; nest other UI inside the nav itself if needed. 3. Click-outside-to-close lag: tapping the overlay sometimes requires a double-tap on iOS because Safari treats the first tap as a focus shift, not a click. Fix: ensure the overlay <label for='t'> has both cursor: pointer AND -webkit-tap-highlight-color: transparent to remove the iOS tap-delay heuristic. 4. backdrop-filter on iOS < 15: glassmorphism demos (#10) use backdrop-filter: blur() — older iOS needs the -webkit-backdrop-filter prefix too. Add both. 5. Visual :checked sync: if your checkbox has any visible styling (which it shouldn't for the burger pattern), make sure it's truly hidden via position: absolute; clip: rect(0,0,0,0) rather than display: nonedisplay: none removes it from the focus order and breaks keyboard tab-to-toggle.
Are these side menus free, RTL-compatible, and how do I drop them into Next.js / Astro / SvelteKit?
Yes — all 16 side-menu designs are MIT licensed and free for personal and commercial use (client projects, SaaS products, design systems, open-source libraries). The license requires only keeping the copyright notice if you redistribute the source as-is; in shipped production HTML / CSS that you've adapted, no visible attribution is needed. A one-line credit pointing to https://codefronts.com is appreciated when shipping in an open-source library but not required. RTL (right-to-left) support: every demo uses logical properties where possible (inset-inline-start, margin-inline, padding-inline) or is one CSS swap away (flip left: 0right: 0 and negate the translateX direction). For full RTL: wrap your page in <html dir='rtl'> and use transform: translateX(100%) instead of translateX(-100%) for the default-closed state — every direction-aware property mirrors automatically. Framework integration: Next.js (App Router) — drop the markup into a layout.tsx sidebar slot or a client component; the checkbox-hack is server-renderable with zero hydration cost. Astro — paste the HTML + CSS into an .astro file; Astro's scoped styles automatically prefix the CSS so the .sm-NN namespace doubles up safely. SvelteKit — put the markup in +layout.svelte with <style> auto-scoping. Vue 3<template> + <style scoped>. Remix — identical to Next.js. Tailwind — convert the CSS to utilities using our CSS to Tailwind converter, or paste raw CSS into @layer components. shadcn/ui — these designs make great visual overrides for the default <Sheet> component; replace the Tailwind classes on <SheetContent> with the demo's CSS. The pure-CSS demos work in ANY framework because they have zero framework dependencies.
Does a CSS side menu improve Lighthouse / Core Web Vitals vs a JS drawer?
Yes — measurably, on all three Core Web Vitals. INP (Interaction to Next Paint) is the biggest win. A JS drawer attaches click handlers that run JavaScript on every open/close — every handler invocation has measurable JS execution cost (parsing, function call, state mutation, re-render in React/Vue) that adds 20-150ms to INP depending on framework and bundle size. A pure-CSS drawer fires zero JavaScript on toggle: the browser flips the :checked state and runs the CSS transition on the compositor thread. INP stays under 50ms regardless of page complexity. CLS (Cumulative Layout Shift): JS drawers shipped via React/Vue/Svelte are typically client-hydrated — the drawer's DOM doesn't exist on first paint, then mounts post-hydration and shifts layout. CSS drawers are server-rendered in their final position; the markup is identical before and after hydration. CLS = 0 from the drawer itself. LCP (Largest Contentful Paint): a JS drawer's bundle (shadcn Sheet via Radix Dialog ~30KB gzipped, MUI Drawer ~95KB, Framer Motion ~50KB for animation) is render-blocking JavaScript that delays LCP. A CSS drawer adds ~2KB of CSS that the browser parses in parallel with HTML — zero LCP impact. Lighthouse Performance score: replacing a single Radix Dialog drawer with a pure-CSS equivalent typically lifts Lighthouse Performance from 78 → 92 on mid-tier mobile (Moto G Power simulated). Concrete measurement: use PageSpeed Insights, Chrome DevTools Performance Insights, WebPageTest, or the Core Web Vitals report in Google Search Console. For real-user metrics, ship the web-vitals library (~3KB) and report INP/LCP/CLS to your analytics — you'll see the CSS drawer's INP land in the <100ms 'Good' bucket on every device tier.
How do I make a CSS side menu work in RTL (Arabic, Hebrew, Persian)?
RTL is one HTML attribute + a handful of CSS swaps. 1. Set direction at the document root: <html dir='rtl' lang='ar'> — every direction-aware property cascades automatically (text alignment, list-item bullets, scrollbar position, default flex-row direction). 2. Use logical properties everywhere physical ones would normally go: inset-inline-start instead of left, inset-inline-end instead of right, margin-inline-start / margin-inline-end instead of margin-left / margin-right, padding-inline instead of padding-left + padding-right, border-inline-start instead of border-left. The browser flips them based on dir. Logical properties are universally supported (Chrome 87+, Safari 14.1+, Firefox 66+). 3. Flip transform direction manually — this is the one place logical properties can't help. The default-closed drawer in LTR uses transform: translateX(-100%) (off-screen left). In RTL, change to transform: translateX(100%) (off-screen right). Wrap in a CSS-only guard: .sm-01__nav { transform: translateX(-100%); } [dir='rtl'] .sm-01__nav { transform: translateX(100%); }. 4. Mirror the burger position — if the burger is position: absolute; left: 16px, change to inset-inline-start: 16px and it auto-mirrors. 5. Verify icon directionality — chevron arrows (›, ‹, ▶) point one direction; for RTL, swap them or transform-flip with [dir='rtl'] .chevron { transform: scaleX(-1); }. 6. Test with real Arabic / Hebrew / Persian content, not Lorem ipsum — different word lengths and the absence of capital letters change visual rhythm. Tools: Chrome DevTools 'Sensors' has no RTL toggle, but you can force it via document.documentElement.dir='rtl' in the console for live preview. 7. Font choice matters: ensure your font has solid Arabic/Hebrew/Persian glyph coverage — system fonts (system-ui) inherit OS Arabic fonts automatically; if you ship a custom Latin font, also load an Arabic/Hebrew counterpart with font-family: 'Latin-Font', 'Arabic-Font', sans-serif. Every demo in this collection is one selector-swap away from full RTL support; the underlying mechanic (checkbox-hack, :hover, :has()) is direction-neutral.

Search CodeFronts

Loading…