Sheet
A gesture-driven sheet with snap points and accessible modal behavior.
Installation
pnpm dlx shadcn@latest add \
https://react-toolbox.io/r/sheet.json \
https://react-toolbox.io/r/button.jsonNew here? Set up your project first. Styles, fonts, and required components are included. The command includes the components used by the examples below.
Usage
Use Sheet for dragging and snap points. Use Drawer for a fixed navigation surface. Gesture behavior comes from Vaul and is installed only with this family. Keep long content in an inner min-h-0 overflow-y-auto region so it can scroll while the surrounding sheet handles dragging.
Composition
Fractional snap points use the viewport height. The Expand notes and Reduce notes button provides a keyboard alternative to dragging the handle. Escape or the Done action dismisses the modal.
Show composition code
'use client';
import { useId, useState } from 'react';
import { Button } from '@/components/ui/material/button';
import {
SheetRoot,
SheetTrigger,
SheetPortal,
SheetOverlay,
SheetContent,
SheetTitle,
SheetDescription,
SheetClose,
SheetHandle,
} from '@/components/ui/material/sheet';
export default function SheetExample() {
const contentId = useId();
const [snapPoint, setSnapPoint] = useState<number | string | null>(0.5);
const expanded = snapPoint === 0.9;
return (
<SheetRoot snapPoints={[0.5, 0.9]} fadeFromIndex={0} activeSnapPoint={snapPoint} setActiveSnapPoint={setSnapPoint}>
<SheetTrigger asChild>
<Button primary raised>
Open visit notes
</Button>
</SheetTrigger>
<SheetPortal>
<SheetOverlay />
<SheetContent id={contentId}>
<SheetHandle />
<div className="mx-auto min-h-0 w-full max-w-xl overflow-y-auto p-6">
<SheetTitle>Visit notes</SheetTitle>
<SheetDescription>
Drag the handle or use the resize button to switch between two sheet sizes.
</SheetDescription>
<p className="my-6 text-toolbox-body">Bring a notebook, a camera, and the latest survey map.</p>
<Button
primary
aria-controls={contentId}
aria-expanded={expanded}
onClick={() => setSnapPoint(expanded ? 0.5 : 0.9)}
>
{expanded ? 'Reduce notes' : 'Expand notes'}
</Button>
<SheetClose asChild>
<Button primary>Done</Button>
</SheetClose>
</div>
</SheetContent>
</SheetPortal>
</SheetRoot>
);
}
State and callbacks
SheetRootaccepts Vaul’sopen/onOpenChange,direction,snapPoints,activeSnapPoint/setActiveSnapPoint, anddismissibleoptions.- Use
SheetHandlefor drag and keyboard resizing; always provide a visible close action.SheetNestedRootcoordinates sheets opened from another sheet. - The parts forward primitive refs, events, and styling props. Full primitive API.
API reference
Key props and defaults. Full types and inline documentation are included with the source in material/sheet.
SheetRoot
| Property | Type | Default |
|---|---|---|
activeSnapPoint | number | string | nullControlled snap point; pair with setActiveSnapPoint. | — |
autoFocus | booleanMove focus into the sheet when it opens. | true |
container | HTMLElement | nullElement that receives the portaled overlay and content. Defaults to document.body. | — |
defaultOpen | booleanOpen on mount without the enter animation. | false |
direction | 'top' | 'bottom' | 'left' | 'right'Edge the sheet enters from. | 'bottom' |
dismissible | booleanClose on Escape, overlay interaction, or drag. With false, control open yourself. | true |
modal | booleanModal sheets trap focus and lock page scrolling. | true |
onOpenChange | (open: boolean) => void | — |
open | booleanControlled visibility. | — |
setActiveSnapPoint | (snapPoint: number | string | null) => void | — |
snapPoints | (number | string)[]Viewport fractions or CSS lengths the sheet rests at. | — |
SheetHandle
| Property | Type | Default |
|---|---|---|
preventCycle | booleanKeep a handle tap from cycling through snap points. | false |
Material guidelines
- Use a sheet for contextual content or actions while retaining the underlying task.
- Give the sheet a title and a visible dismissal action. Dragging supplements keyboard and button interaction.
- The default modal sheet uses a white surface, a 20% black backdrop, and a regular 16px title. Theme overrides can adapt these colors.