@rific/scroll-view
v0.6.5
Published
Blur styled scroll system for React Native. Floating headers/footers, keyboard awareness, and scroll-away chrome
Downloads
886
Maintainers
Readme
@rific/scroll-view
Blur-chrome scroll system for React Native. Drop-in replacements for ScrollView, FlatList, and SectionList with floating headers and footers, pull-to-search, keyboard awareness, horizontal paging, and frosted-glass chrome.
Installation
npm install @rific/scroll-viewPeer dependencies
npm install @rific/auto-paper react-native-reanimated react-native-gesture-handler react-native-safe-area-context react-native-paper react-native-keyboard-controllerThe exported Redux slice has no dependency on @reduxjs/toolkit — it works with RTK stores, vanilla Redux, or no Redux at all.
Quick start
Wrap your screen in ScrollViewProvider, add a ScrollViewHeader, then drop in one of the scroll components.
import { ScrollViewProvider, ScrollViewHeader, FlatList } from '@rific/scroll-view'
export default function MyScreen() {
return (
<ScrollViewProvider>
<ScrollViewHeader title="My Screen" backAction={() => router.back()} />
<FlatList data={items} renderItem={({ item }) => <Row item={item} />} keyExtractor={(item) => item.id} />
</ScrollViewProvider>
)
}For app-wide defaults, wrap your root layout with ScrollViewSettingsProvider.
import { ScrollViewSettingsProvider } from '@rific/scroll-view'
export default function RootLayout({ children }) {
return (
<ScrollViewSettingsProvider initialValue={{ snapBack: true }}>
{children}
</ScrollViewSettingsProvider>
)
}Components
ScrollViewSettingsProvider
App-wide defaults for scroll settings. Wrap your root layout to set global defaults.
| Prop | Type | Description |
|------|------|-------------|
| children | ReactNode | — |
| initialValue | Partial<ScrollViewSettings> | Initial settings applied once on mount |
| onChange | (settings: ScrollViewSettings) => void | Called whenever settings change |
ScrollViewProvider
Screen-level provider that owns header/footer state and scroll position. Must wrap ScrollViewHeader, ScrollViewFooter, and the scroll component.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| blur | boolean | system | Enable frosted-glass backdrop on header/footer |
| fixed | boolean | false | Pin both header and footer (overrides headerFixed/footerFixed) |
| footerAboveKeyboard | boolean | false | With a fixed footer, float it above the keyboard instead of letting the keyboard cover it — see Keyboard awareness |
| headerFixed | boolean | false | Pin header; overrides settings default |
| footerFixed | boolean | false | Pin footer; overrides settings default |
| snapBack | boolean | false | Snap header and footer back when scrolling up |
| snapBackHeader | boolean | — | Override snapBack for header only |
| snapBackFooter | boolean | — | Override snapBack for footer only |
| tabBarHeight | number | 0 | Extra bottom inset reserved for a host app's own persistent tab bar (added on top of the safe-area inset, independent of any ScrollViewFooter) |
ScrollViewHeader
Floating header with blur backdrop, title, back action, trailing action, and built-in progress bar. Place it as a direct child of ScrollViewProvider.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| title | string | — | Center title text |
| caption | string | — | Subtitle below title |
| centerContent | ReactNode | — | Replaces title/caption with custom content |
| children | ReactNode | — | Left-side content (icon buttons, etc.) |
| backAction | () => void | — | Renders a back button and calls this on press |
| backActionFixed | boolean | settings | Keep back button visible as header scrolls away |
| trailingAction | ReactNode | — | Right-side floating action |
| trailingActionFixed | boolean | true | Keep trailing action visible as header scrolls away |
| actionSize | number | 48 | Size of floating action buttons |
| iconSize | number | actionSize/2 | Icon size inside action buttons |
| actionStyle | ViewStyle | — | Style applied to action button backgrounds |
| style | ViewStyle | — | Style applied to the header content area |
| topInset | boolean | true | Pad for safe area top inset |
ScrollViewFooter
Floating footer with blur backdrop. Place it as a direct child of ScrollViewProvider.
| Prop | Type | Description |
|------|------|-------------|
| children | ReactNode | Footer content |
| style | ViewStyle | Style applied to the inner row |
ScrollView
Drop-in for React Native's ScrollView. Accepts all ScrollViewProps plus:
| Prop | Type | Description |
|------|------|-------------|
| chipProps | ChipProps | Customize the scroll-to-top chip (label, style, etc.) |
| chipThreshold | number | Scroll offset (px) before the chip appears. Default 100 |
| footerFixed | boolean | Pin footer for this scroll view; overrides provider |
| gesture | GestureType | Compose with an external RNGH gesture |
| headerFixed | boolean | Pin header for this scroll view; overrides provider |
| keyboardAware | boolean | Add keyboard height to bottom content inset |
| onChipPress | () => void | Additional callback fired when the chip is pressed |
| onRefresh | () => void | Enables pull-to-refresh |
| pullSearchHeight | number | Reserve space above content for PullSearch |
| ref | RefObject<RNScrollView> | Forward ref to the underlying scroll view |
| refreshing | boolean | Controlled refreshing state |
FlatList
Drop-in for React Native's FlatList. Accepts all FlatListProps<T> plus:
| Prop | Type | Description |
|------|------|-------------|
| chipProps | ChipProps | Customize the scroll-to-top/start chip (label, style, etc.) |
| chipThreshold | number | Scroll offset (px) before the chip appears. Default 100 |
| footerFixed | boolean | Pin footer for this list; overrides provider |
| gesture | GestureType | Compose with an external RNGH gesture |
| headerFixed | boolean | Pin header for this list; overrides provider |
| keyboardAware | boolean | Add keyboard height to bottom content inset |
| onChipPress | () => void | Additional callback fired when the chip is pressed |
| onRefresh | () => Promise<void> \| void | Enables pull-to-refresh |
| pullSearchHeight | number | Reserve space above content for PullSearch |
| ref | RefObject<RNFlatList> | Forward ref to the underlying list |
| renderFilters | ReactNode | Rendered below ListHeaderComponent, above list items |
Performance props are user-overridable with sensible defaults:
| Prop | Default |
|------|---------|
| initialNumToRender | 20 |
| maxToRenderPerBatch | 50 |
| windowSize | 100 |
| removeClippedSubviews | false |
| showsHorizontalScrollIndicator | horizontal |
| showsVerticalScrollIndicator | !horizontal |
When horizontal and pagingEnabled are both true and data has more than one item, horizontal dot indicators render automatically.
SectionList
Drop-in for React Native's SectionList. Accepts all SectionListProps<ItemT, SectionT> (except horizontal) plus the same set of library props as FlatList. Custom sticky section headers are supported — pass renderSectionHeader and stickySectionHeadersEnabled and the library renders them via an animated overlay so they properly account for the floating header offset.
CustomList
Wraps any list component that follows the FlatList scroll API (e.g. FlashList). Pass the component via the component prop; all other props are forwarded.
import { FlashList } from '@shopify/flash-list'
import { CustomList } from '@rific/scroll-view'
<CustomList component={FlashList} data={items} renderItem={renderItem} estimatedItemSize={80} />| Prop | Type | Description |
|------|------|-------------|
| chipProps | ChipProps | Customize the scroll-to-top chip (label, style, etc.) |
| component | ComponentType<P> | The list component to render |
| footerFixed | boolean | Pin footer; overrides provider |
| gesture | GestureType | Compose with an external RNGH gesture |
| headerFixed | boolean | Pin header; overrides provider |
| keyboardAware | boolean | Add keyboard height to bottom content inset |
| onRefresh | () => Promise<void> \| void | Enables pull-to-refresh |
| pullSearchHeight | number | Reserve space above content for PullSearch |
| renderFilters | ReactNode | Rendered below ListHeaderComponent, above list items |
| scrollRef | RefObject<{ scrollToOffset }> | Forward ref to the underlying list |
The underlying component receives contentInset, contentOffset, onScroll, refreshControl, and scrollEventThrottle — props it must support for the library to function.
ChipProps
Passed via chipProps on any scroll component to customise the floating scroll-to-top chip. Accepts all Chip props from @rific/auto-paper except compact, icon, and onPress (which are managed by the library), plus:
| Prop | Type | Description |
|------|------|-------------|
| label | ReactNode | Override the default label ("Top" / "Start") |
| style | ViewStyle | Merged with the chip's default style |
<FlatList
chipProps={{ label: 'Back to top', style: { opacity: 0.9 } }}
data={items}
renderItem={renderItem}
keyExtractor={(item) => item.id}
/>PullSearch
A search bar that lives above the list content and is revealed by pulling down. Use alongside pullSearchHeight on the scroll component.
import { useRef, useState } from 'react'
import { FlatList, PullSearch, type PullSearchHandle } from '@rific/scroll-view'
export default function SearchScreen() {
const searchRef = useRef<PullSearchHandle>(null)
const [query, setQuery] = useState('')
const [searchHeight, setSearchHeight] = useState(0)
return (
<ScrollViewProvider>
<ScrollViewHeader title="Search" />
<FlatList
data={filteredItems}
renderItem={renderItem}
keyExtractor={(item) => item.id}
pullSearchHeight={searchHeight}
ListHeaderComponent={
<PullSearch
ref={searchRef}
value={query}
onChangeText={setQuery}
onHeightChange={setSearchHeight}
placeholder="Search…"
debounce
/>
}
/>
</ScrollViewProvider>
)
}| Prop | Type | Description |
|------|------|-------------|
| onChangeText | (text: string) => void | Called with trimmed search text |
| onHeightChange | (height: number) => void | Required — pass the height back as pullSearchHeight |
| value | string | Controlled value |
| placeholder | string | Placeholder text |
| debounce | boolean | Debounce onChangeText by 500ms |
Ref methods: focus(), blur()
Keyboard awareness
Pass keyboardAware to any scroll component (ScrollView, FlatList, SectionList, CustomList) to add the keyboard's height to the bottom content inset, so a focused field near the bottom of the content stays above the keyboard instead of getting hidden behind it.
<ScrollViewProvider>
<ScrollViewHeader title="Edit profile" />
<ScrollView keyboardAware>
<TextInput label="Name" />
<TextInput label="Bio" multiline />
</ScrollView>
</ScrollViewProvider>By default, a fixed ScrollViewFooter doesn't follow the keyboard — it stays pinned to the screen bottom and the keyboard simply covers it. If the footer holds something the user needs while typing (a submit button, say), set footerAboveKeyboard on ScrollViewProvider so the footer floats above the keyboard instead, reachable without dismissing it first:
<ScrollViewProvider footerFixed footerAboveKeyboard>
<ScrollViewHeader title="Edit profile" />
<ScrollView keyboardAware>
<TextInput label="Name" />
<TextInput label="Bio" multiline />
</ScrollView>
<ScrollViewFooter>
<Button mode="contained" onPress={handleSubmit}>Save</Button>
</ScrollViewFooter>
</ScrollViewProvider>footerAboveKeyboard only has an effect when the footer is fixed (footerFixed) — a footer that scrolls away with content has nothing to reserve keyboard space for. Always pair it with keyboardAware: true on any scroll component that could have a focused input while it's visible. Content always reserves space for the current footer height regardless of keyboardAware (so a fixed footer never overlaps it) — and while floating, that height already includes the keyboard, so a list with keyboardAware: false ends up reserving keyboard space anyway, just indirectly through the footer rather than directly. There's no useful configuration where you'd want the footer reachable above the keyboard but not want that same list's own content clear of it too, so don't rely on keyboardAware: false to opt a list out of this while footerAboveKeyboard is on — it won't.
Floating is done by growing the footer's own container rather than translating it, so its BlurView backdrop always stays sealed against the true physical screen bottom — nothing shows through beneath it, including at the keyboard's rounded top corners.
While floating above an open keyboard, the footer also drops its safe-area bottom inset — it's no longer sitting at the physical screen edge, so that padding would just be dead space under its content. As the keyboard closes, the inset ramps back in continuously over the last bit of its travel rather than snapping back once it's fully closed, so the content settles at the safe-area line smoothly instead of dropping to the physical bottom edge and correcting back up.
Keyboard awareness is native-only — useKeyboardInset (and therefore keyboardAware/footerAboveKeyboard) always resolves to 0 height on web.
Hooks
useScrollView
Access scroll state and progress control from anywhere inside a ScrollViewProvider.
const { scrollPosition, scrollHeight, progressing, progress, setProgress, setProgressing } = useScrollView()| Return | Type | Description |
|--------|------|-------------|
| scrollPosition | SharedValue<number> | Live scroll offset (Reanimated worklet-safe) |
| scrollHeight | number | Visible scroll area height (window minus header/footer) |
| progressing | boolean | Whether the header progress bar is visible |
| progress | number \| null | Progress bar value (0–1), or null for indeterminate |
| setProgress | (p: number \| null) => void | Set progress; pass null for indeterminate |
| setProgressing | (b: boolean) => void | Show/hide the progress bar |
// Show an indeterminate progress bar while loading
useEffect(() => {
setProgressing(true)
fetchData().finally(() => setProgressing(false))
}, [])useScrollViewSettings
Read and update scroll settings at runtime from anywhere inside ScrollViewSettingsProvider.
const { settings, set } = useScrollViewSettings()
// Toggle snap-back on the fly
set({ snapBack: true })useKeyboardInset
Returns the current keyboard height, updating as the keyboard animates in and out. Useful when you need the keyboard height outside of a scroll component.
const keyboardHeight = useKeyboardInset()Redux integration
If your app uses Redux, you can drive scroll settings from the store instead of (or in addition to) ScrollViewSettingsProvider.
// store.ts
import { scrollViewReducer } from '@rific/scroll-view'
export const store = configureStore({
reducer: {
scrollView: scrollViewReducer,
// ...
}
})// Dispatch initial settings (e.g. from a remote config)
store.dispatch(scrollViewActions.initialize({ snapBack: true, headerFixed: false, footerFixed: false, backActionFixed: true }))Settings reference
ScrollViewSettings defaults:
| Setting | Default | Description |
|---------|---------|-------------|
| headerFixed | false | Pin header globally |
| footerFixed | false | Pin footer globally |
| snapBack | false | Snap header and footer back when scrolling up |
| snapBackHeader | — | Override snapBack for header only |
| snapBackFooter | — | Override snapBack for footer only |
| backActionFixed | true | Keep back button visible as header scrolls away |
Settings cascade: ScrollViewSettingsProvider → ScrollViewProvider → individual scroll component props. More specific values always win.
