Animation

A spring or a timing. The platform defaults, reduce motion, and what each callback hears.

<BottomSheet animation={{ type: "spring", damping: 30, stiffness: 300 }}>

One animation for every move: the open, the close, a snap, the keyboard's extend, a snap point that moved under a settled sheet. A gesture's release hands the spring half its velocity, so a flick continues rather than starting from rest.

SheetAnimation

type SheetAnimation =
  | {
      type: "spring";
      damping?: number;
      stiffness?: number;
      mass?: number;
      overshootClamping?: boolean;
      restDisplacementThreshold?: number;
      restSpeedThreshold?: number;
      reduceMotion?: "system" | "always" | "never";
    }
  | {
      type: "timing";
      duration?: number;
      easing?: "linear" | "outExp" | "outCubic" | "inOutCubic";
      reduceMotion?: "system" | "always" | "never";
    };

Every field is optional; a missing one is filled from the platform default. easing is a name, not a function, because the config is resolved as plain data in the pure core and only becomes a Reanimated Easing once, on the JS thread, when it changes.

Platform defaults

PlatformDefault
iOS{ type: "spring", damping: 500, stiffness: 1000, mass: 3, overshootClamping: true }
everything else{ type: "timing", duration: 250, easing: "outExp" }

The spring is stiff and over-damped — it settles in a few frames with no overshoot — and it is the same tuning the library this replaces shipped, so a sheet migrated from it moves the same. Android gets a timing because a spring is the worse guess wherever there is no native driver tuned for it.

Reanimated 4 replaced the spring's two rest thresholds with one relative energyThreshold; the two fields are accepted and kept as data, and the default threshold settles within a frame or two of them.

Reduce motion

Every config carries Reanimated's ReduceMotion, system by default: a reader who has asked the OS to reduce motion gets the end state and no animation. overrideReduceMotion on the root beats the config's own reduceMotion, which beats "system".

<BottomSheet overrideReduceMotion="never">

Mount

animateOnMount (the default) animates the first open from closed; off, the sheet lands on its snap point. Either way the first open waits for layout: the frame, the handle, the content and — when there is one — the footer all have to report a size before the sheet has a snap point to move to. An open asked for before that is queued, not dropped.

Callbacks

PropFiresWith
onAnimatean animation startedfromIndex, toIndex, fromHeight, toHeight, source
onIndexChangea settle moved the indexindex, height, source — -1 on close
onClosethe sheet reached its closed height, by any path
onOpenChangeopen state flipped, by any pathisOpen — false follows onClose

source is why the move happened: mount, gesture, user (a method or a controlled prop), snapPoints (the snap points changed under a settled sheet), keyboard or container (a resize).

A cancelled animation settles nothing: a drag that interrupts an open owns the height from that frame, and reporting the open's target would name an index the sheet never reached. The callbacks read their latest props through a ref, so a handler that changes identity every render is fine.

The step transition

BottomSheet.Steps takes an animation of its own for the height glide and the crossfade or slide between steps. Omitted, it uses the root's, which is what keeps the panel's top edge and the body's bottom edge moving together.

API Reference

Prop

Type

On this page