ScrollFade
Fades the edges of a scroll container.
Fades the edges of a scroll container, so content passes under the boundary rather than being cut off at it.
A fading edge is also an affordance: it means there is more content behind it.
Scroll position, content size and viewport size are held in shared values and read by the fades on the UI thread, so scrolling never re-renders React.
Installation
ScrollFade ships with the library — no separate install.
import { ScrollFade, Item, PackageIcon, CheckIcon } from 'panelui-native';Or copy the source into your project, to own and edit it:
npx panelui-cli@latest add scroll-fadeUsage
<ScrollFade size={40}>
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
<Item.Group orientation="horizontal">
{categories.map((category) => (
<Item key={category.id} orientation="vertical" variant="outline" size="sm" className="w-44">
<Item.Media variant="icon"><PackageIcon size={16} /></Item.Media>
<Item.Content>
<Item.Title>{category.name}</Item.Title>
<Item.Description>{category.summary}</Item.Description>
</Item.Content>
</Item>
))}
</Item.Group>
</ScrollView>
</ScrollFade>Examples
A horizontal row of cards
A horizontal group of vertical items: each entry is a card, and the fade is what tells you there are more of them past the edge.
<ScrollFade size={40}>
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
<Item.Group orientation="horizontal">
{categories.map((category) => (
<Item key={category.id} orientation="vertical" variant="outline" size="sm" className="w-44">
<Item.Media variant="icon"><PackageIcon size={16} /></Item.Media>
<Item.Content>
<Item.Title>{category.name}</Item.Title>
<Item.Description>{category.summary}</Item.Description>
</Item.Content>
</Item>
))}
</Item.Group>
</ScrollView>
</ScrollFade>A vertical list
No horizontal on the child, so the fades move to the top and bottom edges.
<ScrollFade size={44} className="h-72">
<ScrollView showsVerticalScrollIndicator={false}>
<Item.Group>
{events.map((event, index) => (
<Fragment key={event.id}>
{index > 0 && <Item.Separator />}
<Item size="sm">
<Item.Media variant="icon"><CheckIcon size={14} /></Item.Media>
<Item.Content>
<Item.Title>{event.title}</Item.Title>
<Item.Description>{event.when}</Item.Description>
</Item.Content>
</Item>
</Fragment>
))}
</Item.Group>
</ScrollView>
</ScrollFade>One edge, on a card
The fade resolves to the theme background by default — pass color when the scrollable sits on another surface, or it will not blend.
<Card>
<Card.Content>
<ScrollFade edges="end" size={56} color="var(--color-card)">
<ScrollView horizontal>
<Item.Group orientation="horizontal">{/* … */}</Item.Group>
</ScrollView>
</ScrollFade>
</Card.Content>
</Card>With your own scroll handler
The child becomes a Reanimated animated component, so an onScroll you pass has to be an animated handler — a plain function will not run.
const onScroll = useAnimatedScrollHandler({
onScroll: (event) => {
offset.value = event.contentOffset.y;
},
});
<ScrollFade>
<Animated.ScrollView onScroll={onScroll}>
{/* … */}
</Animated.ScrollView>
</ScrollFade>Turning it off
<ScrollFade enabled={!isRefreshing}>
<ScrollView>{/* … */}</ScrollView>
</ScrollFade>API Reference
ScrollFade
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | — | |
size | number | 48 | Depth of the fade in pixels. |
edges | 'both' | 'start' | 'end' | 'none' | 'both' | Which edges fade. |
orientation | 'horizontal' | 'vertical' | — | Scroll axis. Inferred from the child's horizontal prop when omitted — pass it explicitly for children that scroll horizontally without that prop (a FlatList with horizontal set through contentContainerStyle, say). |
color | string | — | Colour the fade resolves to — normally whatever sits behind the scrollable. Defaults to the theme's background. |
fadeInDistance | number | 24 | Distance in pixels over which an edge fades from clear to full. |
enabled | boolean | true | Set false to render the child with no fades at all. |
Every part also accepts the underlying React Native props (ViewProps or TextProps) and a className for Tailwind utilities.
Notes
Orientation is read from the child's horizontal prop. Pass orientation explicitly for scrollables that do not expose it.
An edge only fades once there is content past it, and neither edge fades when the content fits inside the viewport — this is correct from the first frame, not just after the first scroll event.
The child becomes a Reanimated animated component. If you need onScroll on it yourself, it has to be an animated handler from useAnimatedScrollHandler — a plain function will not run.
The fade resolves to the theme background by default. Pass color when the scrollable sits on a card or another surface, or the fade will not blend.
Public exports
Values: ScrollFade
Types: ScrollFadeProps