Stacksheetv1.1.4

Drag to Dismiss

Swipe gestures, thresholds, and drag configuration.

Sheets can be dismissed by dragging them in the direction they came from — right panels drag right, left panels drag left, bottom panels drag down. This is enabled by default.

How it works

The gesture pipeline runs on pointer events:

  1. Dead zone (10px) — small movements are ignored to avoid accidental drags
  2. Angle check (35°) — the gesture must be roughly aligned with the dismiss direction
  3. Direction check — must be moving away from the screen (right for right panels, etc.)
  4. Commit — once past the dead zone and checks, the drag is active
  5. Threshold — on release, the sheet closes if it passed 25% of the panel width OR the velocity exceeded 0.5 px/ms

Configuration

OptionTypeDefaultDescription
dragbooleantrueEnable drag-to-dismiss
closeThresholdnumber0.25Fraction of panel dimension to trigger close (0–1)
velocityThresholdnumber0.5Velocity in px/ms to trigger close
dismissiblebooleantrueMaster switch for all dismissal (drag + backdrop + escape)
createStacksheet({
  drag: true,
  closeThreshold: 0.3,
  velocityThreshold: 0.4,
});

Disabling drag

Disable just drag while keeping backdrop/escape close:

createStacksheet({ drag: false });

Disable all forms of dismissal (drag, backdrop click, and escape key):

createStacksheet({ dismissible: false });

When dismissible is false, the sheet can only be closed programmatically via close() or pop().

Data attributes

AttributePurpose
data-stacksheet-handleMarks a drag handle. Drag always starts from handle elements, even inside no-drag zones. Added automatically by Sheet.Handle.
data-stacksheet-no-dragPrevents drag from starting on this element and its children. Useful for text inputs, sliders, or other interactive areas.

Interactive elements

Drag never starts from these elements, so text selection, button clicks, and form inputs work normally:

  • INPUT, TEXTAREA, SELECT
  • BUTTON, A
  • Elements with contentEditable

Handle-only drag

To restrict drag to only the handle area, wrap your sheet content in a data-stacksheet-no-drag container and place the handle above it:

function MySheet() {
  return (
    <>
      <Sheet.Handle />
      <div data-stacksheet-no-drag="">
        {/* Content here won't initiate drag */}
        <input type="text" placeholder="Safe to select text" />
      </div>
    </>
  );
}

Rubber-band resistance

When dragging in the opposite direction (pulling a sheet further open), the gesture uses square-root damping for elastic resistance — the same physics as iOS over-scroll. This gives tactile feedback that the sheet can't be pulled further without feeling rigid.

Scroll interaction

The drag system detects scrollable containers inside the panel. When a scrollable area hasn't reached its edge in the dismiss direction, the browser's native scroll takes priority. Once scrolled to the edge, the drag gesture activates.

For bottom panels, this means drag is suppressed when scrollTop > 0. Once scrolled back to the top, drag works normally.

Stacked sheets

When dragging a stacked sheet (stack depth > 1), the gesture calls pop() instead of close() — removing just the top sheet rather than closing the entire stack.

Combining with other features

  • Non-modal mode: drag works the same way. No overlay to interfere with.
  • Composable mode: drag works on the panel regardless of whether Sheet.Header is present.
  • Body scale: the background stays scaled during drag and animates back on close.

On this page