Reference

Sheet API

The sheet API is assembled from state, view, content, and action primitives so consumers can adopt only the layers they need.

View as Markdown

The headless Sheet contract is a compound React API. Every primitive forwards its ref and normal DOM props unless the table says otherwise.

Import

tsx
import {
  Sheet,
  SheetStack,
} from "@velvetui/react/sheet";
import "@velvetui/react/sheet.css";

Sheet.Root

PropTypeDefaultPurpose
openbooleanuncontrolledControlled presented state
defaultOpenbooleanfalseInitial uncontrolled state
onOpenChange(open) => voidReceives requested state changes
snapnumberuncontrolledControlled detent index
defaultSnapnumberdefaultOpen ? 1 : 0Initial detent
onSnapChange(snap) => voidReceives detent changes
sheetRole"dialog" | "alertdialog""dialog"Modal semantics and dismissal policy

Sheet.Panel

Panel is the convenient composition of Portal, View, Backdrop, Content, and an optional Handle.

PropTypeDefault
sideSheetSidebottom
snapPointsdetent or arraynone
modal, dismissible, draggablebooleanView defaults
portalbooleantrue
containerElement, DocumentFragment, or React RefObjectdocument.body
portalProps, viewPropspart props
viewref-forwarding React elementdefault View div
backdropboolean or elementtrue
backdropPropsBackdrop props
handleboolean or elementtrue
handlePropsHandle props
contentPropsContent props
travelAnimation, stackingAnimationanimation map
tsx
<Sheet.Root>
  <Sheet.Trigger>Open filters</Sheet.Trigger>
  <Sheet.Panel
    side="bottom"
    snapPoints={["40lvh", "78lvh"]}
    viewProps={{ tracks: "auto", nativeEdgeSwipePrevention: true }}
    backdropProps={{ travelAnimation: { opacity: [0, 0.42] } }}
  >
    <Sheet.Title>Filters</Sheet.Title>
    <Filters />
    <Sheet.Close>Apply</Sheet.Close>
  </Sheet.Panel>
</Sheet.Root>

Sheet.Portal

Portal accepts a resolved DOM host or a React ref. Passing a ref is the preferred form for a host rendered by the same component because it does not require callback-ref state.

tsx
const portalHostRef = useRef<HTMLDivElement>(null);

<div ref={portalHostRef} className="portal-host" />

<Sheet.Portal container={portalHostRef}>
  <Sheet.View side="right">...</Sheet.View>
</Sheet.Portal>

If the ref is assigned during the same commit as an already-open sheet, Velvet waits for the ref and mounts the portal after it resolves. A supplied ref never silently falls back to document.body while its current value is null.

Sheet.View

PropTypeDefaultPurpose
sidetop | right | bottom | left | centerbottomAuthored content placement
snapPointsnumber | string | arraynoneIntermediate resting lengths
draggablebooleantrueEnables native gesture travel
dismissiblebooleantrueAllows the closed destination
modalbooleantrueEnables inertness, focus scope, and scroll lock
tracksTrack | Track[] | "auto"inferredAllowed gesture direction or scroll handoff
swipeOvershootbooleantrueAllows native rubber-band travel
swipeTrapboolean | { x?: boolean; y?: boolean }modal-awareControls gesture containment by axis
snapOutAcceleration"auto" | numberautoBiases native travel toward dismissal
snapToEndDetentsAcceleration"auto" | numberautoBiases travel toward edge detents
enteringAnimationSettingspreset or settingssmoothProgrammatic opening motion
exitingAnimationSettingspreset or settings520 / 44 / 1 springProgrammatic closing motion
steppingAnimationSettingspreset or settingsentering settingsProgrammatic detent motion
nativeEdgeSwipePreventionbooleanfalsePrevents browser edge navigation conflicts

View callbacks

CallbackPayload
onTravel{ progress, range, progressAtDetents }
onTravelStartno payload
onTravelEndno payload
onTravelStatusChangeidleOutside, entering, idleInside, stepping, exiting
onClickOutsideobject or handler with changeDefault
onEscapeKeyDownobject or handler with changeDefault
tsx
<Sheet.View
  onClickOutside={{ dismiss: false }}
  onEscapeKeyDown={({ changeDefault }) => {
    if (formState.isDirty) changeDefault({ dismiss: false });
  }}
/>

Visual and action parts

PartImportant props
Sheet.Portal`container: Element
Sheet.BackdroptravelAnimation, asChild
Sheet.ContenttravelAnimation, stackingAnimation, asChild
Sheet.BleedingBackgroundnormal div props, asChild
Sheet.Outlettravel and stacking animation
Sheet.Triggeraction, snapTo, onPress, asChild
Sheet.CloseTrigger fixed to close intent
Sheet.StepsnapTo, direction: up | down
Sheet.Handleclose or step action
Sheet.Titleheading props; supplies accessible name
Sheet.Descriptionparagraph props; supplies description

Actions and detent indexes

0 means closed. Positive indexes address resting detents in travel order. Use action="open", close, toggle, step, or { type: 'step', snapTo, direction }.

tsx
<Sheet.Trigger action="open">Open</Sheet.Trigger>
<Sheet.Trigger action="toggle">Toggle</Sheet.Trigger>
<Sheet.Step snapTo={2}>Details</Sheet.Step>
<Sheet.Handle action={{ type: "step", direction: "down" }} />

asChild contract

The child becomes the real DOM node. Pass one non-Fragment element and forward its ref, children, className, style, ARIA/data attributes, disabled state, and event handlers to the final DOM node.

Child handlers run first and event.preventDefault() cancels Velvet's behavior. Refs are composed, class names are concatenated, and the child's inline style wins on collisions. See Bring your own components for complete examples.