Position tracking
Use onPositionChange to observe the sheet’s current position. It is a standard
native event; read the distance in pixels from the bottom of the screen to the
top of the sheet from event.nativeEvent.position. 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>
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.