useCollapsibleHeader
Turn a list's scroll position into a collapse progress for a large header.
Draft
The API shown here is conceptual and may change.
The hook behind Collapsible header. That page explains how the header follows the scroll position, the stretch, the snap and accessibility. This one covers the API.
Installation
npx axiom add use-collapsible-headerpnpm dlx axiom add use-collapsible-headeryarn dlx axiom add use-collapsible-headerbun x axiom add use-collapsible-headerInstalls: react-native-reanimated.
Usage
With AppBar, pass progress to collapseProgress:
import Animated from "react-native-reanimated";
import { useCollapsibleHeader } from "@/hooks/use-collapsible-header";
function Inbox() {
const header = useCollapsibleHeader({
expandedHeight: 96,
collapsedHeight: 44,
});
return (
<View style={{ flex: 1 }}>
<Animated.FlatList
data={messages}
renderItem={renderMessage}
{...header.scrollProps}
/>
<AppBar
variant="large"
collapseProgress={header.progress}
style={styles.overlay}
>
<AppBar.Title>Inbox</AppBar.Title>
</AppBar>
</View>
);
}scrollProps includes contentContainerStyle.paddingTop equal to expandedHeight. If you pass your own contentContainerStyle, merge it with header.contentInset.
A custom header
For a cover image or a profile, read the shared values and build the styles yourself:
function ProfileHeader({ header }: { header: CollapsibleHeader }) {
const container = useAnimatedStyle(() => ({ height: header.height.value }));
const coverStyle = useAnimatedStyle(() => ({
opacity: interpolate(header.progress.value, [0, 0.7], [1, 0], "clamp"),
transform: [
{
scale: interpolate(
header.offset.value,
[-80, 0],
[1.3, 1],
"clamp",
),
},
],
}));
const title = useAnimatedStyle(() => ({
opacity: interpolate(header.progress.value, [0.6, 1], [0, 1], "clamp"),
}));
return (
<Animated.View style={[styles.header, container]}>
<Animated.Image
source={coverImage}
style={[StyleSheet.absoluteFill, coverStyle]}
/>
<Animated.Text style={[styles.compactTitle, title]}>
Grace Hopper
</Animated.Text>
</Animated.View>
);
}offset goes below 0 on overscroll, which is what drives the zoom on the cover.
Expanding from code
expand() scrolls the list back to the top, which expands the header. Use it from a button of your own. For the tab press and a scroll-to-top button, useScrollToTop does the same on the same list ref.
Options
| Option | Type | Default | Description |
|---|---|---|---|
expandedHeight | number | — | Header height at the top of the list, in pt, safe area excluded. |
collapsedHeight | number | 44 | Height of the compact bar, in pt. |
snap | boolean | true | When the scroll ends between the two heights, scrolls to the nearest end. |
stretch | boolean | true | Adds the overscroll to height. Turned off with Reduce Motion. |
Return value
| Key | Type | Description |
|---|---|---|
progress | SharedValue<number> | From 0 (expanded) to 1 (collapsed). |
height | SharedValue<number> | Current header height, stretch included. |
offset | SharedValue<number> | Raw scroll offset. Negative on overscroll. |
expand | () => void | Scrolls the list to the top. |
contentInset | { paddingTop: number } | Padding to apply to the list content. |
scrollProps | object | ref, onScroll, scrollEventThrottle and contentContainerStyle, for an Animated ScrollView, FlatList, SectionList or FlashList. |