OverlaysSheet
Sheet
A non-modal panel that slides in from the right or bottom. Used for the search palette, the mobile nav, and the case study figure lightbox.
Demo
The default is a 32 rem wide sheet that slides in from the right. On mobile, sheets that aren't "right" become "bottom" by default.
Right
Search docs
Sizes
Sizes
sm — 24 rem
md — 32 rem (default)
lg — 48 rem
full — 100% of the side
Anatomy
- Slide-in: 250 ms ease-out (right) or 250 ms ease-out (bottom, + 12 px rise)
- Surface:
--surface-elevatedwithelevation-3shadow - Padding: 20 px
- Title row: 0.875 rem weight 500, with a close button on the right
- Backdrop: 30% ink at 30% opacity; click closes the sheet
- Body scroll on the page is NOT locked (sheets are non-modal); focus is trapped within the sheet while open
Implementation
sheet.tsx
"use client"
import { useEffect, type ReactNode } from "react"
import { X } from "lucide-react"
import SiteIcon from "@/components/site-icon"
interface SheetProps {
open: boolean
onClose: () => void
side?: "right" | "bottom"
title: string
size?: "sm" | "md" | "lg" | "full"
children: ReactNode
}
export function Sheet({ open, onClose, side = "right", title, size = "md", children }: SheetProps) {
useEffect(() => {
if (!open) return
function onKey(e: KeyboardEvent) { if (e.key === "Escape") onClose() }
window.addEventListener("keydown", onKey)
return () => window.removeEventListener("keydown", onKey)
}, [open, onClose])
if (!open) return null
return (
<div className="sheet__backdrop" onClick={onClose}>
<aside
className={`sheet sheet--${side} sheet--${size}`}
role="dialog"
aria-modal="true"
aria-label={title}
onClick={(e) => e.stopPropagation()}
>
<div className="sheet__head">
<p className="sheet__title">{title}</p>
<button onClick={onClose} aria-label="Close">
<SiteIcon icon={X} size="sm" tone="muted" />
</button>
</div>
<div className="sheet__body">{children}</div>
</aside>
</div>
)
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | None | Required: controlled open state |
onClose | () => void | None | Required: close handler |
side | "right" | "bottom" | "right" | Slide-in direction |
title | string | None | Required: sheet title |
size | "sm" | "md" | "lg" | "full" | "md" | Width (right) or height (bottom) |