Hide-on-scroll
Bars slide out while the user scrolls down and come back as soon as they scroll up.
Draft
Specified, not implemented yet. The API is described in useHideOnScroll and may change.
While the user scrolls down a list, the top and bottom bars slide out of the screen and the content gets the room. The moment they scroll up, even in the middle of the list, the bars come back.
Scroll the list, with the wheel, a trackpad or by dragging it. Stop half-way and the bars snap to the nearest end.
The same behavior, frame by frame:
Bars always visible near the top.
Bars leave with the finger, pt for pt.
Clamped at the bar height. Content gets the room.
Any scroll up brings them back, mid-list.
Half-way on release: snaps to the nearest end.
When to use it
- Long feeds and lists read from top to bottom: news, social feed, search results, a long article.
- Bars that are useful but not needed while reading: AppBar, BottomTabBar, a floating action button.
Don’t hide a bar that holds the main action of the screen (a checkout button, a composer), short lists that barely scroll, or a screen where the user switches tabs often.
How it works
The bars don’t follow the scroll position, they follow the scroll delta. hidden is how many points of the bar are out of view, between 0 and the bar height:
hidden = clamp(hidden + (offset - previousOffset), 0, height);offset 0 ──────── 24 ──────── 72 ──────── 56 ──────── 52
delta +24 +48 -16 -4
hidden 0 ──────── 24 ──────── 40 ──────── 24 ──────── 20 ──► release: snaps to 0
(clamped)Four rules make it feel right:
| Rule | Why |
|---|---|
Clamp between 0 and height | Scrolling up 16pt after a long scroll down shows 16pt of bar, not nothing. |
Near the top, hidden never exceeds offset | The bars are always complete when the list is at its start. |
Ignore bounces: offsets below 0 or past the end of the content | On iOS the bounce would otherwise move the bars back and forth. |
Snap on release: at the end of the drag or the momentum, animate to 0 or height, whichever is nearest | A bar is never left half-hidden. |
Both bars read the same hidden: the top bar moves by -hidden, the bottom bar by +hidden.
Implementation
Axiom implements it with one hook, useHideOnScroll, that returns the value and the scroll props, and leaves the bars untouched.
| Library | Used for |
|---|---|
react-native-reanimated | hidden as a shared value computed in useAnimatedScrollHandler (onScroll, onEndDrag, onMomentumEnd), so the bars move on the UI thread with no React render while scrolling. withTiming for the snap. |
| React Native layout | The bars are positioned over the list, and the list gets top and bottom padding equal to the bar heights, so nothing is covered at rest. |
react-native-gesture-handler isn’t needed: the scroll events are enough, on iOS and Android.
In a screen
const bars = useHideOnScroll({ height: 56 });
<Animated.FlatList
data={posts}
renderItem={renderPost}
{...bars.scrollProps}
/>;
// top bar: translateY -bars.hidden.value, bottom bar: +bars.hidden.valueInstallation, options and return value are documented in useHideOnScroll. Components that support it take the value directly, like hideOnScroll on SearchBar in an AppBar.
Accessibility
- When a screen reader is on (
AccessibilityInfo.isScreenReaderEnabled), keep the bars shown: moving focus through a list shouldn’t take the navigation away. - Call
show()when focus moves into a bar or a keyboard opens. - With Reduce Motion on, skip the snap animation and jump to the nearest end.