ScrollText
Text that resolves word by word as the screen scrolls past it.
Installation
ScrollText ships with the library — install it once, then import:
import { ScrollText } from "@delacour/react-native-ui/scroll-text";Or copy the source into your project, to own and edit it:
bunx delacour@latest add scroll-textUsage
import { Screen } from "@/components/ui/screen";
import { ScrollText } from "@/components/ui/scroll-text";
export default function Example() {
return (
<Screen>
<Screen.ScrollArea>
<ScrollText size="2xl" weight="semibold">
We build interfaces that move when you do.
</ScrollText>
</Screen.ScrollArea>
</Screen>
);
}Wiring the scroll position
source reads the enclosing Screen by default; pass any scroll view's offset, and its ref as
viewport when it is smaller than the screen.
const scrollY = useSharedValue(0);
const viewport = useAnimatedRef<Animated.ScrollView>();
const onScroll = useAnimatedScrollHandler((event) => {
scrollY.value = event.contentOffset.y;
});
<Animated.ScrollView onScroll={onScroll} ref={viewport} scrollEventThrottle={16}>
<ScrollText source={{ kind: "offset", scrollY, viewport }}>Every word waits its turn.</ScrollText>
</Animated.ScrollView>With the default screen source and no Screen above it, the text renders fully revealed and does
not move — a development build warns once.
Colour
The default effect takes each word from muted-foreground to foreground; name either end with from and to.
<ScrollText effect={{ kind: "colour", from: "muted-foreground", to: "primary" }}>…</ScrollText>Rise
rise lifts each unit distance points into place while it fades up from a 0.15 floor; fade is the same without the lift.
<ScrollText effect={{ kind: "rise", distance: 16 }} size="3xl" weight="bold">Every word arrives on time.</ScrollText>
<ScrollText effect={{ kind: "fade", from: 0.2 }}>…</ScrollText>Highlight
highlight fills a band behind each unit — primary at 20% unless you name a color.
<ScrollText effect={{ kind: "highlight" }} overlap={0.2}>A marker band sweeps through the sentence.</ScrollText>Characters
split="character" reveals one grapheme at a time, and a low overlap makes it read as typing.
<ScrollText split="character" overlap={0.1} effect={{ kind: "fade" }}>Letter by letter.</ScrollText>Lines
split="line" reveals the lines a plain Text of the same string would break into.
<ScrollText split="line" effect={{ kind: "rise", distance: 10 }}>…</ScrollText>Setting the window
window gives the viewport fractions, 0 at the top, where the block's top edge starts and finishes
its reveal.
<ScrollText window={{ start: 1, end: 0.5 }}>Finishes by the middle of the screen.</ScrollText>Driving it from something else
A progress source takes a shared value already in 0..1, so a slider, a pager or a gesture can drive it.
const progress = useSharedValue(0);
<ScrollText source={{ kind: "progress", progress }}>The same text, by hand.</ScrollText>
<Slider defaultValue={0} onChange={(value) => (progress.value = Number(value) / 100)} />Reduced motion
Under reduce motion and DelacourProvider's isMotionCalm
the text is fully revealed and nothing moves.
Accessibility
The block is one element that reads the whole string once; the units are hidden from assistive technology.
API Reference
ScrollText
Prop
Type