About this animation
A conversation timeline with a swipeable summary stack and composer, with configurable threads, contact details, colors, and fonts.
Why this one
A bubble list makes yesterday’s plans disappear into a long scroll. This stack keeps each conversation’s context visible, preserves your draft while browsing, and lands a sent message directly in the selected thread. Initials replace profile photos. A coral date rail and a rolling message count give the history its own identity.
How it works
onLayoutmeasures the host. Cards scale by 4% and move 10 points per previous level; tablet width stops at 560 points.- One continuous shared
positiondrives the stack, dates, scale, opacity, and paint order. A velocity projection selects the nearest thread; an interruptible spring with explicit mass and overshoot clamping settles it. - Gesture Handler composes pan and tap exclusively. Tapping the active summary opens its message history; tapping outside, BACK, or Close thread closes it.
- Expansion uses one interruptible clock: the complete summary (including metadata and sent preview) fades and moves out over 180 ms before messages enter in chronological order, 50 ms apart. Closing reverses those phases, so the summary returns only after all messages disappear. Bubbles keep their measured rows with an 8-point gap. The history fills the card and scrolls with 16-point fades at both edges and padded endpoints.
- A real
TextInputretains the draft while browsing. Sending dismisses the keyboard, flies a coral bubble into the selected card in 480 ms, appends a local message, rolls the count, and displays a check for 800 ms.onSendfires once after landing; persistence belongs to the host. - Small containers and landscape use outer scrolling. Keyboard avoidance and a scroll-to-composer on keyboard appearance keep Send reachable. Reduced motion switches expansion immediately, with no flight, scaling, staggered displacement, or count rolling.
Install
npx expo install react-native-reanimated react-native-worklets react-native-gesture-handler expo-linear-gradient @shopify/react-native-skiaWrap the host in GestureHandlerRootView. On web, initialize CanvasKit before mounting this component using the Skia web setup for your Expo SDK. The component has no imports from the catalog or its demo. Icons: Lucide (ISC); see icons.ts and the kit’s license.
Usage
import { useRef } from 'react';
import { ThreadStackTimeline, type ThreadStackTimelineRef } from './thread-stack-timeline';
const timeline = useRef<ThreadStackTimelineRef>(null);
<ThreadStackTimeline
ref={timeline}
contact={{ name: 'Alex', initials: 'AL', color: '#FF7868' }}
threads={[{
id: 'weekend', date: '27 NOVEMBER',
summary: 'Let’s choose a weekend for our trip.',
messages: [{ id: 'hello', text: 'Does Saturday work?' }],
files: 2, time: '2:40 PM',
}]}
onSend={(text, index) => saveMessage(text, index)}
/>;
// Optional controls and personality knobs:
timeline.current?.goTo(0);
timeline.current?.send('Saturday works!');
<ThreadStackTimeline
contact={contact}
threads={threads}
sendDuration={420}
placeholder="Write a reply"
colors={{ accent: '#8FCDA8', outgoingText: '#102419' }}
fontFamily="YourRegularFont"
monoFontFamily="YourMonoFont"
/>;Supply host safe-area padding, including the top inset. Font loading belongs to the host. The demo uses Geist regular, medium, light, and mono, loaded once by the app. For a light theme, override the complete palette (background, surface, text, muted, border, accent, button, buttonText, bubble, outgoingText).
Props
| Prop | Type | Default | Purpose |
|---|---|---|---|
| contact | ThreadContact | Required | Name, initials, and avatar color |
| threads | TimelineThread[] | Required | Stable unique IDs, date, summary, messages, files, time, optional messageCount |
| initialIndex | number | 0 | Initial zero-based position |
| onSend | (text, index) => void | — | Called after landing; host persists the message |
| onIndexChange | (index) => void | — | Nearest index changes, not every frame |
| placeholder / sendLabel | string | Your message / Send | Composer copy |
| backLabel / onBack | string / () => void | BACK / — | Back action, or close when expanded |
| previousLabel / nextLabel / closeLabel | string | Previous / Next / Close thread | Accessible tap alternatives |
| messagesLabel / filesLabel / lastLabel | string | MESSAGES / FILES / LAST | Metadata copy |
| sentAnnouncement | string | Message sent | Screen-reader success announcement |
| colors | Partial<ThreadStackColors> | Dark palette | Full semantic color customization |
| fontFamily / mediumFontFamily / lightFontFamily / monoFontFamily | string | System | Typography roles |
| sendDuration | number | 480 | Flight duration; minimum 180 ms |
| style / testID | StyleProp<ViewStyle> / string | — / thread-stack-timeline | Integration and control IDs |
Ref: goTo(index), send(text), expand(open), setDraft(text), and reset(). Empty or concurrent sends are ignored. reset() clears demo-local messages and replays the entrance. Messages stay local until the host saves them; avoid echoing a locally sent message back into the same thread while this instance remains mounted. Treat the supplied thread IDs and ordering as stable during a gesture.
Demo timing
Entrance 0–650 ms; previous thread at 600 ms; return at 1,100 ms; simulated typing 1,455–2,225 ms; send at 2,320 ms, landing at 2,800 ms; check lasts 800 ms; expand at 3,400 ms; close at 5,600 ms; reset at 7,600 ms. The first cycle starts after 100 ms. Typing updates the real input through the ref without opening the keyboard. Timers are canceled on unmount.
Compatibility
Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1, Worklets 0.13.0, Gesture Handler 3.3, Skia 2.14.0, and LinearGradient 58.0.3.
Accessibility
Thread targets expose summary and date, a button role, and an accessibility tap. Previous and Next provide 48-point tap alternatives to dragging. Send and Close have labeled targets, and landing announces the configurable success text. Reduced motion replaces navigation movement with fades, switches expansion immediately, and sends without flight. Body text remains scalable; long expanded histories and compact hosts can scroll. Test IDs suffix the root with -thread-N, -input, -send, -back, -previous, -next, and -close.




