Sheet
A panel that slides in from a screen edge for a secondary task or navigation.
@garn/ui/sheetDefault
Trigger → right-side edit panel with footer actions.
Sides
Open from any edge — top, right, bottom, or left.
Installation
garn is copy-in — the garn CLI writes this component's source into your project, so you own and can edit it. It resolves any base files and installs the npm dependencies below for you.
npx garn-ui add sheet- Registry
sheet- Source
- sheet.tsx
- Deps
@radix-ui/react-dialogclass-variance-authoritylucide-react- Registry deps
utils
Import
import { Sheet } from "@garn/ui/sheet";Props
Generated from the component's TypeScript types.
side- Description
- No description.
- Type
"bottom" | "left" | "right" | "top"- Default
right
Plus 5 inherited native HTML attributes.
Styling
Target these data-slots and remap these tokens to restyle without forking the component.
[data-slot="sheet-content"]| Property | Token | Tier |
|---|---|---|
padding | --garn-pad-surface | semantic · air |
gap | --garn-gap-stack | semantic · air |
headerGap | --garn-gap-field | semantic · air |
closeSize | --garn-control-h-xs | semantic · air |
motion | --garn-motion-base | semantic |
Overview
Sheet slides a panel in from a screen edge for a secondary task or navigation that shouldn't replace the page — a filter panel, a detail/edit form, a mobile nav. SheetContent carries the side variant (top/right/bottom/left), portals its overlay, and ships a built-in close X; SheetTitle (required) names it, with optional Description/Header/Footer. Use it for longer or side-anchored tasks than a Dialog suits; on touch/mobile a bottom Drawer with a drag handle often feels more native.
Guidelines
Reach for a Sheet when a Dialog would feel cramped. It's the right home for a multi-field edit form, a filter set, or a side nav — content that benefits from height and an edge anchor. Pick the side that matches intent: right for detail/edit, left for nav, bottom for mobile actions.
Always render a SheetTitle. It's the panel's accessible name and the contract is enforced; hide it visually (sr-only) if the design has no heading, but keep it. Pin primary actions in SheetFooter so they stay reachable as the body scrolls.
Keep the modal contract honest. Focus traps inside while open and returns to the trigger on close; Escape and the close X both dismiss. Don't stack sheets and dialogs — resolve one surface before opening another.
Best practices
- Always include a SheetTitle (sr-only if you don't want it visible).
- Pick
sideto fit the mental model — right for details/edit, left for nav, bottom for mobile. - Keep it dismissible: Escape, the close button, and overlay click all close it.
- Don't put a destructive forced choice in a sheet — use alert-dialog.
- Don't omit the SheetTitle.
- Don't nest sheets or stack a sheet over a dialog.
Content guidelines
- Give the SheetTitle a clear, specific name for the panel (“Edit profile”, “Filters”).
- Use the description for a one-line summary of what the panel is for.
- Label footer actions by outcome (“Save changes” / “Cancel”) and keep the primary action last.
Accessibility
- Role
dialog- ARIA APG
- dialog (modal)
- Focus
- Focus trapped inside the panel; returns to the trigger on close.
Enforceable contracts
Every SheetContent must contain a SheetTitle (wrap it in sr-only to hide it visually).
when SheetContent has no SheetTitle
Keyboard
| Escape | Close the sheet. |
| TabShift+Tab | Cycle focus within the sheet (focus is trapped). |
- • aria-modal; labelled by SheetTitle and (if present) described by SheetDescription.
- • Focus moves into the panel on open and returns to the trigger on close.
- • A built-in close button (sr-only "Close") sits in the corner; Escape and overlay click also close.
- Status
- Stable
- Version
v0.3.0- Introduced
v0.1.0
garn doesn't yet record a per-release version history. This reflects the component's current lifecycle state; full changelog entries will appear once the metadata pipeline emits a lifecycle.history field.