Sheet
A panel that slides in from a screen edge for a secondary task or navigation.
@garn/ui/sheetOn this page
Default
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- Deps
@radix-ui/react-dialogclass-variance-authoritylucide-react- Registry deps
utils
Import
import { Sheet, SheetTrigger, SheetContent, SheetClose, SheetTitle, SheetDescription, SheetHeader, SheetFooter } from "@garn/ui/sheet";Anatomy
The parts this component is built from — compose them to assemble it.
SheetRequiredRoot — owns the open state. No DOM of its own.
SheetTrigger[data-slot="sheet-trigger"]Opens the sheet (asChild to wrap your own button).
SheetContentRequired[data-slot="sheet-content"]The edge panel; carries the side variant, portals its overlay, and includes a built-in close X.
SheetClose[data-slot="sheet-close"]Closes the sheet (e.g. a Cancel button).
SheetTitleRequired[data-slot="sheet-title"]Accessible name for the panel (required; sr-only if visually hidden).
SheetDescription[data-slot="sheet-description"]Supplementary description, wired to aria-describedby.
SheetHeader[data-slot="sheet-header"]Layout wrapper for title + description.
SheetFooter[data-slot="sheet-footer"]Layout wrapper for actions (right-aligned on ≥sm).
Props
The component's public props and their types.
side- Description
- No description.
- Type
"bottom" | "left" | "right" | "top"- Default
right
Plus 5 inherited native HTML attributes.
Styling
Target these data-slots and states, and remap these tokens, to restyle without forking the component.
[data-slot="sheet-content"]States
| Selector | State | Description |
|---|---|---|
[data-state=open] | open | Content + overlay mounted and slid in. |
[data-state=closed] | closed | Sliding out before unmount (translated off its edge). |
| 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 |
When to use
Slide a panel in from a screen edge for a secondary task or navigation that shouldn't replace the page — filters, a detail/edit panel, a nav drawer.
Reach for something else when
- A small focused confirmation (use dialog / alert-dialog)
- contextual content anchored to a control (use popover)
- a mobile bottom-sheet with drag (use drawer)
- content that needn't overlay.
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.
Troubleshooting
Console warning about a missing Title/Description; screen readers can't name the panel.
Cause. SheetContent without a SheetTitle (Radix Dialog requires it).
Fix. Add a SheetTitle (sr-only if hidden); add SheetDescription to silence the description warning.
The panel slides from the wrong edge.
Cause. side defaults to "right".
Fix. Pass side="left" | "top" | "bottom" on SheetContent.
Accessibility
- Role
dialog- ARIA APG
- dialog (modal)
- Focus
- Focus trapped inside the panel; returns to the trigger on close.
Accessibility requirements
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.