Push Side Drawer

The navigation drawer slides in from the left and pushes the page aside, which shrinks and dims a little to show it is in the background.

  • CSS
  • Layout
  • Toggle
  • Added

AI prompt

Create a push-style side drawer in React and CSS. The drawer (180px wide) starts at translateX(-100%) on the left; the page content sits at rest. When open, slide the drawer to translateX(0) and move the page to translateX(140px) scale(0.92) with a rounded corner and reduced brightness, both over 420ms with cubic-bezier(0.16, 1, 0.3, 1), so the page reads as pushed back. Stagger the drawer links in with opacity and translateX(-12px) → 0 using transition-delay of 60ms × index. Clicking the dimmed page closes the drawer, as does Escape. The menu button carries aria-expanded and aria-controls, and the drawer is inert while closed. Under prefers-reduced-motion, show and hide the drawer without moving the page. The links read Overview, Projects, Team, and Settings.

Source

Demo.tsx
'use client'

import { useId, useState } from 'react'
import s from './demo.module.css'

export default function SideDrawer({ labels }: { labels: Record<string, string> }) {
  const [open, setOpen] = useState(false)
  const id = useId()

  return (
    <div className={s.shell} data-open={open || undefined} onKeyDown={(e) => e.key === 'Escape' && setOpen(false)}>
      <nav id={id} className={s.drawer} inert={!open}>
        {[labels.a, labels.b, labels.c, labels.d].map((t, i) => (
          <a key={t} href="#" className={s.link} style={{ '--i': i } as React.CSSProperties} onClick={(e) => e.preventDefault()}>
            {t}
          </a>
        ))}
      </nav>
      <div className={s.page} onClick={() => open && setOpen(false)}>
        <div className={s.bar}>
          <button
            type="button"
            className={s.menu}
            aria-expanded={open}
            aria-controls={id}
            onClick={(e) => {
              e.stopPropagation()
              setOpen(!open)
            }}
          >
            ☰ {labels.menu}
          </button>
          <strong className={s.title}>{labels.title}</strong>
        </div>
        <div className={s.grid}>
          <span />
          <span />
          <span />
          <span />
        </div>
      </div>
    </div>
  )
}
demo.module.css
.shell {
  position: absolute;
  inset: 0;
  overflow: hidden;
  background: var(--stage-bg);
}

.drawer {
  position: absolute;
  inset: 0 auto 0 0;
  display: grid;
  align-content: start;
  gap: 4px;
  width: 180px;
  padding: 56px 16px 16px;
  transform: translateX(-100%);
  transition: transform 420ms var(--ease-out);
}

.link {
  padding: 8px 10px;
  border-radius: 8px;
  color: var(--stage-ink);
  font: 500 var(--text-sm) / 1 var(--font-body);
  text-decoration: none;
  opacity: 0;
  transform: translateX(-12px);
  transition: opacity 260ms, transform 260ms var(--ease-out), background-color var(--dur-micro);
}

.link:hover { background: var(--stage-surface); }
.link:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 1px; }

.page {
  position: absolute;
  inset: 0;
  padding: 14px 16px;
  background: var(--stage-surface);
  transform-origin: 0 50%;
  transition: transform 420ms var(--ease-out), border-radius 420ms var(--ease-out), filter 420ms;
}

.shell[data-open] .drawer { transform: none; }
.shell[data-open] .link { opacity: 1; transform: none; transition-delay: calc(120ms + var(--i) * 60ms); }

.shell[data-open] .page {
  transform: translateX(140px) scale(0.92);
  border-radius: 16px;
  filter: brightness(0.7);
  cursor: pointer;
}

.bar {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 16px;
}

.menu {
  height: 32px;
  padding: 0 10px;
  border: 1px solid var(--stage-rule);
  border-radius: 8px;
  background: var(--stage-bg);
  color: var(--stage-ink);
  font: 500 var(--text-sm) / 1 var(--font-body);
  cursor: pointer;
}

.menu:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }

.title { font: 600 var(--text-base) / 1 var(--font-display); color: var(--stage-ink); }

.grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}

.grid span { height: 64px; border-radius: 10px; background: var(--stage-bg); }

@media (prefers-reduced-motion: reduce) {
  .drawer, .link, .page { transition: none; }
  .shell[data-open] .page { transform: none; filter: brightness(0.5); }
}

Suggest an idea

Seen a motion or a theme you would like to find here? Describe it, and it opens as a prefilled GitHub issue for you to post.

What are you suggesting?

Needs a GitHub account. Suggestions are public.