Reference
Scroll API
Coordinate nested scrolling, virtual keyboards, focus, progress, and imperative spring scrolling.
Scroll coordinates long content, virtual keyboards, programmatic travel, and the gesture handoff between a nested scroller and its parent sheet.
Primitive tree
tsx
<Scroll.Root componentRef={scrollApi}>
<Scroll.View
axis="y"
safeArea="visual-viewport"
onScrollProgress={({ progress, distance }) => {}}
>
<Scroll.Content><Article /></Scroll.Content>
</Scroll.View>
</Scroll.Root>Scroll.Root and imperative API
componentRef receives getProgress, getDistance, getAvailableDistance, scrollTo, and scrollBy.
tsx
scrollApi.current?.scrollTo({
progress: 1,
animationSettings: "smooth",
});
scrollApi.current?.scrollBy({
distance: -160,
animationSettings: { stiffness: 420, damping: 40 },
});Scroll.View props
| Prop | Type | Default |
|---|---|---|
axis | x | y | y |
safeArea | none, layout-viewport, visual-viewport | visual-viewport |
scrollGestureTrap | boolean or string | false |
scrollGestureOvershoot | boolean | true |
onScrollProgress | progress callback | — |
onFocusInside | policy object or callback | — |
nativeFocusScrollPrevention | boolean | true |
Scroll.Trigger
tsx
<Scroll.Trigger action={{
type: "scroll-to",
progress: 1,
animationSettings: "snappy",
}}>
Jump to end
</Scroll.Trigger>The action type is scroll-to or scroll-by. Supply either distance in pixels or normalized progress.
Sheet handoff
tsx
<Sheet.View side="bottom" tracks="auto">
<Sheet.Content asChild>
<Scroll.Root asChild>
<Scroll.View><Scroll.Content><Feed /></Scroll.Content></Scroll.View>
</Scroll.Root>
</Sheet.Content>
</Sheet.View>With tracks="auto", the sheet reads the nearest scroll boundary. A downward drag dismisses only after the scroller reaches its start.
Keyboard-safe forms
Use safeArea="visual-viewport" and include interactive-widget=resizes-content in the viewport meta tag. Keep the focused field and fixed composer inside the same Scroll.View.
html
<meta
name="viewport"
content="width=device-width, initial-scale=1, viewport-fit=cover, interactive-widget=resizes-content"
/>