Skip to content

Sheet

A panel that slides in from a screen edge for a secondary task or navigation.

Stablev0.4.2added in v0.1.0@garn/ui/sheet
On 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.

terminal
npx garn-ui add sheet
First time? Set up garn in your project
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.

SheetRequired

Root — 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

SelectorState
[data-state=open]open
[data-state=closed]closed
PropertyTokenTier
padding--garn-pad-surfacesemantic · air
gap--garn-gap-stacksemantic · air
headerGap--garn-gap-fieldsemantic · air
closeSize--garn-control-h-xssemantic · air
motion--garn-motion-basesemantic

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

Do
  • Always include a SheetTitle (sr-only if you don't want it visible).
  • Pick side to 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
  • 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

warn

Every SheetContent must contain a SheetTitle (wrap it in sr-only to hide it visually).

when SheetContent has no SheetTitle

Keyboard

EscapeClose the sheet.
TabShift+TabCycle focus within the sheet (focus is trapped).
A1.3.1Info and RelationshipsA2.1.1KeyboardA2.4.3Focus OrderA4.1.2Name, Role, Value
  • 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.