Skip to content
Native Animation
Popout Hero Card animation preview

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

npx nativeanimation add popout-hero-card

Popout Hero Card

A layered hero card that tilts beyond its frame and becomes a stats card, with configurable artwork, stats, palette, 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 layered hero card that tilts beyond its frame and becomes a stats card, with configurable artwork, stats, palette, and fonts.

Why this one

The hero is an independent transparent image, rather than a flattened card illustration. Finger tilt moves it 1.8 times as far as the card, with a subtle 1.04 foreground scale. The same card surface brightens into a gear showcase with rolling statistics, preserving continuity between the two screens.

How it works

  1. onLayout measures the host. Card width is capped; a scrolling layout keeps the controls reachable in small containers and landscape.
  2. A horizontal-first Gesture Handler pan feeds one shared tilt vector. Perspective stays at 1,000, rotation is capped at ±10°, and an explicit mass-1, critically damped spring returns to rest.
  3. A separate hero viewport extends above and to the right of the card while clipping the image's lower edge. The hero and its shadow share a three-second floating phase.
  4. start() and back() animate one reversible transition progress. It drives card color, cover opacity, hero flight, and stats entry. Counting uses a separate shared value so the state transition stays quick.
  5. Each decimal column rolls only at its own carry. Separators remain in normal flow with narrower widths; all columns finish exactly on their integer digit.
  6. The demo owns the timers: tilt right at 350 ms, left at 1,200 ms, settle at 2,150 ms, start at 2,900 ms, hold stats for three seconds after the transition, and return at 6,500 ms. Any touch cancels the loop.

The primary action is a clean 60 pt pill with a 30 pt corner radius and a soft 3 pt shadow derived from the accent colors. Its independent press shared value moves it down 2 pt and scales it to 0.97, then springs back. A subtle mass-1 scale spring plays on cover entry; reduced motion disables both scale and displacement feedback. The button contour never deforms.

Install

npx expo install react-native-reanimated react-native-worklets react-native-gesture-handler expo-image expo-linear-gradient @shopify/react-native-skia

Wrap the host application in GestureHandlerRootView and configure Reanimated/worklets for your Expo SDK. On web, initialize Skia/CanvasKit before importing/rendering the component, using your application's existing Skia loading boundary. Vertical strokes scroll the layout; begin a tilt with a horizontal or diagonal drag. Fonts are supplied by the host; the component does not load fonts or import any demo assets.

Icons: Lucide (ISC); the copied paths live in icons.ts.

Usage

import { useRef } from 'react';
import { PopoutHeroCard, type PopoutHeroCardRef } from './popout-hero-card';

const scenes = [
  { source: require('./scene-a.jpg'), label: 'Floating island' },
  { source: require('./scene-b.jpg'), label: 'Underwater village' },
  { source: require('./scene-c.jpg'), label: 'Ocean race' },
  { source: require('./scene-d.jpg'), label: 'Cloud harbor' },
];

export function SeasonScreen() {
  const card = useRef<PopoutHeroCardRef>(null);
  return (
    <PopoutHeroCard
      ref={card}
      hero={require('./hero.webp')}
      gear={require('./gear.webp')}
      scenes={scenes}
      stats={[
        { id: 'wins', value: 84, label: 'wins' },
        { id: 'points', value: 1756, label: 'points' },
      ]}
      seasonLabel="SEASON 04"
    />
  );
}

A softer pose and a custom theme:

<PopoutHeroCard
  hero={hero}
  gear={gear}
  scenes={scenes}
  stats={stats}
  tiltDegrees={7}
  parallax={1.5}
  duration={500}
  palette={{
    background: ['#25354D', '#39475B'],
    card: '#304B72',
    statsCard: '#EDF3F8',
    ink: '#142C42',
    muted: '#526A7D',
    accent: '#86DDD0',
    accentInk: '#173E39',
    ring: '#C1DBDA',
  }}
  fontFamily="YourBodyFont"
  headingFontFamily="YourBoldFont"
  serifFontFamily="YourEditorialFont"
  monoFontFamily="YourNumberFont"
/>

Props

PropTypeDefaultPurpose
hero, gearImageSourcePropTypeRequiredTransparent character and gear artwork
scenesreadonly HeroScene[]RequiredUp to four images with description labels
statsreadonly SeasonStat[]RequiredUp to four nonnegative integer metrics
seasonLabelstringSEASON 01Background season identifier
brandLabelstringEmptyOptional upper-left wordmark; no logo
titlestringReady to explore?Cover heading
startLabelstringPRESS TO STARTPrimary action
statsTitlestringSeason statsEditorial heading and entry announcement
gearLabelstringMost used gearFeatured item caption
playLabelstringView season statsHero/play accessibility label
backLabelstringBack to seasonReturn action
heroAccessibilityLabelstringSeason heroImage description
gearAccessibilityLabelstringFeatured gearImage description
palettePartial<PopoutHeroPalette>Blue/violet, pink accent, light stats surfaceEvery surface, text, border, ring, shadow, and tile color
fontFamily, headingFontFamily, serifFontFamily, monoFontFamilystringSystemHost-loaded fonts; provide a bold heading font
tiltDegreesnumber10Maximum rotation, limited to 0–10°
parallaxnumber1.8Foreground movement multiplier, limited to 0–3
durationnumber580Cover/stats transition in milliseconds
maxWidthnumber360Card width cap
formatStat(value: number) => stringUS groupingInteger formatting with ASCII digits and separators
onStart, onBack() => void—State transition callbacks on the JS thread
onInteraction() => void—Stops host autoplay when touched
style, testIDStyleProp<ViewStyle>, stringRoot flex 1, popout-hero-cardEmbedding and stable test IDs

Ref methods: start(), back(), and tilt(x, y) (normalized −1…1). They are reversible and do not disable interaction during animation.

Compatibility

Designed for Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1, worklets 0.13.0, Gesture Handler 3.3, and Skia 2.14.0. Dependencies are generated from the imports in piece.json. The installed stack is compatible with Expo Go; no new native modules are introduced.

The component uses no safe-area insets: its host screen owns them. Content can scroll in a 300×400 region or landscape. Card art is limited to a 320 pt hero (332.8 pt at its peak scale), keeping the supplied 1,024 px artwork above 3×; scene thumbnails are smaller than 160 pt.

Accessibility

All actions have button roles, configurable labels, an activate accessibility action, and stable -start, -play, -hero, and -back test IDs. Play, tapping the hero, and the primary action provide alternatives to dragging. Hidden screen content is excluded from accessibility traversal. State changes announce the configured heading or season identifier; metrics expose their final values as single accessible labels.

Reduced motion removes tilt, floating, foreground scaling, flight, and gear rotation. The two states crossfade over 200 ms and metrics show their final digits without rolling. Touch targets are at least 48×48 pt, and press feedback has its own shared value. No real game branding or reference artwork is included.