Skip to content
v2.5.0
English
Русский
Documentation

Sheet

Sheet is a compound component for panel-style overlays, usually bottom-mounted or mobile-first, with trigger, content, and handle parts.

Root Props

PropTypeDefault
snapPointsArray<${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.
activeSnapPointnumber—
The currently active snap point (controlled), as an index into snapPoints.
defaultSnapPointnumber—
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.
steppedSnapPointsbooleanfalse
Moves at most one snap point per drag, however far or fast the drag is.
defaultOpenbooleanfalse
Controls whether the sheet is initially open (uncontrolled).
openboolean—
Controls whether the sheet is currently open (controlled).
onChangeOpen(value: boolean) => void—
Callback fired when the open state changes.
childrenReactNode | 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.

PropTypeDefault
asChildboolean—
If true, renders the trigger as a custom element using a Slot. Allows flexible composition with custom elements like <a>, <div>, or icon buttons.
refRef<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>.

PropTypeDefault
refRef<HTMLDialogElement>—
Ref forwarded to the native <dialog> element. Useful for focus management, measurement, or animation hooks.
shadowedboolean—
Applies a visual shadow to the sheet panel. Adds depth and separation from the background.
blurredbooleanfalse
Blurs the page behind the sheet.
overlaybooleantrue
Dims the page behind the sheet. When false, the page stays visible and the sheet stays modal.
closedByModalDialogClosedBy'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 id or aria-labelledby itself: name it with aria-labelledby pointing to your heading's id, or with aria-label. onCancel and onClose run after the internal handlers. ref points to the <dialog> element.
  • blurred (default false) blurs the page behind the sheet.
  • overlay (default true) dims the page; false makes the ::backdrop transparent while the sheet stays modal.
  • shadowed adds 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 with overflow: auto.
  • Closes on submit of a <form method="dialog"> inside it, also with closedBy='none'. The close goes through onChangeOpen(false), and the submit button's value becomes the dialog's returnValue.
  • closedBy sets which user actions close the sheet, like the native closedby attribute: 'any' (a backdrop click, a swipe down, or Escape, the default), 'closerequest' (Escape only), or 'none'. Use closedBy='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.

PropTypeDefault
refRef<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.

PropTypeDefault
asChildboolean—
If true, renders the close action as its child element using Slot, so any button of your own closes the sheet.
refRef<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">. Without asChild it renders type="button", so it also works inside a form of your own without submitting it; with asChild give your own button type="button".
minimal-composition.tsx · 46 lines
import { useId, type CSSProperties } from 'react';
import {
  Button,
  Heading,
  Icon,
  IconButton,
  Sheet,
  SheetClose,
  SheetContent,
  SheetHandle,
  SheetTrigger,
  Text,

Types

  • SheetProps
  • SheetContentProps
  • SheetCloseProps
  • SheetTriggerProps
  • SheetHandleProps

State Model

  • Open state can be controlled with open or uncontrolled with defaultOpen.
  • Every close request (backdrop click, Escape, swipe, SheetClose click, <form method="dialog"> submit) goes through onChangeOpen(false); a controlled sheet stays open until open becomes false.
  • Page scroll is locked with CSS while the sheet is open (html:has(dialog:modal) { overflow: hidden }), as for Dialog and Drawer.
  • 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. Without snapPoints the sheet rests fully open.
  • defaultSnapPoint, activeSnapPoint, and the onChangeActiveSnapPoint value are indexes into snapPoints in the order you pass them, whatever the visual order.
  • The sheet opens at the lowest snap point unless defaultSnapPoint says otherwise, and an uncontrolled sheet reopens there.
  • onChangeActiveSnapPoint fires only when the user moves the sheet to another snap point.
  • steppedSnapPoints moves 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' on SheetContent should be reserved for flows with a clear completion path.
  • useSheetContext is available for advanced wrappers and controls. It returns { open, changeOpen, contentRef, height, viewport, offsets, activeSnapPoint, changeActiveSnapPoint, steppedSnapPoints } and no longer exposes contentId, floating-ui context, triggerProps, contentProps, transitionStatus, or isMounted.