Skip to content
Native Animation
Bundle Builder Box animation preview

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

npx nativeanimation add bundle-builder-box
Skia

Bundle Builder Box

A three-bottle bundle builder with product flights into a box and configurable bottle data, labels, accent color, 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 three-bottle bundle builder with product flights into a box and configurable bottle data, labels, accent color, and fonts.

Why this one

Dashed slots become solid as they fill, and a synchronized lid, drawn bow, rolling price, and savings seal make the discount feel earned. The demo uses five coherent local product cutouts and the Tidewell brand from the catalog kit.

How it works

  1. onLayout measures the container. The box sits at 12–44% of its height, the horizontal shelf at 48–72%, and the summary and action at 76–96%.
  2. Three stable slots reserve products immediately. Tap a shelf card or its 44-point plus area to add to the first empty slot; duplicate variants are ignored.
  3. A vertical pan previews the same bottle outside the scroll clipping region. A release inside the box selects it; a release outside springs it back. Horizontal movement remains available to the shelf.
  4. Each bottle has a continuous flight progress. Its horizontal motion is linear, its vertical motion follows an arc, and its rotation peaks at 8 degrees before a short { damping: 58, stiffness: 320 } (effective mass 4) spring seats it.
  5. One closure progress drives the perspective lid, bottle visibility, bow path trim, price transition, savings seal, original-price strike, and fine sparkles. It starts after the final flight and lasts 500 ms.
  6. Tap an open-box bottle to remove it. Tap the closed box to remove the last occupied slot and reopen it. reset() empties the box; onAddToBag hands the three selected products to your checkout.

Install

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

Use GestureHandlerRootView at the app root and the Reanimated/worklets configuration appropriate to Expo SDK 58. For web, load CanvasKit before importing this component, using Skia's WithSkiaWeb or your existing deferred Skia loader.

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

Usage

import { GestureHandlerRootView } from 'react-native-gesture-handler';
import BundleBuilderBox from './bundle-builder-box';

const bottles = [
  { id: 'ink', name: 'Ink', price: 58, image: require('./ink.png') },
  { id: 'sage', name: 'Sage', price: 58, image: require('./sage.png') },
  { id: 'clay', name: 'Clay', price: 58, image: require('./clay.png') },
];

export function BundleScreen() {
  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <BundleBuilderBox bottles={bottles} onAddToBag={addBundleToCart} />
    </GestureHandlerRootView>
  );
}

Customization and external control:

const builder = useRef<BundleBuilderBoxRef>(null);

<BundleBuilderBox
  ref={builder}
  bottles={bottles}
  bundlePrice={142}
  accentColor="#0F766E"
  kraftColor="#D6C3A5"
  colorScheme="light"
  fontFamily="YourInterfaceFont"
  monoFontFamily="YourNumberFont"
  flightDuration={320}
  onSelectionChange={updateSelection}
  onAddToBag={addBundleToCart}
/>;

builder.current?.add('sage');
builder.current?.remove('sage');
builder.current?.reset();

Props

PropTypeDefaultDescription
bottlesBundleBottle[]RequiredStable ID, variant name, image source, and dollar price. At least three unique products are recommended.
bundlePricenumber142Complete bundle price.
buttonLabelstringAdd trio to bagCheckout button text and accessibility label.
shelfLabelstringChoose your colorsShelf instruction.
bundleLabelstringTrioSummary bundle name.
savingsLabelstringSave 18%Circular seal copy; customize with your price.
accentColorstring#0F766EActive slots, ribbon, seal, and button.
kraftColorstring#D6C3A5Box material.
fontFamilystringSystemInterface font.
monoFontFamilystringSystemSlot numerals and tabular price font.
colorSchemelight | dark | autoautoSurface and text appearance. Set the enclosing screen background to match.
flightDurationnumber320Flight duration in milliseconds before seating.
onSelectionChange(bottles: BundleBottle[]) => void—Selected products in slot order.
onAddToBag(bottles: BundleBottle[]) => void—Complete bundle checkout callback.
styleStyleProp<ViewStyle>—Root style; the root defaults to flex: 1.
testIDstring—Root test identifier.

The ref exposes add(id), remove(id), and reset(). Autoplay is confined to the demo: additions at 1.0, 1.5, and 2.0 seconds; completion by 2.9 seconds; reset at 6 seconds; a new color combination every 7.4 seconds.

Compatibility

Expo SDK 58; React Native 0.88.0; Reanimated 4.7.1; worklets 0.13.0; Skia 2.14.0. No new native dependency is added to this project.

Accessibility

All product cards offer button roles, variant and price labels, selected states, and screen-reader activation actions. Selected bottles offer removal actions. The closed box offers a labeled reopen/remove action; checkout is disabled until three slots are occupied. The selection count is a polite live region and the total has a dollar amount label.

Reduced motion replaces bottle flights with a 180 ms slot fade, disables drag in favor of tap, removes the bow and sparkles, removes rotation and bounce, and uses short fades for completion. Gesture taps use Gesture Handler rather than nested React Native press handlers; checkout pressure uses its own shared value.