Drawer

Side panel that slides into view.

html

<div class="drawer-demo drawer-demo--backdrop">
  <div class="drawer drawer--right drawer--open" style="--size:320px;--accent:#BFD9F2;" role="dialog" aria-modal="true" aria-labelledby="drawer-title">
    <div class="drawer__header">
      <h3 class="drawer__title" id="drawer-title">Filters</h3>
      <button class="drawer__close" type="button" aria-label="Close">
        <svg width="14" height="14" viewBox="0 0 14 14" aria-hidden="true"><path d="M1 1l12 12M13 1L1 13" stroke="currentColor" stroke-width="1.6" fill="none" stroke-linecap="round"/></svg>
      </button>
    </div>
    <div class="drawer__body">
      <p>Panel content lives here, for filters, forms, or navigation that do not need a full page.</p>
    </div>
  </div>
</div>
<style>
.drawer-demo { position: relative; min-height: 360px; border-radius: 12px; overflow: hidden; background: #FAF7F2; }
.drawer-demo--backdrop { background: rgba(36,20,48,.45); }
.drawer { position: absolute; box-sizing: border-box; background: #FAF7F2; color: #241430; padding: 20px; box-shadow: 0 20px 44px rgba(36,20,48,.28); font: 16px/1.5 system-ui, sans-serif; }
@media (prefers-reduced-motion: no-preference) {
.drawer { transition: transform .2s ease; }
}
.drawer--left { left: 0; top: 0; bottom: 0; width: var(--size); border-right: 4px solid var(--accent); transform: translateX(-100%); }
.drawer--left.drawer--open { transform: translateX(0); }
.drawer--right { right: 0; top: 0; bottom: 0; width: var(--size); border-left: 4px solid var(--accent); transform: translateX(100%); }
.drawer--right.drawer--open { transform: translateX(0); }
.drawer--top { top: 0; left: 0; right: 0; height: var(--size); border-bottom: 4px solid var(--accent); transform: translateY(-100%); }
.drawer--top.drawer--open { transform: translateY(0); }
.drawer--bottom { bottom: 0; left: 0; right: 0; height: var(--size); border-top: 4px solid var(--accent); transform: translateY(100%); }
.drawer--bottom.drawer--open { transform: translateY(0); }
.drawer__header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.drawer__title { margin: 0; font-size: 17px; font-weight: 600; }
.drawer__close { background: none; border: none; cursor: pointer; color: #241430; padding: 4px; border-radius: 6px; }
.drawer__close:hover, .drawer__close:focus-visible { background: var(--accent); outline: none; }
.drawer__body p { margin: 0; font-size: 14px; color: #241430cc; }
</style>

tailwind

<div class="relative min-h-[360px] rounded-xl overflow-hidden bg-[#FAF7F2] drawer-tw-demo--backdrop">
  <div role="dialog" aria-modal="true" aria-labelledby="drawer-tw-title" class="drawer-tw drawer-tw--right drawer-tw--open absolute box-border bg-[#FAF7F2] text-[#241430] p-5 shadow-2xl font-sans" style="--size:320px;--accent:#BFD9F2;">
    <div class="flex items-center justify-between mb-2.5">
      <h3 id="drawer-tw-title" class="m-0 text-[17px] font-semibold">Filters</h3>
      <button type="button" aria-label="Close" class="bg-transparent border-none cursor-pointer text-[#241430] p-1 rounded-md hover:bg-[var(--accent)] focus-visible:bg-[var(--accent)] focus-visible:outline-none">
        <svg width="14" height="14" viewBox="0 0 14 14" aria-hidden="true"><path d="M1 1l12 12M13 1L1 13" stroke="currentColor" stroke-width="1.6" fill="none" stroke-linecap="round"/></svg>
      </button>
    </div>
    <div class="text-sm text-[#241430]/80">
      <p class="m-0">Panel content lives here, for filters, forms, or navigation that do not need a full page.</p>
    </div>
  </div>
</div>
<style>
.drawer-tw-demo--backdrop { background: rgba(36,20,48,.45); }
.drawer-tw--left { left: 0; top: 0; bottom: 0; width: var(--size); border-right: 4px solid var(--accent); transform: translateX(-100%); }
@media (prefers-reduced-motion: no-preference) {
.drawer-tw--left { transition: transform .2s ease; }
}
.drawer-tw--left.drawer-tw--open { transform: translateX(0); }
.drawer-tw--right { right: 0; top: 0; bottom: 0; width: var(--size); border-left: 4px solid var(--accent); transform: translateX(100%); }
@media (prefers-reduced-motion: no-preference) {
.drawer-tw--right { transition: transform .2s ease; }
}
.drawer-tw--right.drawer-tw--open { transform: translateX(0); }
.drawer-tw--top { top: 0; left: 0; right: 0; height: var(--size); border-bottom: 4px solid var(--accent); transform: translateY(-100%); }
@media (prefers-reduced-motion: no-preference) {
.drawer-tw--top { transition: transform .2s ease; }
}
.drawer-tw--top.drawer-tw--open { transform: translateY(0); }
.drawer-tw--bottom { bottom: 0; left: 0; right: 0; height: var(--size); border-top: 4px solid var(--accent); transform: translateY(100%); }
@media (prefers-reduced-motion: no-preference) {
.drawer-tw--bottom { transition: transform .2s ease; }
}
.drawer-tw--bottom.drawer-tw--open { transform: translateY(0); }
</style>

react

import { useState } from "react";

export function Drawer({
  title = "Filters",
  side = "right",
  width = 320,
  accent = "#BFD9F2",
  backdrop = true,
  open: initialOpen = true,
}) {
  const [open, setOpen] = useState(initialOpen);
  const positions = {
    left: { left: 0, top: 0, bottom: 0, width, borderRight: `4px solid ${accent}`, transform: open ? "translateX(0)" : "translateX(-100%)" },
    right: { right: 0, top: 0, bottom: 0, width, borderLeft: `4px solid ${accent}`, transform: open ? "translateX(0)" : "translateX(100%)" },
    top: { top: 0, left: 0, right: 0, height: width, borderBottom: `4px solid ${accent}`, transform: open ? "translateY(0)" : "translateY(-100%)" },
    bottom: { bottom: 0, left: 0, right: 0, height: width, borderTop: `4px solid ${accent}`, transform: open ? "translateY(0)" : "translateY(100%)" },
  };
  return (
    <div style={{ position: "relative", minHeight: 360, borderRadius: 12, overflow: "hidden",
      background: backdrop ? "rgba(36,20,48,.45)" : "#FAF7F2" }}>
      {!open && (
        <button type="button" onClick={() => setOpen(true)} style={{ position: "absolute", top: 16, left: 16,
          padding: "8px 14px", borderRadius: 8, border: "1.5px solid #24143033", background: "#FAF7F2", cursor: "pointer" }}>
          Open drawer
        </button>
      )}
      <div role="dialog" aria-modal={backdrop ? "true" : "false"} aria-labelledby="drawer-title" className="drawer-panel" style={{
        position: "absolute", boxSizing: "border-box", background: "#FAF7F2", color: "#241430", padding: 20,
        boxShadow: "0 20px 44px rgba(36,20,48,.28)", fontFamily: "system-ui, sans-serif",
        ...positions[side] }}>
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 10 }}>
          <h3 id="drawer-title" style={{ margin: 0, fontSize: 17, fontWeight: 600 }}>{title}</h3>
          <button type="button" onClick={() => setOpen(false)} aria-label="Close" style={{
            background: "none", border: "none", cursor: "pointer", color: "#241430", padding: 4, borderRadius: 6 }}>
            <svg width="14" height="14" viewBox="0 0 14 14" aria-hidden="true"><path d="M1 1l12 12M13 1L1 13" stroke="currentColor" strokeWidth="1.6" fill="none" strokeLinecap="round"/></svg>
          </button>
        </div>
        <p style={{ margin: 0, fontSize: 14, color: "#241430cc" }}>Panel content lives here, for filters, forms, or navigation that do not need a full page.</p>
      </div>
      <style>{`
        @media (prefers-reduced-motion: no-preference) {
          .drawer-panel { transition: transform .2s ease; }
        }
      `}</style>
    </div>
  );
}