Root Props
| Prop | Type | Default |
|---|---|---|
snapPoints | Array<${number}px | number> | — |
The heights the sheet rests at, like the detents of a native sheet: a fraction of the viewport height (0.5) or a pixel height ('200px'). The sheet fits its content unless SheetContent has a CSS height, and a snap point above its height rests at its top. Without snap points the sheet rests fully open. | ||
activeSnapPoint | number | — |
The currently active snap point (controlled), as an index into snapPoints. | ||
defaultSnapPoint | number | — |
The snap point the sheet opens at when uncontrolled, as an index into snapPoints. Defaults to the lowest one. | ||
onChangeActiveSnapPoint | (activeSnapPoint: number) => void | — |
| Callback fired when the user moves the sheet to another snap point. | ||
steppedSnapPoints | boolean | false |
| Moves at most one snap point per drag, however far or fast the drag is. | ||
defaultOpen | boolean | false |
| Controls whether the sheet is initially open (uncontrolled). | ||
open | boolean | — |
| Controls whether the sheet is currently open (controlled). | ||
onChangeOpen | (value: boolean) => void | — |
| Callback fired when the open state changes. | ||
children | ReactNode | ReactNode[] | — |
| The content rendered inside the sheet. Can include any layout or form elements. | ||
Child Parts
SheetTrigger
Open action for the sheet. Sets aria-haspopup="dialog" and aria-expanded.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | — |
If true, renders the trigger as a custom element using a Slot. Allows flexible composition with custom elements like <a>, <div>, or icon buttons. | ||
ref | Ref<HTMLButtonElement> | — |
| Ref forwarded to the underlying trigger element. Useful for focus management or external control. | ||
Also accepts all <button> attributes.
SheetContent
The native modal <dialog>.
| Prop | Type | Default |
|---|---|---|
ref | Ref<HTMLDialogElement> | — |
Ref forwarded to the native <dialog> element. Useful for focus management, measurement, or animation hooks. | ||
shadowed | boolean | — |
| Applies a visual shadow to the sheet panel. Adds depth and separation from the background. | ||
blurred | boolean | false |
| Blurs the page behind the sheet. | ||
overlay | boolean | true |
| Dims the page behind the sheet. When false, the page stays visible and the sheet stays modal. | ||
closedBy | ModalDialogClosedBy | 'any' |
Which user actions close the sheet, like the native closedby attribute: any a backdrop click, a swipe down or Escape, closerequest only Escape, none neither. | ||
Also accepts all <dialog> attributes.
- It sets no
idoraria-labelledbyitself: name it witharia-labelledbypointing to your heading'sid, or witharia-label.onCancelandonCloserun after the internal handlers.refpoints to the<dialog>element. blurred(defaultfalse) blurs the page behind the sheet.overlay(defaulttrue) dims the page;falsemakes the::backdroptransparent while the sheet stays modal.shadowedadds the surface shadow.- Has horizontal padding (
clamp(var(--spacing-12), 3vw, var(--spacing-16))) and bottom padding (var(--spacing-12)plus the safe-area inset). A scrollable region inside is an element withoverflow: auto. - Closes on submit of a
<form method="dialog">inside it, also withclosedBy='none'. The close goes throughonChangeOpen(false), and the submit button'svaluebecomes the dialog'sreturnValue. closedBysets which user actions close the sheet, like the nativeclosedbyattribute:'any'(a backdrop click, a swipe down, orEscape, the default),'closerequest'(Escapeonly), or'none'. UseclosedBy='none'for a sheet that must not be dismissed.- A drag follows the finger, resists past the top and bottom snap points, and settles at the snap point its momentum would carry it to, so a flick can skip a snap point. With
closedBy='any', a drag down past the lowest snap point closes the sheet. - Stays mounted only while open and while the close transition plays.
SheetHandle
The grip at the top of the sheet. It is always a <button> with no name of its own, so pass aria-label, for example Resize sheet. When the sheet has several snap points it moves the sheet to the next snap point, and back to the lowest one from the top. Otherwise it is disabled and aria-hidden.
| Prop | Type | Default |
|---|---|---|
ref | Ref<HTMLButtonElement> | — |
Ref forwarded to the handle <button>. | ||
Also accepts all <button> attributes.
SheetClose
Closes the sheet when clicked. It has no styles, icon, or default aria-label: render your own button inside it with asChild and label it.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | — |
If true, renders the close action as its child element using Slot, so any button of your own closes the sheet. | ||
ref | Ref<HTMLButtonElement> | — |
| Ref forwarded to the underlying button. | ||
Also accepts all <button> attributes.
- The close goes through
onChangeOpen(false), so the close transition plays and a controlled parent can keep the sheet open. - Use it for a standalone close button, such as the X in a header. For buttons that answer the sheet (Cancel, Confirm, a
returnValue), use a<form method="dialog">. WithoutasChildit renderstype="button", so it also works inside a form of your own without submitting it; withasChildgive your own buttontype="button".
minimal-composition.tsx · 46 lines
import { useId, type CSSProperties } from 'react';
import {
Button,
Heading,
Icon,
IconButton,
Sheet,
SheetClose,
SheetContent,
SheetHandle,
SheetTrigger,
Text,Types
SheetPropsSheetContentPropsSheetClosePropsSheetTriggerPropsSheetHandleProps
State Model
- Open state can be controlled with
openor uncontrolled withdefaultOpen. - Every close request (backdrop click,
Escape, swipe,SheetCloseclick,<form method="dialog">submit) goes throughonChangeOpen(false); a controlled sheet stays open untilopenbecomesfalse. - Page scroll is locked with CSS while the sheet is open (
html:has(dialog:modal) { overflow: hidden }), as forDialogandDrawer. - Snap points affect the visible interaction model, not just layout.
- The sheet fits its content up to the viewport height. Set a CSS height on
SheetContent(style={{ height: '80dvh' }}or a class) to make it a fixed height. Snap points are fractions of the viewport height or pixel heights, and one above the sheet's height rests at its top, so with short content the sheet stays content-sized. WithoutsnapPointsthe sheet rests fully open. defaultSnapPoint,activeSnapPoint, and theonChangeActiveSnapPointvalue are indexes intosnapPointsin the order you pass them, whatever the visual order.- The sheet opens at the lowest snap point unless
defaultSnapPointsays otherwise, and an uncontrolled sheet reopens there. onChangeActiveSnapPointfires only when the user moves the sheet to another snap point.steppedSnapPointsmoves the sheet one snap point at most per drag and resists a drag past it.- A flick carries the sheet past snap points further than it carries it to closing: a light flick down from the lowest snap point keeps it open, and a flick from a higher one settles at a snap point.
closedBy='none'onSheetContentshould be reserved for flows with a clear completion path.useSheetContextis available for advanced wrappers and controls. It returns{ open, changeOpen, contentRef, height, viewport, offsets, activeSnapPoint, changeActiveSnapPoint, steppedSnapPoints }and no longer exposescontentId, floating-uicontext,triggerProps,contentProps,transitionStatus, orisMounted.