Skip to content
All Taste components

Overlays

Side drawer

A panel that slides in from the edge to show or edit one record without leaving the list.

right side · header with status · details · sticky footer

"use client";
import { useEffect, useRef } from "react";
import { X } from "lucide-react";

/** A native <dialog> pinned to the right edge: focus is trapped and Escape closes it. */
export function Drawer({ open, onClose, title, subtitle, badge, children, footer }: {
  open: boolean;
  onClose: () => void;
  title: string;
  subtitle?: string;
  badge?: React.ReactNode;
  children: React.ReactNode;
  footer?: React.ReactNode;
}) {
  const ref = useRef<HTMLDialogElement>(null);

  useEffect(() => {
    const d = ref.current;
    if (!d) return;
    if (open && !d.open) d.showModal();
    if (!open && d.open) d.close();
  }, [open]);

  return (
    <dialog
      ref={ref}
      onClose={onClose}
      onClick={(e) => { if (e.target === ref.current) onClose(); }}
      aria-labelledby="drawer-title"
      className="fixed inset-y-0 left-auto right-0 m-0 h-dvh max-h-none w-[min(380px,90vw)] border-l border-border bg-bg p-0 text-fg shadow-2xl backdrop:bg-neutral-950/45 open:flex open:flex-col"
    >
      <header className="relative grid gap-1 border-b border-border py-4 pl-5 pr-[52px]">
        <div className="flex flex-wrap items-center gap-2">
          <h2 id="drawer-title" className="text-base font-semibold tracking-tight">{title}</h2>
          {badge}
        </div>
        {subtitle && <p className="text-[13.5px] text-fg-muted">{subtitle}</p>}
        <button type="button" onClick={onClose} aria-label="Close"
          className="absolute right-3 top-3 grid size-8 place-items-center rounded text-fg-subtle hover:bg-surface-2 hover:text-fg">
          <X className="size-4" aria-hidden />
        </button>
      </header>
      <div className="grid flex-1 content-start gap-3.5 overflow-auto p-5">{children}</div>
      {footer && <footer className="flex flex-wrap justify-end gap-2 border-t border-border bg-surface px-5 py-3">{footer}</footer>}
    </dialog>
  );
}

The code uses the Taste Tailwind preset. Make yours on the Taste page with Export my system.