useScrollAwareAppBar
Turn the scroll position into an elevation and a title progress for an AppBar.
Draft
The API shown here is conceptual and may change.
The hook behind Scroll-aware AppBar. That page explains the elevation, the title handoff and accessibility. This one covers the API.
Installation
npx axiom add use-scroll-aware-app-barpnpm dlx axiom add use-scroll-aware-app-baryarn dlx axiom add use-scroll-aware-app-barbun x axiom add use-scroll-aware-app-barInstalls: react-native-reanimated.
Usage
With AppBar, pass the two values to elevationProgress and titleProgress:
import Animated from "react-native-reanimated";
import { useScrollAwareAppBar } from "@/hooks/use-scroll-aware-app-bar";
const COVER_HEIGHT = 240;
function Article({ article }: { article: Article }) {
const bar = useScrollAwareAppBar({ distance: COVER_HEIGHT - 44 });
return (
<View style={{ flex: 1 }}>
<Animated.ScrollView {...bar.scrollProps}>
<Image source={article.cover} style={{ height: COVER_HEIGHT }} />
<Text variant="title" {...bar.titleProps}>
{article.title}
</Text>
<Text>{article.body}</Text>
</Animated.ScrollView>
<AppBar
elevationProgress={bar.elevation}
titleProgress={bar.title}
style={styles.overlay}
>
<AppBar.Leading>
<IconButton
icon="chevron-left"
accessibilityLabel="Back"
onPress={goBack}
/>
</AppBar.Leading>
<AppBar.Title>{article.title}</AppBar.Title>
</AppBar>
</View>
);
}titleProps carries an onLayout. Spread it on the element whose position should hand the title to the bar.
On a plain list
Without a cover or an in-content title, only the hairline matters. Leave out titleProps and keep the title always visible:
const bar = useScrollAwareAppBar({ distance: 16 });
<AppBar elevationProgress={bar.elevation}>
<AppBar.Title>Settings</AppBar.Title>
</AppBar>
<Animated.FlatList {...bar.scrollProps} … />title stays at 1 when no title has been measured.
A custom bar
Both values are shared values, from 0 to 1:
const background = useAnimatedStyle(() => ({
backgroundColor: interpolateColor(
bar.elevation.value,
[0, 1],
["transparent", colors.background.default],
),
borderBottomColor: interpolateColor(
bar.elevation.value,
[0, 1],
["transparent", colors.border.default],
),
}));
const title = useAnimatedStyle(() => ({
opacity: bar.title.value,
transform: [{ translateY: (1 - bar.title.value) * 6 }],
}));Options
| Option | Type | Default | Description |
|---|---|---|---|
distance | number | 16 | Scroll distance in pt over which elevation goes from 0 to 1. With a cover, its height minus the bar height. |
barHeight | number | 44 | Height of the bar, safe area excluded. The title handoff starts when the measured title reaches it. |
Return value
| Key | Type | Description |
|---|---|---|
elevation | SharedValue<number> | 0 transparent, 1 background and hairline. |
title | SharedValue<number> | 0 hidden, 1 shown. 1 when titleProps isn’t used. |
offset | SharedValue<number> | Raw scroll offset. |
titleProps | { onLayout } | Spread on the in-content title. |
scrollProps | object | onScroll and scrollEventThrottle, for an Animated ScrollView, FlatList, SectionList or FlashList. |