All components
0:00/0:00

Recorded on iPhone. Labubu, Expo, Discord, PlayStation and Pizza Hut stickers scattered on a board, peeled up, dragged and stuck back down.

Gesture

Holographic Sticker

Die-cut foil stickers that catch rainbow light as you tilt the phone and peel up off the screen when you hold them.

Features

  • Stickers shine with rainbow foil as you tilt the phone
  • Hold one and it peels up from under your finger, with a light tap of feedback
  • Drag it around, twist it with two fingers, or pinch to zoom
  • Let go and it sticks back down, starting from the edge you lifted
  • Drop in a list of stickers and the board scatters them for you
  • Runs on iOS and Android from the same code

Built with

  • Skia mesh and shaders
  • Reanimated
  • Gesture Handler
  • expo-haptics
  • expo-image

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 react-native-gesture-handler expo-haptics expo-image
2Wrap your app in GestureHandlerRootView
import { GestureHandlerRootView } from 'react-native-gesture-handler';

export default function RootLayout() {
  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <Stack />
    </GestureHandlerRootView>
  );
}

Without it nothing errors, but the stickers never respond to a touch.

3Run a development build (Expo Go can't load Skia)
npx expo run:ios   # or: npx expo run:android

Usage

import { StickerBoard } from '@/components/holographic-sticker';

export function Stickers() {
  return (
    <StickerBoard
      stickers={[
        { key: 'heart', source: require('./heart.png'), width: 100 },
        { key: 'pizza', source: require('./pizza.png'), width: 140 },
      ]}
    />
  );
}

The board fills its parent, tosses each sticker on at a random spot and angle, and brings the one you pick up to the front. Try it on a real device: a simulator can't be tilted, so the foil never shows.

Examples

A board in a card, with fixed positions

Give the board a size and place each sticker yourself.

<StickerBoard
  style={{ height: 320, borderRadius: 24, backgroundColor: '#f4f1ea' }}
  stickers={[
    {
      key: 'heart',
      source: require('./heart.png'),
      width: 110,
      x: 24,
      y: 40,
      rotation: -8,
    },
    {
      key: 'pizza',
      source: require('./pizza.png'),
      width: 140,
      x: 150,
      y: 120,
      rotation: 12,
    },
  ]}
  onPickUp={(key) => console.log('picked up', key)}
/>

A single sticker

For one sticker, such as a badge, use HolographicSticker inside a FoilLightProvider.

import {
  FoilLightProvider,
  HolographicSticker,
} from '@/components/holographic-sticker';

<FoilLightProvider>
  <HolographicSticker source={require('./badge.png')} width={160} />
</FoilLightProvider>

Props

StickerBoard:

PropTypeDefaultDescription
stickersStickerBoardItem[]RequiredThe stickers to lay out.
styleStyleProp<ViewStyle>—Fills its parent by default. Give it a size to embed it.
onPickUp(key: string) => void—Called as a sticker is picked up. The board brings it to the front.

Each sticker:

FieldTypeDefaultDescription
keystringRequiredUnique on the board. Passed to onPickUp.
sourcenumberRequiredA transparent die-cut PNG, as a require().
widthnumberRequiredPoints. The height follows from the image.
x, ynumberrandomPoints from the board's top-left. Leave them out and the sticker lands at random.
rotationnumberrandomDegrees, clockwise. Leave it out for a random tilt of up to 25° either way.

Advanced

Everything most apps never need to touch.

Use it inside one FoilLightProvider, which takes only children.

PropTypeDefaultDescription
sourcenumberRequiredA transparent die-cut PNG. Its outline is the sticker's shape.
widthnumberRequiredPoints. The height follows from the image.
initialRotationnumber0Degrees it starts turned, clockwise. Read once; twisting turns it from there.
styleStyleProp<ViewStyle>—For example position and left/top.
onPickUp() => void—Called as the peel starts.

There are no presets yet. Every number that shapes the look, such as the peel timing, bend, ripple, shadow and foil, is a named constant in constants.ts, with its unit and what it changes.

  • Hold for 150 ms and the sticker peels up from under your finger. Press near the middle and it peels outward in a circle instead.
  • Drag while holding to move it. Pull 90 points and it peels the rest of the way at once.
  • Twist with a second finger while holding to turn it.
  • Pinch any time to zoom, from 0.4× to 4×.
  • Let go and it sticks back down from the edge you peeled. Grab it again partway and it lifts from that same edge.

  • Put one FoilLightProvider above every HolographicSticker on screen. It runs the single tilt sensor they all share. StickerBoard brings its own.
  • Random spots are rolled once per key, so stickers never reshuffle on a re-render and stay on the board when it resizes.
  • Dragged stickers can leave the board. Add overflow: 'hidden' to its style to clip them.
  • The foil fades in only as the phone tilts. Once the phone lies flat, nothing redraws.

  • A sticker sets no accessibility label of its own. If one carries meaning, such as a badge, wrap it in a view with accessible and an accessibilityLabel.
  • With Reduce Motion on, the wobble after the peel is turned off. The peel itself still plays, since it shows the sticker has been picked up.

How it works

Each sticker is drawn with Skia as a 16 × 16 mesh. Holding it starts a bend at your finger that sweeps to the far side, and each point rises the further the bend has passed it, with perspective so the lifted part grows toward you. The same mesh, blurred and flattened, is the shadow. One shader rides the bend with a bright crest and a dark trough so the crease reads as a crease; another adds the rainbow foil, clipped to the art's outline and driven by the phone's rotation sensor on the UI thread.

Next componentGeometric Carousel