Dynamic header measurement
Header Motion automatically measures your header so it knows exactly how many pixels of scroll should collapse it. Understanding how this works will help you build headers that behave predictably.
Two layers of measurement
There are two components involved:
HeaderMotion.Headermeasures the total height of your header viaonLayout. This value is used to offset your scrollable content so it appears below the header.HeaderMotion.Header.Dynamicmeasures the collapsible part — the section that should disappear as the user scrolls. This value feeds intoprogressThreshold, which determines the scroll distance forprogressto go from0to1.
<HeaderMotion.Header>
{/* This stays visible — it's outside Header.Dynamic */}
<View style={styles.stickyTitle}>
<Text>My App</Text>
</View>
{/* This is the collapsible section */}
<View style={{ overflow: 'hidden' }}>
<HeaderMotion.Header.Dynamic>
<View style={styles.dynamicContent}>
<Text>Welcome back!</Text>
</View>
</HeaderMotion.Header.Dynamic>
</View>
</HeaderMotion.Header>
progressThreshold
The progressThreshold prop on HeaderMotion controls how many pixels of scroll it takes for progress to go from 0 (expanded) to 1 (collapsed). You can pass it in two ways:
As a number — a fixed pixel value:
<HeaderMotion progressThreshold={120}>
As a function — receives the measured dynamic height and returns the threshold:
<HeaderMotion progressThreshold={(measuredDynamic) => measuredDynamic + 20}>
When you don't pass progressThreshold at all, the default behavior is (measuredDynamic) => measuredDynamic — the collapse distance equals the dynamic section's height exactly.
Custom measurement with measureDynamic
By default, HeaderMotion.Header.Dynamic measures its height using the onLayout event:
(e) => e.nativeEvent.layout.height;
If you need a different measurement strategy — for example, measuring other properties or applying a multiplier — you can pass a custom measureDynamic function to HeaderMotion:
<HeaderMotion measureDynamic={(e) => e.nativeEvent.layout.height * 0.75}>
measureDynamicMode
The measureDynamicMode prop on HeaderMotion controls when the dynamic section is re-measured:
'mount'(default) — measures once whenHeaderMotion.Header.Dynamicfirst mounts. Subsequent layout changes are ignored.'update'— re-measures every time the layout changes. Use this when your dynamic section's height can change after mount (e.g., dynamic content loading).
<HeaderMotion measureDynamicMode="update">
Using 'update' mode means the progressThreshold can change mid-scroll, which may cause visual jumps. Prefer 'mount' unless you have a genuine need for dynamic re-measurement.
asChild
Both HeaderMotion.Header and HeaderMotion.Header.Dynamic accept an asChild prop. When true, the component does not render its own Animated.View — instead, it forwards measurement behavior to the single child element you provide.
import { LinearGradient } from 'expo-linear-gradient';
<HeaderMotion.Header asChild>
<LinearGradient colors={['#304077', '#1a1a2e']}>
{/* your header content */}
</LinearGradient>
</HeaderMotion.Header>;
This is useful when you need full control over the wrapper element.
What's next?
Now that you know how measurement works, head over to Fixed progress threshold to learn how to skip dynamic measurement when you already know the collapse distance.