All components
0:00/0:00

Recorded on iPhone. Willow fireworks over a night-sky weather screen, each burst lighting up the clouds around it.

Celebration

Fireworks

Small, quiet fireworks for a purchase, a streak or a finished task, that never get in the way of your content.

Features

  • One line gives you the whole show
  • Rockets climb with a comet tail, then burst into a ball of fine, glittering sparks
  • Or switch to willow: golden strands that droop and fall with long tails
  • Keep bursts to the edges or the top, so your text stays readable
  • Bursts space themselves out and never pile up
  • Your colours, with a glow for dark screens and solid sparks for light ones
  • Slow it down, or drive the speed from a scroll or a drag
  • Each burst tells you where it went off, so the rest of your screen can light up with it
  • Costs nothing while nothing is on screen

Built with

  • Skia
  • Reanimated

Early access

Checkout opens soon

TesseraUI is in early access. Leave your email and we'll let you know the moment you can buy, before anyone else.

No spam. One email when checkout opens.

Or get it with Pro

Installation

1Install dependencies
npx expo install @shopify/react-native-skia react-native-reanimated react-native-worklets
2Run a development build (Expo Go can't load Skia)
npx expo run:ios   # or: npx expo run:android

Usage

It fills its parent and keeps launching until you tell it to stop.

import { Fireworks } from '@/components/fireworks';

export function Milestone() {
  return <Fireworks height={240} />;
}

Examples

Around your text

Bursts stay on the left and right edges, and anything inside draws on top.

<Fireworks height={240} area="edges" maxBursts={2}>
  <Text style={styles.title}>100 day streak</Text>
</Fireworks>

Willow

Strands that droop and fall with long tails. Gold suits them best.

<Fireworks
  shape="willow"
  burstRadius="18%"
  colors={['#FFD58A', '#FFC266', '#FFE8C2']}
/>

One show when something happens

Turn off the loop and fire a show from your own button.

function Purchase() {
  const fireworks = useRef<FireworksRef>(null);

  return (
    <View>
      <Fireworks ref={fireworks} height={240} loop={false} />

      <View style={styles.controls}>
        <Button
          title="Buy"
          onPress={() => fireworks.current?.celebrate()}
        />
      </View>
    </View>
  );
}

Props

PropTypeDefaultDescription
width, heightpoints or "%""100%"The box the fireworks fill.
colorsstring[]soft whites, blush and lilacEach burst takes one at random.
area"sky" | "full" | "center" | "edges" | "top""sky"Where bursts go. edges keeps the middle clear for text. You can also pass your own regions.
shape"peony" | "willow""peony"peony is a ball of grains that fades where it is. willow droops and falls with long tails.
blend"glow" | "solid""glow"glow adds light, for dark backgrounds. solid paints the sparks, for light ones.
maxBurstsnumber3Most bursts on screen at once. Rockets wait for room.
speednumber | SharedValue<number>1How fast bursts play. 0.5 is slow motion.
loopbooleantrueKeep launching. Off, it plays one show and stops.

Advanced

Everything most apps never need to touch.

Pass only the fields you want to change, as rocket={{ trail: 0.5 }} or spark={{ size: 1.4 }}.

FieldDefaultDescription
rocket.climb0.6How fast rockets climb, 0–1.
rocket.trail0.25Length of the trail, 0–1. 0 hides rockets: bursts only.
spark.count700Grains per burst at intensity 0.5.
spark.size2.2Grain size in points. Small ones glitter, large ones look like confetti.
spark.life2200Milliseconds a grain lives.
spark.gravity0.15How much grains fall, 0–1.
spark.drag0.6How quickly a burst opens, 0–1. 0 is a slow bloom, 1 a sudden pop.
spark.twinkle0.7Share of grains that glitter as the burst fades, 0–1.

MethodDescription
celebrate()Fires one show. During a show, its bursts join in.
burst(at?)One burst now, at { x, y } in points or percentages. The centre by default.
stop()Fires nothing more. Sparks already in the air finish.
clear()Removes everything at once.

PropDefaultDescription
intensity0.5Size, density and brightness in one knob, 0–1.
bursts4Bursts in one show.
duration3000Milliseconds the launches of one show are spread over.
colorMix0Share of each burst's sparks in a second colour, 0–1.
burstRadius"42%"How far a burst spreads: points, or a share of the box's shorter side.
blur0Points of softness on every grain.
depth0Spreads bursts near and far, 0–1. Far ones are smaller, dimmer and softer.
autoPlaytruePlays one show as soon as it has a size, when not looping.
playing—false freezes everything in place. Also takes a shared value.
onBurst—Called as each burst goes off, with its index and { x, y, color }: where in the box, and which of your colors.
onComplete—Called when a show's last spark fades. Not called while looping.
pointerEvents"box-none"Touches pass through to what's behind.
accessibilityLabel"Fireworks"Announced when a show plays.

  • Content you put inside draws above the fireworks, so it works as the background of a card or a whole screen.
  • Taps through burst() always go off, even past maxBursts.
  • Use onBurst to light up what's around the fireworks, like a sky or a card, in each burst's colour.
  • Your own area regions can reach past the edges, from -1 to 2, so some bursts are cut off by the frame.
  • Nothing runs while it's idle, in the background or paused.
  • Works on iOS and Android from the same code. It doesn't run on web.

  • The sparks are hidden from screen readers, which hear accessibilityLabel once when a show plays. Content inside keeps its own labels.
  • With Reduce Motion on, each burst becomes a soft glow of its colour that fades in and out, with no rockets or flying sparks.

How it works

Rockets, trails and sparks all live in one preallocated array on the UI thread, stepped by a Reanimated frame callback, so nothing allocates or re-renders while it plays. Each burst is a ball of grains that pops most of the way out at once and then glides, born white-hot and cooling to its colour; a willow uses fewer, longer-lived sparks under stronger gravity, and leaves a trail of fading dots as they fall. Every particle is one sprite in a single Skia atlas, drawn in one call; blur and depth just pick a softer sprite from it.

Next componentRolling Carousel