Skip to content
Native Animation
Split Sheet Library animation preview

This preview uses fictional demo content. See the props below for the customization options available in this component.

npx nativeanimation add split-sheet-library
Carousel

Split Sheet Library

A reading home that unfolds into a sheet-based library with shared cards, with configurable shelves, library data, colors, and fonts.

$9 for this animation

Add to your project

Copy the component and its assets into your app with the Native Animation CLI, or paste the agent prompt into Claude Code, Codex or Cursor once the MCP server is connected.

About this animation

A reading home that unfolds into a sheet-based library with shared cards, with configurable shelves, library data, colors, and fonts.

How it works

  1. A Gesture Handler ScrollView supplies one UI-thread scroll offset. The sheet belongs to its content; the header recedes with half-speed parallax, an 8-degree cover tilt, and a shrinking banner.
  2. One spring drives the route. It changes the header spacer from 61.5% of the editorial height to 18% of the container height, preserving the current scroll offset when Library opens.
  3. The story card is mounted once. Its two layout slots are measured inside the sheet; its vertical position interpolates between those exact coordinates as the sheet moves.
  4. One continuous tab position drives the sliding content and a variable-width, stretching active pill. Book pressure has its own spring, so release cannot cancel navigation.
  5. The banner snaps on touch and advances every four seconds. Any physical interaction pauses automatic advancement. Demo sequencing uses the public handle and stops on touch.

Install

npx expo install @expo/vector-icons expo-image expo-linear-gradient react-native-gesture-handler react-native-reanimated react-native-worklets

Wrap your app in GestureHandlerRootView. Load your preferred fonts in the host. Copy the component, types.ts, and index.ts into your project. Demo images and demo font imports are separate from the reusable component.

Usage

import { useRef } from 'react';
import SplitSheetLibrary, { type SplitSheetLibraryHandle } from './split-sheet-library';

const libraryRef = useRef<SplitSheetLibraryHandle>(null);

<SplitSheetLibrary
  ref={libraryRef}
  user={{ initials: 'MJ' }}
  rows={[
    { id: 'romance', title: 'Because you like romance', books },
    { id: 'fantasy', title: 'Because you like fantasy', books: fantasyBooks },
  ]}
  banners={[{
    id: 'new-release', title: 'Your next chapter', subtitle: 'New stories this week',
    art: require('./assets/books.jpg'), color: '#1B6BE3',
  }]}
  story={{ title: 'Find your next favorite story', cta: 'Browse stories', art: readerArt }}
  library={{ tabs: ['Current Reads', 'Archive', 'Reading Lists'], rows, featured: [] }}
  onOpenBook={(id) => openBook(id)}
/>;

libraryRef.current?.openLibrary();
libraryRef.current?.setTab(1);
libraryRef.current?.goHome();
libraryRef.current?.scrollTo(240, 1200);

Each book has id, title, chip, and cover, plus optional titleColor, titleFontFamily, titleAlign, and titleSize for its cover typography. titleSize is the nominal size at a 108-point cover width and scales down with smaller covers. The title is drawn over text-free local art. library.featured adds author, formatted views, formatted chapters, and tags. Use strings to control number formatting and units. Supply library.tabRows to show different shelves for each tab.

<SplitSheetLibrary
  {...content}
  colorScheme="auto"
  colors={{ activeDot: '#1B6BE3', story: '#202128', storyText: '#FFFFFF' }}
  labels={{ compactTitle: 'Selected for you', back: 'Return to recommendations' }}
  springDamping={26}
  springStiffness={240}
  bannerInterval={6000}
  fontFamily="YourRegularFont"
  titleFontFamily="YourBoldFont"
  monoFontFamily="YourNumberFont"
  topInset={insets.top}
  bottomInset={insets.bottom}
/>

Props

PropTypeDefaultPurpose
user{ initials: string }{ initials: 'AB' }Avatar initials
rowsSheetRow[]RequiredHome shelves; romance first, fantasy second
bannersSheetBanner[]RequiredLocal artwork, text, and color for each announcement
storySheetStoryRequiredShared story card art, title, and CTA
librarySheetLibraryRequiredTabs, shelves, optional per-tab shelves, recommendations
labelsPartial<SheetLabels>English labelsAll structural text and accessibility strings
colorsPartial<SheetColors>Black header, white sheet, blue accentComplete surface, ink, border, overlay, and control palette
colorScheme'light' | 'dark' | 'auto''light'Sheet and control theme; header stays dark
initialRoute'home' | 'library''home'Starting route
fontFamilystringSystemInterface text
titleFontFamilystringSystemTitles and buttons
monoFontFamilystringSystemChapter and view counts
motion'auto' | 'reduced' | 'full''auto'Motion preference
topInset, bottomInsetnumber0Real safe areas, supplied by the full-screen host
springDamping, springStiffnessnumber22, 210Route physics; mass is explicitly 1
bannerIntervalnumber4000Automatic carousel interval; 0 disables
onOpenBook(id: string) => void—Book action
onRouteChange(route) => void—Target route changed
onTabChange(index: number) => void—Target tab changed
onPremium, onBrowse() => void—Main CTA actions
onProfile, onPreferences, onGift, onMore() => void—Toolbar actions
showSearchbooleanfalseOptional Library search field
onSearchChange(text: string) => void—Host-owned search/filtering
onInteraction() => void—Stop host autoplay on physical interaction
autoplayboolean—Host demo contract; sequencing lives in demo/
accessibilityLabelstring'Story library'Root description
testIDstring'split-sheet-library'Stable root/control prefix
styleStyleProp<ViewStyle>—Flexible measured container

The handle exposes openLibrary(), goHome(), setTab(index), scrollTo(y, duration = 1200), and nextBanner().

Compatibility

Designed for Expo SDK 58, React Native 0.88, Reanimated 4.7, Worklets 0.13, and Gesture Handler 3.3. Uses Expo Go-compatible modules and supports iOS, Android, and web. Requires the New Architecture used by Reanimated 4. No native folders, custom native modules, or remote image requests are required.

The layout measures its container and centers at a maximum width of 430 points. Short containers retain a scrollable editorial canvas rather than compressing labels or controls. Home and Library intentionally scroll vertically; shelves and banners scroll horizontally. The full-screen host supplies real safe-area insets. Do not add a second safe-area wrapper that duplicates those same insets.

Accessibility

Buttons expose roles, labels, and selected states; covers announce their titles. Keyboard Enter and Space activate the same button action on web. Back provides a tap alternative to the handle drag. Default reduced motion follows the system: no perspective, parallax, automatic banner loop, or shared-element flight; route changes use a 220 ms fade. Supply labels, cover titles, and the root description for localization. Text retains system font scaling.

Stable identifiers include the root, -sheet, -banner, -book-<row>-<index>, -open-library, -back, and -tab-<index>.

Why this one

The white sheet is the route transition itself. It climbs over the Home instead of replacing the entire screen, while a single story card travels between its two measured slots. The same motion values power touch, imperative control, and demo playback.

Icons: Feather from @expo/vector-icons, the Lucide kit's supported predecessor (MIT). Demo artwork is supplied locally by the host and is not part of the reusable component's file list.