Skip to content
Native Animation
Receipt Split Bill animation preview

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

npx nativeanimation add receipt-split-bill

Receipt Split Bill

A bill splitter with drag-to-assign dishes and rolling shares, with configurable diners, dishes, labels, 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 bill splitter with drag-to-assign dishes and rolling shares, with configurable diners, dishes, labels, and fonts.

Why this one

Bill calculators usually ask you to fill out a form. This receipt lets people divide the actual dishes, see who shares each one, and watch their amounts change together. The torn paper edge, fine paper grain, sage setting, and spring-driven assignment flight make the accounting tangible.

How it works

  1. onLayout measures the container, caps the composition at 440 points, and places the receipt in a scrollable surface. Rows retain 70-point spacing so their 48-point assignment controls remain separate.
  2. A long-press pan lifts a diner while shared values track its position and highlighted dish on the UI thread.
  3. Releasing commits the assignment to React state and springs a small avatar into that dish's sharing row.
  4. Each dish divides its integer cents in diner order. Tax and tip follow each diner's subtotal; leftover cents go to the first diner. Shares always sum to the receipt total.
  5. Each numeric column uses a continuous cent value and rolls only at its own carry boundary. Static currency symbols stay aligned with tabular digits.
  6. Each changed share sends a colored amount chip from the receipt total to its diner over 480 ms, staggered by 35 ms. Flights and digits stay on the UI thread.
  7. The demo starts Sam's wine assignment at 600 ms and repeats every 5.2 seconds. Reduced motion applies assignments immediately and fades amounts.

Install

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

Use an Expo SDK 58 project with the Reanimated/worklets Babel setup. Mount the screen beneath GestureHandlerRootView. For web, initialize CanvasKit before mounting this component (Expo's Skia web setup, or the host application's existing lazy Skia boundary). Fonts are supplied by the host; the component has no catalog imports. Icons: Lucide (ISC); the copied paths are in icons.ts.

Usage

import { ReceiptSplitBill } from './receipt-split-bill';

const people = [
  { id: 'a', name: 'Alex', color: '#F97316' },
  { id: 'b', name: 'Blair', color: '#0EA5E9' },
];
const dishes = [{ id: 'soup', name: 'Seasonal soup', cents: 1100, icon: 'soup' as const }];

<ReceiptSplitBill people={people} dishes={dishes} taxCents={96} />;

// Customized typography, initial ownership, and request integration:
<ReceiptSplitBill
  people={people}
  dishes={dishes}
  initialAssignments={{ soup: ['a'] }}
  restaurantName="Dinner club"
  fontFamily="YourInterfaceFont"
  titleFontFamily="YourHeadingFont"
  monoFontFamily="YourMonoFont"
  springDamping={30}
  colorScheme="auto"
  onRequest={(cents) => sendPaymentRequests(cents)}
/>;

ref.assign(personId, dishId) demonstrates an assignment using the same drop handler as the gesture. ref.reset() restores the initial tip and assignments. The first person pays the bill; the request excludes their share. Supply positive integer cent values, unique IDs, and at least one person. The intended mobile composition is up to six dishes and four diners; the demo uses four dishes to leave room for the receipt and diner totals. Initial props seed uncontrolled state; call reset() to apply changed initial assignments.

Props

PropTypeDefaultPurpose
peopleBillPerson[]RequiredDiners with stable IDs, names, and hex colors; payer first.
dishesBillDish[]RequiredStable IDs, names, integer cents, and kit icon names.
initialAssignmentsRecord<string, string[]>{}Initial sharing; omitted dishes belong to everyone.
restaurantNamestringDinner togetherHeading beside the kit flame tile.
tableLabelstringTable 12Receipt metadata.
titlestringYour billReceipt heading.
taxCentsnumber0Fixed tax in integer cents.
initialTipnumber18Tip percentage; controls offer 15, 18, and 20.
fontFamilystringSystemInterface font.
titleFontFamilystringSystemEditorial heading font.
monoFontFamilystringSystemFont for amounts and metadata.
colorSchemelight / dark / autolightWarm light or dark receipt.
springDampingnumber30Flight and amount spring damping. Values below 30 are raised to 30 with explicit mass 1 and stiffness 300.
labelsPartial<ReceiptLabels>English labelsOverrides item, subtotal, tax, tip, total, request, success, and instruction text.
formatAmount(cents: number) => stringUS dollarsFormats receipt values, shares, request amounts, and flights. Custom formats use static digits to preserve their numbering conventions.
onChange(shares, totalCents) => void—Balanced amounts after a change.
onRequest(cents) => void—Request action callback.
styleStyleProp<ViewStyle>—Root overrides.
testIDstring—Root test ID.

Compatibility

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 included in this project's Expo Go SDK. Supports iOS, Android, and web. No dependencies were added.

Accessibility

Diners, dishes, tip choices, mini-avatars, and the request action have button roles and labels. Tap a diner, then a dish, to assign without dragging. Tap a dish without a selected diner to split evenly. Tap a mini-avatar to remove that diner; a dish with no assigned diners remains charged to the first diner until reassigned. Assignment and tip controls have 48-point targets; diner avatars are 52 points. Stable test ID suffixes identify dishes, diners, tip choices, and the request button. Reduced motion removes flights and digit travel and uses a short amount fade. Amount windows expose the final currency label.

Review notes

The receipt has a cream paper surface, fine deterministic grain, a Skia serrated edge, and two shadow layers. The setting moves from cream to sage; colored initials identify diners without photographs. The dinner total is 28-point tabular type and the request amount is 22-point type. Supply Geist, Instrument Serif, and Geist Mono through the font props as the demo does.

The composition caps its width at 440 points on tablets. At 320×568, in landscape, and in a 300×400 slot, vertical scrolling keeps the receipt, diner controls, and request reachable rather than shrinking touch targets. At 390×844 the four-dish demo fits in the viewport. The header reserves the top-right viewer close area. All amount chips are actual moving text within colored pills; there is no sweep layered over the receipt text. Reduced motion removes flights and digit travel. The first autoplay assignment starts at 600 ms and changes the shares within the first three seconds.

No generated artwork is required.