About this animation
A product finish picker with a liquid color reveal and configurable color options, finishes, product copy, and fonts.
How it works
- A transparent product image supplied through
colorsis placed in a responsive frame above the controls, with transparent studio margins cropped out. - A single interruptible
waveshared value clips the next color from the base upward and drives its white wave edge. - A Gesture Handler pan gives the bottle a restrained false-3D tilt; release springs back with the gesture velocity.
- Matte, Gloss, and Metal change restrained vertical reflections confined to the straight bottle body; the price updates from the selected state.
- The store header introduces Tidewell, Halo, and the current product price. Pre-rendered color names cross-fade and slide with interruptible springs, without replacing text mid-frame.
- The panel is a light glass surface with a sliding swatch ring, a finish segmented control, engraving, and a bag success state.
Install
npx expo install expo-blur expo-image expo-linear-gradient react-native-gesture-handler react-native-reanimated react-native-workletsSupply your own product images through the required, non-empty colors prop; demo artwork is not included in the delivered files.
Usage
import { FinishConfigurator, type FinishConfiguratorColor } from './finish-configurator';
const colors: FinishConfiguratorColor[] = [
{ name: 'Ink', value: '#172A3A', source: require('./assets/product-ink.webp') },
{ name: 'Sage', value: '#8FAF9A', source: require('./assets/product-sage.webp') },
];
<FinishConfigurator colors={colors} /><FinishConfigurator
colors={colors}
initialColor={1}
initialFinish={2}
initialEngraving
fontFamily="Geist_400Regular"
serifFontFamily="InstrumentSerif_400Regular"
monoFontFamily="GeistMono_400Regular"
onAddToBag={() => cart.add('tidewell-halo')}
/>Load custom fonts in the host app before rendering. Without font props, the component uses the system font. The catalog demo supplies its shared fonts; the component does not import the catalog theme.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
colors | FinishConfiguratorColor[] | Required | Non-empty product color list; each option supplies name, value (swatch color), and source (an expo-image image source or local require()). |
finishes | FinishConfiguratorFinish[] | DEFAULT_FINISHES | Finish labels and price adjustments. |
brandName | string | 'Tidewell' | Product brand. |
title | string | 'Halo bottle' | Product title. |
productDetail | string | 'HALO / 24 OZ' | Product metadata below the color name. |
price | number | 58 | Base price in dollars. |
engravingPrice | number | 6 | Engraving surcharge in dollars. |
initialColor | number | 0 | Initial color swatch. |
initialFinish | number | 0 | Initial finish option. |
initialEngraving | boolean | false | Initial engraving state. |
onColorChange | (index: number) => void | undefined | Called after a color is selected. |
onFinishChange | (index: number) => void | undefined | Called after a finish is selected. |
onAddToBag | () => void | undefined | Called when the bag action is completed. |
autoplay | boolean | false | Lets the demo show its opening sequence. |
fontFamily | string | undefined | Interface font supplied by the host app. Defaults to the system font. |
serifFontFamily | string | fontFamily | Editorial font supplied by the host app. Defaults to fontFamily. |
monoFontFamily | string | fontFamily | Monospaced font supplied by the host app. Defaults to fontFamily. |
style | StyleProp<ViewStyle> | undefined | Optional style for the component root. |
accessibilityLabel | string | 'Product finish configurator' | Screen-reader label for the configurator. |
testID | string | undefined | Optional test identifier. |
duration | number | 650 | Color-wave transition duration in milliseconds. Spring physics and reduced-motion timing are unchanged. |
Compatibility
Built for Expo SDK 58, React Native 0.88, Reanimated 4, Worklets 0.13, and Gesture Handler 3. It is designed for iOS, Android, and web.
Accessibility
Color, finish, engraving, and bag controls expose labels, roles, and selected/checked state. Reduced motion removes the bottle turn and liquid wave and commits color changes immediately while preserving the state transition.
Icons: Lucide (ISC), with the selected paths copied to icons.ts.
Why this one
The familiar product configurator pattern becomes a material moment: color fills the bottle like liquid instead of cross-fading, the wave carries a thin line of light, and Metal lets the finish follow the user's finger. The result communicates what changed without turning the UI into decoration.




