Skip to content

ScrollToHide Components

The ScrollToHideProvider is a React context provider that manages scroll-to-hide animations for headers and tab bars. It provides smooth animations that hide/show UI elements based on scroll direction and position.

App.tsx
import { RNCProvider } from 'rnc-theme';
function App() {
return (
<RNCProvider
scrollToHideProps={{
headerHeight: 100,
tabBarHeight: 60,
}}
>
<YourScreenContent />
</RNCProvider>
);
}

children: React.ReactNode

The child components that will have access to the scroll context.

headerHeight?: number

Height of the header component in pixels. Default: 100

tabBarHeight?: number

Height of the tab bar component in pixels. Default: 60

  • Smooth Animations: Uses React Native Reanimated for 60fps animations
  • Bidirectional Scrolling: Responds to both scroll up and scroll down
  • Clamped Values: Prevents over-scrolling animations
  • Flexible Heights: Customizable header and tab bar heights
  • Multiple Handlers: Supports both animated and regular scroll handlers

useScrollToHide Hook Primary Hook

Section titled “useScrollToHide Hook ”

The main hook for accessing scroll-to-hide functionality and animated values.

scrollY: SharedValue

Raw scroll position value from the scroll view. Updates in real-time as user scrolls.

clampedScrollY: SharedValue

Clamped scroll position that ranges from 0 to headerHeight. Used for smooth hide/show animations.

headerTranslateY: SharedValue

Animated translateY value for the header component. Ranges from 0 to -headerHeight.

tabBarTranslateY: SharedValue

Animated translateY value for the tab bar component. Ranges from 0 to tabBarHeight.

headerHeight: number

The configured header height in pixels.

tabBarHeight: number

The configured tab bar height in pixels.

width: number

Current window width from useWindowDimensions.

onScroll: AnimatedScrollHandler

Animated scroll handler for use with Animated.ScrollView or Animated.FlatList.

onScrollRegular: ScrollHandler

Regular scroll handler for use with standard ScrollView components.

resetPosition: ResetPosition

Reset to the initial scroll position.

MyScreen.tsx
import { useScrollToHide } from 'rnc-theme';
import Animated from 'react-native-reanimated';
function MyScreen() {
const {
headerTranslateY,
tabBarTranslateY,
onScroll,
headerHeight,
tabBarHeight
} = useScrollToHide();
return (
<View style={{ flex: 1 }}>
{/* Animated Header */}
<Animated.View
style={[
{
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: headerHeight,
backgroundColor: '#fff',
zIndex: 1000,
},
{ transform: [{ translateY: headerTranslateY }] }
]}
>
<Text>Header Content</Text>
</Animated.View>
{/* Scrollable Content */}
<Animated.ScrollView
onScroll={onScroll}
scrollEventThrottle={16}
style={{ flex: 1, marginTop: headerHeight }}
>
{/* Your content here */}
</Animated.ScrollView>
{/* Animated Tab Bar */}
<Animated.View
style={[
{
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
height: tabBarHeight,
backgroundColor: '#f0f0f0',
},
{ transform: [{ translateY: tabBarTranslateY }] }
]}
>
<Text>Tab Bar Content</Text>
</Animated.View>
</View>
);
}

useScrollValues Hook Lightweight Hook

Section titled “useScrollValues Hook ”

A lightweight hook that only returns the current scroll values without the full context.

scrollValue: number

Current raw scroll position value.

clampedValue: number

Current clamped scroll position value (0 to headerHeight).

ScrollIndicator.tsx
import { useScrollValues } from 'rnc-theme';
function ScrollIndicator() {
const { scrollValue, clampedValue } = useScrollValues();
return (
<View>
<Text>Scroll Position: {scrollValue}</Text>
<Text>Clamped Value: {clampedValue}</Text>
</View>
);
}
AnimatedFlatListExample.tsx
import { useScrollToHide } from 'rnc-theme';
import Animated from 'react-native-reanimated';
function AnimatedFlatListExample() {
const { onScroll, headerTranslateY } = useScrollToHide();
const data = Array.from({ length: 50 }, (_, i) => ({ id: i, title: `Item ${i}` }));
return (
<View style={{ flex: 1 }}>
<Animated.View
style={[
styles.header,
{ transform: [{ translateY: headerTranslateY }] }
]}
>
<Text style={styles.headerText}>Dynamic Header</Text>
</Animated.View>
<Animated.FlatList
data={data}
onScroll={onScroll}
scrollEventThrottle={16}
renderItem={({ item }) => (
<View style={styles.item}>
<Text>{item.title}</Text>
</View>
)}
keyExtractor={item => item.id.toString()}
/>
</View>
);
}
RegularScrollViewExample.tsx
import { useScrollToHide } from 'rnc-theme';
import { ScrollView } from 'react-native';
function RegularScrollViewExample() {
const { onScrollRegular, headerTranslateY } = useScrollToHide();
return (
<View style={{ flex: 1 }}>
<Animated.View
style={[
styles.header,
{ transform: [{ translateY: headerTranslateY }] }
]}
>
<Text>Header with Regular ScrollView</Text>
</Animated.View>
<ScrollView
onScroll={onScrollRegular}
scrollEventThrottle={16}
>
{/* Your content */}
</ScrollView>
</View>
);
}
CustomAnimationExample.tsx
import { useScrollToHide } from 'rnc-theme';
import { interpolate, useDerivedValue } from 'react-native-reanimated';
function CustomAnimationExample() {
const { clampedScrollY, headerHeight } = useScrollToHide();
// Custom opacity animation
const headerOpacity = useDerivedValue(() => {
return interpolate(
clampedScrollY.value,
[0, headerHeight * 0.5],
[1, 0],
'clamp'
);
});
// Custom scale animation
const headerScale = useDerivedValue(() => {
return interpolate(
clampedScrollY.value,
[0, headerHeight],
[1, 0.8],
'clamp'
);
});
return (
<Animated.View
style={[
styles.header,
{
opacity: headerOpacity,
transform: [{ scale: headerScale }]
}
]}
>
<Text>Custom Animated Header</Text>
</Animated.View>
);
}
CompleteLayoutExample.tsx
function CompleteLayoutExample() {
const {
headerTranslateY,
tabBarTranslateY,
onScroll,
headerHeight,
tabBarHeight,
width
} = useScrollToHide();
return (
<View style={{ flex: 1 }}>
{/* Status Bar Spacer */}
<View style={{ height: 44 }} />
{/* Header */}
<Animated.View
style={[
{
position: 'absolute',
top: 44,
left: 0,
right: 0,
height: headerHeight,
backgroundColor: '#ffffff',
zIndex: 1000,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 5,
},
{ transform: [{ translateY: headerTranslateY }] }
]}
>
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text style={{ fontSize: 18, fontWeight: 'bold' }}>
Scroll to Hide Header
</Text>
</View>
</Animated.View>
{/* Main Content */}
<Animated.ScrollView
onScroll={onScroll}
scrollEventThrottle={16}
style={{ flex: 1 }}
contentContainerStyle={{
paddingTop: headerHeight,
paddingBottom: tabBarHeight
}}
>
{Array.from({ length: 50 }, (_, i) => (
<View key={i} style={styles.contentItem}>
<Text>Content Item {i + 1}</Text>
</View>
))}
</Animated.ScrollView>
{/* Tab Bar */}
<Animated.View
style={[
{
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
height: tabBarHeight,
backgroundColor: '#f8f8f8',
flexDirection: 'row',
borderTopWidth: 1,
borderTopColor: '#e0e0e0',
},
{ transform: [{ translateY: tabBarTranslateY }] }
]}
>
<TouchableOpacity style={styles.tabItem}>
<Text>Home</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.tabItem}>
<Text>Search</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.tabItem}>
<Text>Profile</Text>
</TouchableOpacity>
</Animated.View>
</View>
);
}
// ❌ This will throw an error
function ComponentOutsideProvider() {
const scrollContext = useScrollToHide(); // Error!
return <View />;
}
// Error: useScrollToHide must be used within a ScrollToHideProvider

The components provide full TypeScript support with proper type definitions:

TypedComponent.tsx
import { useScrollToHide, ScrollToHideProvider } from 'rnc-theme';
import type { ScrollToHideProviderProps } from 'rnc-theme';
// Provider props are fully typed
const providerProps: ScrollToHideProviderProps = {
children: <MyComponent />,
headerHeight: 120,
tabBarHeight: 80,
};
function TypedComponent() {
const {
scrollY, // SharedValue<number>
clampedScrollY, // SharedValue<number>
headerTranslateY, // SharedValue<number>
tabBarTranslateY, // SharedValue<number>
headerHeight, // number
tabBarHeight, // number
width, // number
onScroll, // AnimatedScrollHandler
onScrollRegular, // (event: NativeSyntheticEvent<NativeScrollEvent>) => void
} = useScrollToHide();
// All values are properly typed
}
  1. Use scrollEventThrottle: Set to 16 for smooth 60fps animations
  2. Avoid excessive re-renders: Use useScrollValues when you only need values
  3. Memoize expensive operations: Cache complex calculations based on scroll values
  4. Optimize scroll content: Use FlatList for large datasets
  5. Profile animations: Use Flipper or React DevTools to monitor performance
Worklet Implementation
// The onScrollRegular handler is marked as 'worklet' for performance
const onScrollRegular = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
'worklet'; // Runs on UI thread
scrollY.value = event.nativeEvent.contentOffset.y;
};

Consistent Heights

Define header and tab bar heights as constants to maintain consistency across your app.

Safe Area Handling

Consider device safe areas when positioning absolute elements.

Performance Testing

Test scroll performance on lower-end devices to ensure smooth animations.

Common Styles
const styles = StyleSheet.create({
header: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
backgroundColor: '#ffffff',
zIndex: 1000,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 5,
},
tabBar: {
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
backgroundColor: '#f8f8f8',
flexDirection: 'row',
borderTopWidth: 1,
borderTopColor: '#e0e0e0',
},
contentItem: {
padding: 20,
borderBottomWidth: 1,
borderBottomColor: '#e0e0e0',
},
tabItem: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
});

Animations not smooth

Cause: Missing scrollEventThrottle={16} or running on JS thread Solution: Add throttling and ensure worklet usage

Header/TabBar positioning issues

Cause: Incorrect z-index or positioning values Solution: Use proper absolute positioning and z-index values

Context errors

Cause: Using hooks outside of RNCProvider Solution: Wrap your app with RNCProvider