Skip to content
Native Animation
Pricing Toggle Plans animation preview

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

npx nativeanimation add pricing-toggle-plans
SkiaGlass

Pricing Toggle Plans

A pricing selector with rolling prices and expanding disclosures, with configurable plans, 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 pricing selector with rolling prices and expanding disclosures, with configurable plans, labels, accent color, and fonts.

Why this one

Only changing price columns roll. Unchanged digits stay still, decimal places expand into the layout, annual billing reveals with height and opacity, and the savings badge celebrates the annual switch. The recommendation border is a real gradient stroke; the demo headline applies its gradient inside the glyphs.

How it works

  1. A single billing shared value drives the thumb, every price column, units, and disclosure lines.
  2. Tap and horizontal pan use Gesture Handler exclusively. Release projects velocity and settles with an interruptible spring.
  3. Whole digits align at the decimal boundary. Each changed column moves its two glyphs one line apart and fades by distance; at rest exactly one glyph is visible.
  4. A separate selection value drives card scale. Selection borders fade over 200 ms, while the recommended border stays on.
  5. Skia draws one conic border and all three Lucide checks per card. One border revolution takes eight seconds.
  6. Pressure uses independent shared values, preventing native release events from canceling billing or selection.

Install

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

Set up Reanimated and a GestureHandlerRootView in the host app. On web, load CanvasKit before mounting this component (for example, Skia's WithSkiaWeb); the catalog must register this demo with its existing withSkia loader. No packages or native configuration were changed for this piece.

Usage

import PricingTogglePlans from './pricing-toggle-plans';

<PricingTogglePlans onContinue={(plan, billing) => openCheckout(plan, billing)} />;
const pricing = useRef<PricingTogglePlansRef>(null);

<PricingTogglePlans
  ref={pricing}
  plans={myPlans}
  initialPlan="pro"
  header={<MyProductHeader />}
  accentColor="#7C5CFF"
  springDamping={26}
  springStiffness={300}
  borderDuration={8000}
  fontFamily="MyInterfaceFont"
  mediumFontFamily="MyMediumFont"
  monoFontFamily="MyMonoFont"
/>;

pricing.current?.setBilling('yearly');
pricing.current?.selectPlan('team');
pricing.current?.reset();

The host supplies loaded fonts. The component falls back to platform text fonts. The Nimbus demo uses Geist, Geist Mono, and Instrument Serif, plus local font assets for Skia's headline.

Props

PropTypeDefaultPurpose
plansreadonly PricingPlan[]Basic / Plus / GroupThree plans with monthly and annual display prices, three benefits, disclosure, recommendation, and action labels
initialPlanstringRecommended planInitially selected identifier
headerReactNodeGeneric headingBrand and headline content
fontFamilystringSystemInterface typeface
mediumFontFamilystringSystemHeadings and buttons
monoFontFamilystringSystemTabular price typeface
accentColorstring#7C5CFFBorder and environmental accent
backgroundColorstring#0B0B12Screen background
springDampingnumber56Billing and selection damping. Values below the damping needed for ζ ≥ 0.8 are raised to that minimum.
springStiffnessnumber300Billing and selection stiffness
borderDurationnumber8000Revolution duration in milliseconds
savingsLabelstringSave 20%Annual savings badge
recommendedLabelstringMost popularRecommendation badge
footerLabelstringCancel anytimeReassurance copy
accessibilityLabelstringYearly billingSwitch label
onBillingChange(period) => void—Billing selection callback
onPlanChange(id) => void—Plan selection callback
onContinue(id, period) => void—Checkout callback
styleStyleProp<ViewStyle>—Root overrides
testIDstring—Root identifier

PricingPlan provides id, name, monthly, yearly, features, optional yearlyUnit, yearlyBill, recommended, and actionLabel. Prices exclude $; Free denotes a free plan. Annual prices can represent a monthly equivalent or an annual total, with the appropriate unit and disclosure. Keep benefits short enough for the side column.

Demo timing and layout

The demo switches to annual at 850 ms and selects Team at 2700 ms, repeating every 4800 ms. The first transition is the wow moment. The header begins at 5.5% of container height, switch at 27%, cards at 35%, and action at 88%. Card heights are 14.5% / 18.5% / 14.5%, with 8-point gaps. Cards occupy 92% of container width; the switch occupies 70%. The action is 56 points tall. Intended portrait sizes: 390×844 and 320×694; longer localized content or a larger text scale requires a host scroll container and more height.

Compatibility

Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1, worklets 0.13.0, and Skia 2.14.0. Uses installed Expo Go modules. Android blur targets the environmental backdrop with BlurTargetView; Android below API 31 uses the supported translucent fallback.

Accessibility

The billing control exposes a switch role, checked state, value, and an activate action. Plans expose radio roles and checked states with names, prices, and benefits. The action has a button role and reflects the selected plan. Touch targets are at least 44 points at the intended sizes. Reduced motion replaces rolling with fades, removes pressure and selection scaling, freezes the border and ambient drift, and disables the savings bounce. Screen readers do not traverse decorative canvases as controls.

Icons: Lucide (ISC). Demo identity: Nimbus from the project kit. No external images or reference branding.

Spring damping floors use Reanimated 4.7.1’s default mass of 4 when the configuration does not set a mass. Stiffness and mass remain unchanged.