useScrollAwareAppBar
Pre-alphaThe registry and the CLI are not published yet.Roadmap

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-bar
pnpm dlx axiom add use-scroll-aware-app-bar
yarn dlx axiom add use-scroll-aware-app-bar
bun x axiom add use-scroll-aware-app-bar

Installs: react-native-reanimated.

Usage

With AppBar, pass the two values to elevationProgress and titleProgress:

Conceptual
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:

Conceptual
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:

Conceptual
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

OptionTypeDefaultDescription
distancenumber16Scroll distance in pt over which elevation goes from 0 to 1. With a cover, its height minus the bar height.
barHeightnumber44Height of the bar, safe area excluded. The title handoff starts when the measured title reaches it.

Return value

KeyTypeDescription
elevationSharedValue<number>0 transparent, 1 background and hairline.
titleSharedValue<number>0 hidden, 1 shown. 1 when titleProps isn’t used.
offsetSharedValue<number>Raw scroll offset.
titleProps{ onLayout }Spread on the in-content title.
scrollPropsobjectonScroll and scrollEventThrottle, for an Animated ScrollView, FlatList, SectionList or FlashList.