react-flip-toolkit
A lightweight FLIP animation library for React, delivering smooth layout transitions without cloning or crossfading.
Repository Health
Technical Analysis
react-flip-toolkit is a React library for the FLIP (First, Last, Invert, Play) animation technique, letting you animate elements as they change position, size, or opacity across re-renders without manually calculating transforms or resorting to the cloning-and-crossfading tricks some competing libraries use. Wrap animated children in a Flipper component keyed by a flipKey, then wrap the elements themselves in Flipped components tagged with a matching flipId; the library measures each element before and after the DOM update and animates the difference using spring physics.
Under the hood, the React-facing package is a thin wrapper around flip-toolkit, a framework-agnostic core (published separately) that does the actual position measurement, matrix math, and animation scheduling, so the same FLIP engine that powers React here can also be used directly in vanilla JS or other frameworks. Spring-driven animation is handled by a forked, trimmed-down copy of Rebound, the physics engine originally built for Facebook’s Paper app, chosen over the project’s earlier dependency on WobbleJS for better performance with many simultaneously animated elements.
What You Get
- Flipper / Flipped components - Wrap animated regions in a
Flipperkeyed byflipKey, and tag individual elements withFlippedand aflipIdto have position, scale, and opacity changes animated automatically. - Spring-based animation engine - Animations run on a forked, size-reduced copy of Facebook’s Rebound spring physics library, with named presets (
noWobble,gentle,wobbly, etc.) or fully custom spring configs. - Nested scale-transform correction - Automatically compensates child elements’ scale when a parent element’s size is animated, so text and nested content don’t visibly distort during the transition.
- Enter/exit and stagger support -
onAppear/onExitcallbacks and astaggerprop let you sequence list-item animations (e.g. cards entering or leaving a list) without hand-rolled delay logic. - Framework-agnostic core (
flip-toolkit) - The underlying measurement/animation engine is published as its own package, usable outside React by library authors targeting other frameworks.
Common Use Cases
- Animating a list as items are shuffled, reordered, added, or removed
- Expanding a card or thumbnail into a full-screen detail view
- Transitioning shared elements between route changes in a single-page app
- Building drag-free UI transitions such as accordions, tabs, and modals that need to feel physically continuous
Under The Hood
Architecture
The codebase splits cleanly into two packages: flip-toolkit (packages/flip-toolkit/src), a framework-agnostic engine, and react-flip-toolkit (packages/react-flip-toolkit/src), a thin React binding layer. The React layer’s Flipper component (Flipper/index.tsx) is a class component that captures pre-update DOM measurements in getSnapshotBeforeUpdate and triggers flip-toolkit’s onFlipKeyUpdate in componentDidUpdate, delegating all actual measurement and animation logic to the core package rather than reimplementing it. Flipped (Flipped/index.tsx) is a thin declarative wrapper that serializes flip configuration into data-flip-config/data-flip-id DOM attributes rather than passing props through React context directly, letting the core engine read configuration straight off the DOM during the FLIP measurement pass. Inside flip-toolkit, the flip/ directory implements the FLIP lifecycle as discrete steps — getFlippedElementPositions (before/after measurement), animateFlippedElements (matrix math and spring driving), and animateUnflippedElements (inverse-scale correction for un-flipped children) — each isolated in its own subdirectory with its own types, and a forked, trimmed copy of Rebound (forked-rebound/) supplies the spring runtime consumed by Spring/index.ts. The DOM-attribute serialization is the seam between the two packages, so that data contract, not the class hierarchy, is what governs compatibility between react-flip-toolkit and flip-toolkit versions.
Tech Stack
The project is a Yarn workspaces monorepo (root package.json lists packages/*) built with TypeScript, compiled per-package via microbundle to produce CJS, ESM, and UMD bundles plus type declarations. The React package targets React 16+ as a peer dependency and pulls in prop-types and its sibling flip-toolkit package as runtime dependencies; the core package’s only runtime dependency is rematrix for 2D/3D matrix transform math, alongside its embedded fork of Facebook’s Rebound for spring physics rather than an external spring library. Testing runs on Jest for the React package and Mocha/Chai plus Parcel-served DOM tests for the core package’s browser-dependent behavior that Jest’s jsdom can’t exercise. Linting uses ESLint with @typescript-eslint, formatting via Prettier, and CI runs through Travis.
Code Quality
Both packages carry real test coverage rather than token stubs: the React package uses Jest in Flipper.test.tsx and Flipped.test.tsx, and the core package pairs plain Mocha/Chai unit tests (utilities/tweenProp.test.ts, springSettings/index.test.ts, animateFlippedElements/__tests__/matrix.test.ts and rectInViewport.test.ts) with browser-dependent *.domtest.js files for behavior that requires real layout (getFlippedElementPositions.domtest.js, animateUnflippedElements.domtest.js). Types are organized via dedicated types.ts files per module rather than one global types file, and tsc --noEmit is wired as a check-types script. Error handling is explicit and minimal — functions like Flipper.addFlipped throw descriptive errors ('No flipId provided', 'no element provided') on misuse rather than silently no-op-ing. Naming is consistent and descriptive (getFlippedElementPositionsBeforeUpdate, onFlipKeyUpdate), and ESLint plus Prettier are configured project-wide, though a handful of @ts-ignore/@ts-expect-error escape hatches appear around React lifecycle typing quirks.
API Design
The public API is deliberately minimal and declarative: consumers only need two components, Flipper (given a flipKey prop that changes to trigger animation) and Flipped (given a flipId prop to match elements across renders), to get position, scale, and opacity animated with no manual measurement code — the README’s quick-start examples produce a working animation in well under 15 lines. Naming is consistent and intention-revealing throughout (flipKey, flipId, onAppear, onExit, delayUntil, staggerConfig), and TypeScript types (FlipperProps, FlippedProps) give editors autocomplete and inline documentation without a trip to the README. The tradeoff for that simplicity is a large surface of optional advanced props (portalKey, handleEnterUpdateDelete, decisionData, custom spring objects) that aren’t discoverable without reading deep into the README’s advanced-props sections, and the library communicates via DOM data-attributes under the hood — invisible to consumers, but something that can complicate debugging in browser devtools. Overall it favors an approachable common path with escape hatches available but not always self-documenting.