ScrollText

Text that resolves word by word as the screen scrolls past it.

Open Source

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-text

Usage

app/index.tsx
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.

Colour
<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.

Rise
<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.

Highlight
<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.

Characters
<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.

Lines
<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.

Scrub
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

On this page