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.