About this animation
A React Native animated character introduction gives a conversation screen a face and a short opening script. This component reveals lines character by character, changes facial expressions, and moves the companion into a ready-to-chat layout with a composer. Facial features are built from Views, Reanimated handles expression crossfades and layout transitions, and Expo Linear Gradient provides the background. Typing uses Unicode-aware character splitting so emoji remain intact, with timers cleared when playback changes or the component unmounts. Use it for an assistant introduction, a journaling welcome, a coaching interface, or conversational onboarding. Configure script lines and expressions, typing speed, colors, fonts, and screen copy. Connect face, script-end, and send callbacks, disable automatic start, or replay and skip the introduction through its ref.
When to use it
- Introduce a conversational assistant.
- Welcome users to a journaling screen.
- Give coaching onboarding a character.
- Transition a script into a chat composer.
A friendly character introduces itself line by line, changes expression while speaking, and settles into a ready-to-chat screen.
How it works
- The layout measures its own container with
onLayout; the face, text, controls, and chat composer all scale from that width and height. - A small JavaScript interval reveals Unicode characters with
Array.from, so emoji stay intact. A second interval crossfades the smile and an open mouth only while text is being typed. - Four expression layers share the same View-built facial geometry. Reanimated fades and scales those layers over 220 ms whenever the script changes expression.
- Reanimated shared values drive the face entrance, its move into the chat layout, the staggered copy, and two slow background blobs without rerendering React on every frame.
- The forwarded ref owns replay and skip behavior, while every timer and interval is tracked and cleared when a run is replaced or the component unmounts.
Install
npx expo install expo-linear-gradient react-native-reanimatedUsage
import CompanionIntro from './CompanionIntro';
export function WelcomeScreen() {
return <CompanionIntro />;
}Customize the character, script, timing, and callbacks:
import { useRef } from 'react';
import CompanionIntro, {
type CompanionIntroRef,
} from './CompanionIntro';
export function WelcomeScreen() {
const introRef = useRef<CompanionIntroRef>(null);
return (
<CompanionIntro
ref={introRef}
script={[
{ text: 'Good morning!', expression: 'sparkle', holdMs: 700 },
{ text: 'Ready when you are.', expression: 'happy' },
]}
typingSpeed={16}
title="Start a conversation"
faceColor="#C8EBD9"
accentColor="#426FF5"
onFacePress={() => showBreathingExercise()}
onSend={(message) => sendMessage(message)}
/>
);
}The forwarded ref exposes replay() and skip().
Props
| Prop | Type | Default | Description |
|---|---|---|---|
script | CompanionLine[] | Pip's three-line introduction | Lines and expressions used during the introduction. |
typingSpeed | number | 14 | Characters revealed per second. |
title | string | 'Talk with Pip' | Chat screen title. |
subtitle | string | "What's on your mind today?" | Chat screen subtitle. |
hint | string | 'Tap me to breathe' | Chat hint and accessible face label. |
privacyNote | string | 'Your conversations stay private' | Message above the composer. |
inputPlaceholder | string | 'Share your thoughts…' | Chat input placeholder. |
faceColor | string | '#BFE8D4' | Character fill color. |
featureColor | string | '#22313A' | Eye and mouth color. |
accentColor | string | '#3E7BFA' | Microphone button color. |
backgroundColors | [string, string, string] | ['#EAF4FF', '#FFE9DC', '#F3E9FF'] | Diagonal background gradient colors. |
fontFamily | string | System font | Font family for body text and the input. |
titleFontFamily | string | System font | Font family for the chat title. |
onScriptEnd | () => void | — | Called after the chat screen appears. |
onFacePress | () => void | — | Called when the character is pressed. |
onSend | (text: string) => void | — | Called when non-empty input is submitted. |
autoStart | boolean | true | Starts the entrance and script on mount. |
style | StyleProp<ViewStyle> | — | Outer-container style. |
testID | string | — | Test identifier for the outer container. |
Compatibility
- Expo SDK 57
- React Native 0.86
- Reanimated 4.5
- iOS: supported, not yet verified on a device
- Android: supported, not yet verified on a device
- Web: supported, not yet verified in a browser
- Works in Expo Go because its native dependencies are included in the SDK
Accessibility
The face is exposed as a labeled button, and each changing phrase uses a polite live region. The sound, send, and voice controls have explicit button labels. With reduced motion enabled, lines appear whole instead of typing, ambient breathing and press bounces stop, and transitions use short fades.
FAQ
- Can I change the character script and expressions?
- Yes. Supply CompanionLine entries with text, expression, and optional holdMs. typingSpeed controls characters per second, and the ref exposes replay and skip.
- Does the introduction connect to an AI service automatically?
- No. onSend receives non-empty submitted text, and onFacePress and onScriptEnd expose other actions. You connect those callbacks to your own application services.
- How does the character introduction handle Reduce Motion?
- Lines appear whole instead of typing, ambient breathing and press bounces stop, and transitions use short fades. The changing phrase remains a polite live-region update.




