wwwwwwwwwwwwwwwwwww

Animations

Swap out animation drivers per-platform or at runtime

Show code

Features

  • Pluggable drivers, fully typed and easy to swap.

  • Support hover, press, and focus animations.

  • Supports enter animation on mount.

  • Supports exit animation with AnimatePresence.

Add animations to Tamagui with an animation driver. See the configuration docs for more on how to set it up, and how to set up different animation drivers per-platform.

Animation drivers are designed to be swappable, so you can use lightweight CSS animations or other web-focused animation libraries on the web, while using larger but more advanced libraries like reanimated on native - all without having to change a line outside of configuration.

Installation

CSS

The @tamagui/animations-css package works with the tamagui compiler and runtime to give you simple ways to share typed animations across all your components.

To install it add to your package.json:

yarn add @tamagui/animations-css

Then add it to your config:

import { createAnimations } from '@tamagui/animations-css'
import { createTamagui } from 'tamagui'
export default createTamagui({
animations: createAnimations({
fast: 'ease-in 150ms',
medium: 'ease-in 300ms',
slow: 'ease-in 450ms',
}),
// ...
})

At runtime, the plugin does very little except to set the transition property in CSS. At compile-time, the compiler do the same, ensuring you get all the benefits of prop removal and view flattening even when using animations.

React Native Animated

React Native's Animated library  is the animation library that comes built into React Native and React Native Web.

To install it add to your package.json:

yarn add @tamagui/animations-react-native

Then add it to your config:

import { createAnimations } from '@tamagui/animations-react-native'
import { createTamagui } from 'tamagui'
export default createTamagui({
animations: createAnimations({
fast: {
damping: 20,
mass: 1.2,
stiffness: 250,
},
medium: {
damping: 10,
mass: 0.9,
stiffness: 100,
},
slow: {
damping: 20,
stiffness: 60,
},
}),
// ...
})

Reanimated

Reanimated  is an animation library that targets React Native and React Native Web. It runs off-thread animations, and provides nicer syntax and hooks. It weighs over 100Kb minified though, which makes it not always useful for the web.

If using the starter, it configures excludeReactNativeWebExports to omit FlatList and other views reanimated expects, change that config to fix errors.

To install it add to your package.json. We use Moti  as the driver, which is a very light layer above reanimated:

yarn add @tamagui/animations-moti react-native-reanimated

Then add it to your config:

Show more
import { createAnimations } from '@tamagui/animations-moti'
import { createTamagui } from 'tamagui'
export default createTamagui({
animations: createAnimations({
fast: {
type: 'spring',
damping: 20,
mass: 1.2,
stiffness: 250,
},
medium: {
type: 'spring',
damping: 10,
mass: 0.9,
stiffness: 100,
},
slow: {
type: 'spring',
damping: 20,
stiffness: 60,
},
}),
// ...
})

At runtime, this plugin parses animatable style properties and hands them over to reanimated off-thread, using worklets. It doesn't do anything at compile time - reanimated must run via JS.

wwwwwwwwwwwwwwwwwww

Note the keys match between CSS and reanimated, so you can swap them out.

Save bundle size by removing Animated and AnimatedFlatList from react-native-web. On Next.js, use the excludeReactNativeWebExports` option config option.

Usage

The animation can now accept slow as a value. By default, animations will apply to all animatable styles, a lot like setting all in a CSS transition. Let's test this by setting hoverStyle:

Here's an example animating hoverStyle:

Show code

A note on usage

As of beta 150, we're adding a rule for an edge case: any animated component that is removing or adding an animation at any time, keep the animation prop defined.

So, <Square animation={isActive ? 'bouncy' : null} /> rather than <Square {...isActive && { animation: 'bouncy' }} />. If you do absolutely need the latter, you must change the key at the same time.

Why? The animation hooks are heavy and would otherwise have to run on every component. Plus enter/exit animations further add renders. Tamagui is designed for performance in the happy path. If every component you render had to run all the animation logic, a lot of the performance of Tamagui would be nullified.

enterStyle

Setting enterStyle styles on any component tell it to start with those styles, and immediately transition into their regular styles:

Show code

Granular animations

The animation prop accepts a string or a more complex object to customize animations per-property.

The basic object we'll call an AnimationConfig, looks like this:

import { YStack } from 'tamagui'
export default () => (
<YStack animation={{ // only x and y will apply animations x: 'bouncy', y: { type: 'bouncy', overshootClamping: true, }, }} />
)

Note that values can either map to AnimationKey as a string, or to { type: AnimationKey, ...configuration }

You can set a default value using a two-arity array with the default in the first position:

import { YStack } from 'tamagui'
export default () => (
<YStack animation={[ // all attributes get "bouncy" 'bouncy', // these are customized { y: 'slow', scale: { type: 'fast', repeat: 2, }, }, ]} />
)

animateOnly

The animateOnly prop will limit your animation config to certain keys. It accepts an array of strings that correspond to style property names.

AnimatePresence and exitStyle

AnimatePresence animates direct children before they unmount. It's inspired by and forked from Framer Motion , but works with any animation in Tamagui.

To use with @tamagui/core, install and import @tamagui/animate-presence. It's already bundled and exported from tamagui.

You can use it simply with enterStyle + exitStyle:

import { AnimatePresence, Stack } from 'tamagui'
export const MyComponent = ({ isVisible }) => (
<AnimatePresence>
{isVisible && (
<Stack key="my-square" animation="bouncy" backgroundColor="green" size={50} enterStyle={{ opacity: 0, y: 10, scale: 0.9, }} exitStyle={{ opacity: 0, y: -10, scale: 0.9 }} />
)}
</AnimatePresence>
)

Note you don't even need to set opacity on the base style. Tamagui knows to normalize styles like opacity and scale to 1 (and y to 0) if it's not defined on the base styles but is defined on enterStyle or exitStyle.

Wrap one or more tamagui components with AnimatePresence. This component will animate on enter and exit.

Animated child components must each have a unique key prop so AnimatePresence can track their presence in the tree.

Advanced AnimatePresence Usage

Show code

AnimatePresence takes two optional special properties, enterVariant and exitVariant, which allow you to override enterStyle and exitStyle conditionally. These properties would be impossible to change otherwise. This is a simple example, hit "Show Code" for the carousel demo above to see a more practical usage of it:

Show more
import { AnimatePresence, Square, styled } from 'tamagui'
const AnimatableSquare = styled(Square, {
variants: {
fromRight: {
true: {
x: 1000,
},
},
fromLeft: {
true: {
x: -1000,
},
},
},
})
export const MyComponent = ({ isVisible }) => (
<AnimatePresence enterVariant="fromLeft" exitVariant="fromRight">
{isVisible && (
<AnimatableSquare key="my-square" animation="bounce" backgroundColor="green" size={50} exitStyle={{ opacity: 0, }} />
)}
</AnimatePresence>
)

Note that it will always find the true variant and use that. In the future, Tamagui may be able to add support for more flexible variants here.

What to know when animating

Allowing for spring based animations alongside every other feature of Tamagui has been a challenge. In order to preserve performance while still allowing for this, we've had to make a couple concessions, which are luckily minor.

Conditional animations and HMR

The animation hooks are heavy, which initially meant we either had to choose great performance or animations. We've settled on a trade-off. We track if the animation prop is set, and if so, we enable the hook. If it is ever set, even just once, then the hooks will continue to run for the remainder of the component lifecycle. This means if you ever plan to animate a component you should keep animation always set on the component props. You can disable it like so:

<Stack animation={condition ? 'animation-name' : undefined} />

Note that because of this constraint, you also will see an error if you add the animation prop to a component in dev mode during an HMR. Simple reloading the page or triggering another HMR will fix it.

AnimatePresence and optimization

If you're using a non-CSS driver + AnimatePresence + have the compiler turned on + are using variant animations like in the carousel demo above (you don't have to worry about enterStyle / exitStyle, the compiler is smart enough to recognize them and avoid problems), you'll want to add the disableOptimization boolean prop to your component to ensure it doesn't flatten during optimization and break your animations.