Confetti

Free · MITReact Native · Expo

A celebration burst: paper pieces launch from a point or rain from the top on closed-form physics (air drag, gravity, sway), spinning and turning over as they fall; one burst per trigger, a static sparkle under reduced motion.

View as Markdown
Try it on your phone
Scan to open this demo in your phone's browser and use it with your fingers.

Installation

$ npx shadcn@latest add https://goodui.dev/native/r/confetti.json

Then run npx expo install --fix so react-native-reanimated, react-native-svg match your Expo SDK. Files land in components/goodui-native/confetti.

Usage

import { useState } from "react";
import { View } from "react-native";
import { Button } from "@/components/goodui-native/button/button";
import { Confetti } from "@/components/goodui-native/confetti/confetti";

export function GoalReached() {
  const [celebrations, setCelebrations] = useState(0);
  return (
    <View>
      <Button onPress={() => setCelebrations((c) => c + 1)}>Log 10,000 steps</Button>
      <Confetti trigger={celebrations} announcement="Goal reached" />
    </View>
  );
}

When to use

Use it for
  • A real milestone the person worked for: a goal reached, a first sale, a streak, a course finished.
  • The end of a flow worth celebrating, together with a clear confirmation (success-check or the screen's own result).
Reach for something else
  • Routine confirmations such as saving a form: let the button show success.
  • As the only confirmation of an action: pair it with a visible result.
  • A loading or progress indicator: use progress-ring or the button's own pending state.

Examples

Burst out of a button through a ref

const buttonRef = useRef<View>(null);
const confetti = useRef<ConfettiHandle>(null);

<View ref={buttonRef} collapsable={false}>
  <Button onPress={() => confetti.current?.fire()}>Claim reward</Button>
</View>
<Confetti ref={confetti} origin={buttonRef} count={36} />

Inside a card instead of the whole screen

<View style={{ overflow: "hidden" }}>
  {card}
  <Confetti trigger={done} fullScreen={false} origin="center" power={0.6} />
</View>

API reference

PropTypeDefault
trigger
Fires one burst every time this changes to a new truthy value after mount (a counter, a timestamp, `true`).
number | string | boolean | null—
ref
Imperative handle: `ref.current?.fire()`.
Ref<ConfettiHandle>—
origin
`top` rains across the width; a point or an element ref bursts upward from there. Default `top`.
ConfettiOrigin"top"
count
Pieces per burst, 8 to 120. Default 44: enough to read as a celebration, light enough for low-end phones.
number44
spread
Half-angle of a point burst in degrees, around straight up. Default 55.
number55
power
Launch speed multiplier for point bursts. Default 1.
number1
colors
Piece colors. Defaults to the theme accent and status colors.
string[]—
duration
Milliseconds a burst lives. Default 2400.
number2400
fullScreen
Render above everything through the portal (default), or inside the nearest positioned parent.
booleantrue
haptic
Cue as a burst fires. `false` turns it off. Default `celebrate`.
HapticName | false"celebrate"
announcement
Read by screen readers when a burst fires, for example "Goal reached". The pieces themselves are hidden.
string—
onComplete
Called when a burst has finished.
() => void—

Motion and haptics

  • Point bursts launch in a cone around straight up (spread), denser in the middle; 'top' rains pieces across the width with staggered starts.
  • Every piece follows a closed-form path with linear air drag and gravity plus a growing sideways sway, spins, and turns over (scaleY follows a cosine) like paper; it fades in the last 28 % of its life.
  • Up to three bursts overlap; a fourth replaces the oldest.
  • Reduced motion: no pieces travel. Seven four-point sparkles at the origin fade in, hold and fade out (about 1.1 s).
  • Haptic: celebrate as each burst fires (haptic prop; false turns it off). Never repeated during the burst.
  • One linear clock per burst on the UI thread; each piece is a plain Animated.View whose style is computed from that clock, no per-frame React state.
  • Default 44 pieces; count is clamped to 8 to 120. Keep it under about 60 for low-end Android.
  • Nothing is mounted between bursts.

Accessibility

  • The pieces are hidden from screen readers and never take touches (pointerEvents none).
  • Pass `announcement` to have a burst read out, for example 'Goal reached'.

Notes for AI

  • trigger fires on every change to a new truthy value after mount (a counter works best); it does not fire on the first render.
  • For a burst from an element pass a ref to a View with collapsable={false} as origin; it is measured when the burst fires.
  • fullScreen (default) renders through the ThemeProvider portal above everything. With fullScreen={false} it fills the nearest parent; give that parent overflow hidden.
  • Default colors are the accent and status colors from the theme; pass colors to use your own tokens.