About this animation
An empty-library scene that lights up and reveals a note, with configurable copy, fonts, accent colors, and ambient intensity.
Why this one
The illustration participates in the action: a moth finds the light, the book opens, and the lamp’s warm light becomes the first note. One persistent surface morphs its position, size, corner radius, and gradient into the note background, with no flying-card handoff. A circular daylight reveal begins at the lamp. Reset traces the same geometry in reverse, and the lamp can also be switched independently without creating a note.
How it works
onLayoutmeasures the containing view. A vector Skia scene fills 80% of its width, with a height limit to keep controls separate on smaller phones.- A UI-thread clock drives a four-second, one-degree lamp sway and the moth's Lissajous flight.
- A single interruptible spring drives note progress. Its default settings are
damping: 28,stiffness: 240, andmass: 1(damping ratio ≈ 0.90), with overshoot clamped. The duration knob scales stiffness and damping together to preserve that ratio. CTA feedback goes from 0.97 to 1 without overshoot. - The same Skia rounded surface starts as a warm radial light field at the shade and becomes the measured note-row background. Progress 0–0.2 grows the light; 0.2–0.85 morphs its bounds, radius, and gradient. A circle reveals daylight from the lamp without fading entire screens.
- The scene and empty-state copy fade out over progress 0.18–0.48 while moving exactly 12 points down. Note text and controls fade in only at 0.85–1, after the scene has left and the material has reached its destination. The surface stays mounted throughout.
- Reset springs that same progress back to zero, reversing all geometry and visibility intervals. Reduced motion keeps the note surface at its destination and uses sequential local fades, with no travel, ambient motion, or expanding reveal. Separate illumination exists only for the lamp-only interaction.
Install
npx expo install @shopify/react-native-skia expo-linear-gradient react-native-reanimated react-native-workletsCopy this folder without demo/. For web, load CanvasKit before mounting the component using your application's Skia web initialization boundary. There are no remote assets or additional font dependencies; font families are supplied by the host.
Usage
import EmptyStateScene from './empty-state-scene';
export function Library() {
return <EmptyStateScene onCreateNote={() => createDraft()} />;
}import { useRef } from 'react';
import { EmptyStateScene, type EmptyStateSceneRef } from './empty-state-scene';
export function CustomLibrary() {
const scene = useRef<EmptyStateSceneRef>(null);
return (
<EmptyStateScene
ref={scene}
title="Make room for an idea"
ctaLabel="Create a note"
accentColors={['#FBBF24', '#F97316']}
ambientIntensity={0.6}
transitionDuration={1100}
onImport={() => openFilePicker()}
onNotePress={() => openDraft()}
/>
);
}The import action triggers the same illustrated first-content transition. onImport delegates actual file selection to the host; this component does not read files or persist notes. Bottom tabs provide product context and are decorative. The search field is a disabled visual placeholder.
Props
| Prop | Type | Default | Purpose |
|---|---|---|---|
heading | string | Library | Screen heading. |
title | string | Nothing here yet | Empty headline. |
body | string | Your notes, highlights and books will glow here. | Supporting copy. |
ctaLabel | string | Write your first note | Primary action and accessible label. |
importLabel | string | Import from files | Secondary action and accessible label. |
noteTitle | string | Untitled note | First content title. |
noteMetadata | string | Just now | Row metadata. |
headerAdornment | ReactNode | — | Optional host brand tile and wordmark. |
fontFamily | string | System | UI font. |
titleFontFamily | string | System | Editorial font. |
nightColor | string | #14110D | Night background. |
dayColor | string | #FBF7EE | Day background. |
accentColors | readonly [string, string] | ['#FBBF24', '#F97316'] | Action gradient. |
ambientIntensity | number | 1 | Lamp sway and moth orbit amplitude. |
transitionDuration | number | 1100 | Response timescale in ms; scales the spring while preserving damping ratio. |
onCreateNote | () => void | — | New draft request. |
onImport | () => void | — | File selection request. |
onNotePress | () => void | — | First row selection. |
style | StyleProp<ViewStyle> | — | Container override. |
testID | string | — | Root identifier. |
Ref methods: createNote(), reset(), and toggleLamp(). Autoplay is isolated in the demo: create at 1.1 seconds, hold the settled note for three seconds, reset at 5.2 seconds, repeat every 6.6 seconds.
Compatibility
Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1, and Skia 2.14.0. Uses the installed Expo Go modules; no additional native module is introduced. Supports iOS, Android, and web.
Accessibility
Buttons expose roles and labels; the lamp exposes its checked state as a switch. Tap targets are at least 44 points. Hidden empty/content controls leave the accessibility tree. Reduced motion disables the moth, lamp sway, cover rotation, surface travel, and expanding reveal, using sequential local fades instead. No gesture is required. Pass localized action copy through props. The demo uses American English and reserves the upper-right viewer close area.
Icons: Lucide (ISC). The demo's Lumen tile uses the catalog kit's lamp glyph and amber-to-orange gradient.





