Animations

Swap out animation drivers per-platform or at runtime

Features

  • Animate any style prop with animation config per-prop.

  • Can animate across all states (media queries, hover, etc).

  • Multiple drivers you can swap out with type safety.

  • SSR safe mount animations.

  • Enter and exit animations with AnimatePresence.

Add animations to Tamagui with an animation driver. 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.

For this guide, we’ll use the React Native driver as an example, but you can choose from several Animation Drivers. For mount and unmount animations, see AnimatePresence.

Installation

yarn add @tamagui/animations-react-native

Then add it to your Tamagui config:

import { createAnimations } from '@tamagui/animations-react-native'
import { createTamagui } from 'tamagui'
export default createTamagui({
animations: createAnimations({
bouncy: {
damping: 10,
mass: 0.9,
stiffness: 100,
},
lazy: {
damping: 18,
stiffness: 50,
},
quick: {
damping: 20,
mass: 1.2,
stiffness: 250,
},
}),
// ...
})

Usage

The transition prop accepts the name of an animation you’ve configured. By default, animations will apply to all animatable styles, similar to setting all in a CSS transition.

Here’s an example animating hoverStyle:

The transition prop rules

If you add a transition prop, you must always keep the prop. If you need the animation to be disabled, pass false, null or even undefined if it suits you.

The spring-based animation drivers have expensive hooks that would degrade runtime performance if present on every component. As a workaround, the animation hooks are called conditionally based on whether the transition key is present in the props object.

So, <Square transition={isActive ? 'bouncy' : null} /> rather than <Square {...isActive && { transition: 'bouncy' }} />.

If you’d like to remove or add a transition prop after a component has already rendered, you’d have to change the key.

enterStyle

Setting enterStyle on a component tells it to start with those styles, then animate to the base styles after mount:

Granular animations

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

You can specify animations for specific properties using an object:

import { YStack } from 'tamagui'
export default () => (
<YStack transition={{ // 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 animation using a two-element array with the default in the first position:

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

Delay

You can add a delay before animations start using the array syntax with a delay property (in milliseconds):

import { Square, XStack } from 'tamagui'
export default () => (
<XStack gap="$2">
{[0, 1, 2, 3].map((i) => (
<Square key={i} transition={['bouncy', { delay: i * 100 }]} enterStyle={{ opacity: 0, scale: 0.5, y: 20 }} size={50} bg="$color10" />
))}
</XStack>
)

This creates a staggered animation effect where each square animates 100ms after the previous one. The delay works with all animation drivers and applies to enter, exit, and style change animations.

Enter/Exit Transitions

You can specify different animations for enter (mount) and exit (unmount) transitions. This is useful when you want elements to enter slowly but exit quickly, or vice versa:

import { AnimatePresence, View } from 'tamagui'
export default ({ show }) => (
<AnimatePresence>
{show && (
<View key="panel" transition={{ enter: 'lazy', exit: 'quick' }} enterStyle={{ opacity: 0, y: 20 }} exitStyle={{ opacity: 0, y: -20 }} />
)}
</AnimatePresence>
)

The enter and exit keys accept any animation name from your config. You can also combine them with a default for property changes that happen while the element is mounted:

// enter slowly, exit quickly, property changes use medium speed
<View transition={{ enter: 'lazy', exit: 'quick', default: 'bouncy' }} enterStyle={{ opacity: 0 }} exitStyle={{ opacity: 0 }} />

Or use enter/exit with the array syntax to include delay and per-property animations:

// enter with lazy, exit with quick, delay 200ms, x uses its own animation
<View transition={['bouncy', { enter: 'lazy', exit: 'quick', delay: 200, x: 'slow' }]} enterStyle={{ opacity: 0, x: -100 }} exitStyle={{ opacity: 0, x: 100 }} />

This works with all four animation drivers (CSS, React Native, Reanimated, Motion).

animateOnly

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

What to know when animating

Conditional animations and HMR

The animation hooks are heavy, which initially meant we either had to choose great performance or animations. We settled on a trade-off: we track if the transition 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 transition always set on the component props. You can disable it like so:

<View transition={condition ? 'animation-name' : null} />

Note that because of this constraint, you may see an error if you add the transition prop to a component in dev mode during HMR. Often just saving once more will remove the error, or reload the page if needed.

See Also