Position tracking
Use onPositionChange to observe the sheet’s current position. It is a standard
native event; read the distance from the bottom of the sheet’s host to the top
of the sheet from event.nativeEvent.position, in layout units (points on iOS,
dp on Android, and CSS pixels on the web). For a modal sheet the host fills the
screen; for an inline sheet it is the sheet’s container. The same event also
carries event.nativeEvent.index—the fractional detent index in
0..(detents.length - 1) (0 at the shortest detent, 1 at the next, and so
on, interpolated in between)—the continuous counterpart of onIndexChange,
handy for driving a backdrop or per-detent animation without knowing the sheet’s
height.
<BottomSheet // Or `ModalBottomSheet`.
index={index}
onIndexChange={setIndex}
surface={/* ... */}
onPositionChange={(event) => {
console.log(event.nativeEvent.position, event.nativeEvent.index);
}}
>
{/* ... */}
</BottomSheet>
A lazy sheet emits no onPositionChange events
while it is unmounted, from the moment it settles at a closed detent until it
opens again. Handle the closed state from onSettle if you need it.
With Reanimated
To keep the latest position in a Reanimated shared value, update it from the callback:
const position = useSharedValue(0);
<BottomSheet
index={index}
onIndexChange={setIndex}
surface={/* ... */}
onPositionChange={(event) => {
position.value = event.nativeEvent.position;
}}
>
{/* ... */}
</BottomSheet>
Because onPositionChange is a native event, you can also handle it on the UI
thread. Pass Animated.createAnimatedComponent to wrapNativeView—the library
applies it to the native sheet view—and give onPositionChange a worklet
handler from useEvent:
import type { NativeSyntheticEvent } from 'react-native';
import Animated, { useEvent, useSharedValue } from 'react-native-reanimated';
import {
BottomSheet,
type PositionChangeEventData,
} from '@swmansion/react-native-bottom-sheet';
const position = useSharedValue(0);
const detentIndex = useSharedValue(0);
const onPositionChange = useEvent<
NativeSyntheticEvent<PositionChangeEventData>
>(
(event) => {
'worklet';
position.value = event.position;
detentIndex.value = event.index;
},
['onPositionChange']
);
<BottomSheet // Or `ModalBottomSheet`.
index={index}
onIndexChange={setIndex}
surface={/* ... */}
wrapNativeView={Animated.createAnimatedComponent}
onPositionChange={onPositionChange}
>
{/* ... */}
</BottomSheet>
wrapNativeView keeps the animated wrapper on the native sheet view itself, so
the worklet binds on first render—for both inline and modal sheets—without the
library depending on Reanimated. Pass a stable function (such as
Animated.createAnimatedComponent), not an inline lambda.
Inside the worklet, Reanimated unwraps the native event, so you read
event.position directly rather than event.nativeEvent.position. The handler
runs on the UI thread on every frame the sheet moves.