# Animating Elements
## What This Covers
How to animate insertion, removal, and layout changes of elements. Read this when the task involves:
- Adding entrance, exit, or shared-layout transitions to UI
- Choosing between spring physics (`spring(...)`) and time-based easing (`tween`)
- Coordinating CSS transitions with the same easing as JS animations
- Imperative animation loops via `requestAnimationFrame`
Import animation APIs from `remix/ui/animation`. For the smaller set of animation helpers that show up alongside other mixins, see `mixins-styling-events.md`.
## Animation Mixins
### `animateEntrance(config)`
Animates an element when inserted. Config specifies the **starting** style the element animates **from**:
```tsx
```
### `animateExit(config)`
Animates an element when removed. Config specifies the **ending** style the element animates **to**. The element stays in the DOM until the animation completes:
```tsx
{
isVisible && (
)
}
```
### `animateLayout(config?)`
Animates layout changes (position/size) using FLIP-style transforms:
```tsx
{
items.map((item) => (
))
}
```
Options: `duration` (default 200ms), `easing` (default spring snappy), `size` (default true — include scale projection for size changes).
### Combining mixins
```tsx
```
### Shared-layout swap
```tsx
*': { gridArea: '1 / 1' } })}>
{stateA ? (
) : (
)}
```
## Spring API
Physics-based spring animation. Returns a `SpringIterator` with `duration`, `easing`, and `toString()` for CSS.
### Presets
| Preset | Bounce | Duration | Character |
| -------- | ------ | -------- | --------------------------- |
| `smooth` | -0.3 | 400ms | Overdamped, no overshoot |
| `snappy` | 0 | 200ms | Critically damped, quick |
| `bouncy` | 0.3 | 400ms | Underdamped, visible bounce |
```tsx
spring('bouncy')
spring('snappy')
spring('smooth')
spring('bouncy', { duration: 300 }) // override duration
```
### Custom spring
```tsx
spring({ duration: 500, bounce: 0.3 })
spring({ duration: 500, bounce: 0.3, velocity: 2 }) // continue momentum from gesture
```
### Spread into animation mixins
Spreading a spring gives both `duration` and `easing`:
```tsx
animateEntrance({ opacity: 0, ...spring('bouncy') })
```
### CSS transitions
The iterator stringifies to `"550ms linear(...)"`:
```tsx
css({ transition: `width ${spring('bouncy')}` })
```
Or use the `spring.transition()` helper for multiple properties:
```tsx
css({ transition: spring.transition('width', 'bouncy') })
css({ transition: spring.transition(['left', 'top'], 'snappy') })
```
### Web Animations API
```tsx
element.animate(keyframes, { ...spring('bouncy') })
```
### JS iteration
The iterator yields position values from 0 to 1, one per frame:
```tsx
for (let t of spring('bouncy')) {
let x = from + (to - from) * t
updateSomething(x)
await nextFrame()
}
```
## Tween API
Generator-based tween for animating values over time with cubic bezier easing. Prefer animation mixins or CSS transitions with `spring` for most UI work. Use `tween` for imperative `requestAnimationFrame` loops, canvas/WebGL, or non-CSS properties.
```tsx
import { tween, easings } from 'remix/ui/animation'
let animation = tween({
from: 0,
to: 100,
duration: 300,
curve: easings.easeOut,
})
animation.next() // initialize
function tick(timestamp: number) {
if (handle.signal.aborted) return
let { value, done } = animation.next(timestamp)
element.style.transform = `translateX(${value}px)`
if (!done) requestAnimationFrame(tick)
}
requestAnimationFrame(tick)
```
Built-in easings: `easings.linear`, `easings.ease`, `easings.easeIn`, `easings.easeOut`, `easings.easeInOut`.
## Practical Guidance
- Always key conditional or switching elements you expect to animate.
- Use `animateLayout` only on the element whose position or size changes.
- Prefer one clear transition intent per mixin: entrance starts from a style, exit ends at a style.
- Default to `...spring()` for duration and easing in most cases.
- Keep DOM work in `handle.queueTask(...)` or `ref(...)`, not in render.