auto-animate
Adds automatic animations to DOM changes with zero configuration using FormKit's AutoAnimate. Use when adding smooth transitions for list reordering, adding/removing elements, or quick UI polish.
When & Why to Use This Skill
AutoAnimate is a zero-configuration animation utility that automatically adds smooth transitions to DOM changes. It solves the complexity of manual transition management by providing instant 'add', 'remove', and 'move' animations for web elements. Compatible with React, Vue, Svelte, and Vanilla JS, it allows developers to significantly enhance UI/UX with a tiny 1.9KB footprint and minimal code changes.
Use Cases
- Dynamic List Management: Automatically animate items being added to or removed from todo lists, shopping carts, or activity feeds for a more fluid user experience.
- Interactive Reordering: Provide smooth visual feedback when users sort or reorder items in a list, grid, or dashboard layout.
- Search and Filter Results: Enhance the visual flow of search interfaces by animating the entry and exit of elements as filters are applied in real-time.
- Collapsible UI Components: Implement professional-grade transitions for accordions, dropdown menus, and toggleable content sections without writing complex CSS.
- Rapid UI Polishing: Quickly add high-quality animations to existing web applications by simply wrapping parent elements, requiring zero manual keyframe configuration.
| name | auto-animate |
|---|---|
| description | Adds automatic animations to DOM changes with zero configuration using FormKit's AutoAnimate. Use when adding smooth transitions for list reordering, adding/removing elements, or quick UI polish. |
AutoAnimate
Zero-config, drop-in animation utility that adds smooth transitions to any web app. 1.9KB, works with React, Vue, Svelte, Solid, and vanilla JS.
Quick Start
npm install @formkit/auto-animate
import autoAnimate from '@formkit/auto-animate';
// Apply to any parent element
autoAnimate(document.getElementById('list'));
That's it. Children added, removed, or moved will animate automatically.
What It Animates
AutoAnimate triggers on three DOM events:
- Child added - fade in
- Child removed - fade out
- Child moved - smooth position change
React
useAutoAnimate Hook
import { useAutoAnimate } from '@formkit/auto-animate/react';
function TodoList() {
const [items, setItems] = useState(['Item 1', 'Item 2', 'Item 3']);
const [parent] = useAutoAnimate();
const addItem = () => setItems([...items, `Item ${items.length + 1}`]);
const removeItem = (index) => setItems(items.filter((_, i) => i !== index));
return (
<div>
<button onClick={addItem}>Add Item</button>
<ul ref={parent}>
{items.map((item, index) => (
<li key={item}>
{item}
<button onClick={() => removeItem(index)}>Remove</button>
</li>
))}
</ul>
</div>
);
}
Enable/Disable Animation
function ToggleableAnimation() {
const [parent, enableAnimations] = useAutoAnimate();
const [isEnabled, setIsEnabled] = useState(true);
const toggle = () => {
setIsEnabled(!isEnabled);
enableAnimations(!isEnabled);
};
return (
<>
<button onClick={toggle}>
{isEnabled ? 'Disable' : 'Enable'} Animations
</button>
<ul ref={parent}>
{/* items */}
</ul>
</>
);
}
With Custom Options
function CustomAnimations() {
const [parent] = useAutoAnimate({
duration: 250,
easing: 'ease-in-out'
});
return <ul ref={parent}>{/* items */}</ul>;
}
Vue
Directive (Global Registration)
// main.js
import { autoAnimatePlugin } from '@formkit/auto-animate/vue';
app.use(autoAnimatePlugin);
<template>
<ul v-auto-animate>
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
</ul>
</template>
With Options
<template>
<ul v-auto-animate="{ duration: 300 }">
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
</ul>
</template>
Composable
<script setup>
import { ref } from 'vue';
import { useAutoAnimate } from '@formkit/auto-animate/vue';
const [parent] = useAutoAnimate();
const items = ref(['One', 'Two', 'Three']);
</script>
<template>
<ul ref="parent">
<li v-for="item in items" :key="item">{{ item }}</li>
</ul>
</template>
Svelte
<script>
import autoAnimate from '@formkit/auto-animate';
let items = ['One', 'Two', 'Three'];
function addItem() {
items = [...items, `Item ${items.length + 1}`];
}
</script>
<button on:click={addItem}>Add</button>
<ul use:autoAnimate>
{#each items as item (item)}
<li>{item}</li>
{/each}
</ul>
With Options
<ul use:autoAnimate={{ duration: 300, easing: 'ease-out' }}>
{#each items as item (item)}
<li>{item}</li>
{/each}
</ul>
Solid
import { createAutoAnimate } from '@formkit/auto-animate/solid';
import { createSignal, For } from 'solid-js';
function List() {
const [parent] = createAutoAnimate();
const [items, setItems] = createSignal(['One', 'Two', 'Three']);
return (
<ul ref={parent}>
<For each={items()}>
{(item) => <li>{item}</li>}
</For>
</ul>
);
}
Vanilla JavaScript
import autoAnimate from '@formkit/auto-animate';
const list = document.getElementById('my-list');
autoAnimate(list);
// Add items dynamically - they'll animate in
const newItem = document.createElement('li');
newItem.textContent = 'New Item';
list.appendChild(newItem);
Toggle Animation
import autoAnimate from '@formkit/auto-animate';
const list = document.getElementById('my-list');
const controller = autoAnimate(list);
// Disable
controller.disable();
// Re-enable
controller.enable();
// Check state
console.log(controller.isEnabled());
Configuration Options
autoAnimate(element, {
// Duration in milliseconds (default: 250)
duration: 300,
// Easing function (default: 'ease-in-out')
easing: 'ease-out',
// Disable respecting prefers-reduced-motion
disrespectUserMotionPreference: false
});
Custom Animations
For full control, provide a function that returns a KeyframeEffect.
import autoAnimate from '@formkit/auto-animate';
autoAnimate(element, (el, action, oldCoords, newCoords) => {
let keyframes;
// action: 'add' | 'remove' | 'remain'
if (action === 'add') {
keyframes = [
{ transform: 'scale(0)', opacity: 0 },
{ transform: 'scale(1)', opacity: 1 }
];
} else if (action === 'remove') {
keyframes = [
{ transform: 'scale(1)', opacity: 1 },
{ transform: 'scale(0)', opacity: 0 }
];
} else {
// 'remain' - element moved position
const deltaX = oldCoords.left - newCoords.left;
const deltaY = oldCoords.top - newCoords.top;
keyframes = [
{ transform: `translate(${deltaX}px, ${deltaY}px)` },
{ transform: 'translate(0, 0)' }
];
}
return new KeyframeEffect(el, keyframes, {
duration: 300,
easing: 'ease-out'
});
});
Custom Animation Examples
Slide from Left
autoAnimate(element, (el, action) => {
if (action === 'add') {
return new KeyframeEffect(el, [
{ transform: 'translateX(-100%)', opacity: 0 },
{ transform: 'translateX(0)', opacity: 1 }
], { duration: 300, easing: 'ease-out' });
}
if (action === 'remove') {
return new KeyframeEffect(el, [
{ transform: 'translateX(0)', opacity: 1 },
{ transform: 'translateX(100%)', opacity: 0 }
], { duration: 300, easing: 'ease-in' });
}
// Use default for 'remain'
return new KeyframeEffect(el, [], { duration: 0 });
});
Flip Animation
autoAnimate(element, (el, action, oldCoords, newCoords) => {
if (action === 'remain') {
const deltaX = oldCoords.left - newCoords.left;
const deltaY = oldCoords.top - newCoords.top;
const deltaW = oldCoords.width / newCoords.width;
const deltaH = oldCoords.height / newCoords.height;
return new KeyframeEffect(el, [
{
transform: `translate(${deltaX}px, ${deltaY}px) scale(${deltaW}, ${deltaH})`
},
{ transform: 'translate(0, 0) scale(1, 1)' }
], { duration: 300, easing: 'ease-out' });
}
// Default for add/remove
return new KeyframeEffect(el, [
{ opacity: action === 'add' ? 0 : 1 },
{ opacity: action === 'add' ? 1 : 0 }
], { duration: 200, easing: 'ease-out' });
});
Common Patterns
Animated Todo List
function TodoList() {
const [parent] = useAutoAnimate();
const [todos, setTodos] = useState([]);
const [input, setInput] = useState('');
const addTodo = () => {
if (!input.trim()) return;
setTodos([...todos, { id: Date.now(), text: input }]);
setInput('');
};
const removeTodo = (id) => {
setTodos(todos.filter(t => t.id !== id));
};
return (
<div>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && addTodo()}
/>
<ul ref={parent}>
{todos.map(todo => (
<li key={todo.id}>
{todo.text}
<button onClick={() => removeTodo(todo.id)}>X</button>
</li>
))}
</ul>
</div>
);
}
Sortable List
function SortableList() {
const [parent] = useAutoAnimate();
const [items, setItems] = useState([
{ id: 1, name: 'First' },
{ id: 2, name: 'Second' },
{ id: 3, name: 'Third' }
]);
const moveUp = (index) => {
if (index === 0) return;
const newItems = [...items];
[newItems[index - 1], newItems[index]] = [newItems[index], newItems[index - 1]];
setItems(newItems);
};
const moveDown = (index) => {
if (index === items.length - 1) return;
const newItems = [...items];
[newItems[index], newItems[index + 1]] = [newItems[index + 1], newItems[index]];
setItems(newItems);
};
return (
<ul ref={parent}>
{items.map((item, index) => (
<li key={item.id}>
{item.name}
<button onClick={() => moveUp(index)}>Up</button>
<button onClick={() => moveDown(index)}>Down</button>
</li>
))}
</ul>
);
}
Filter/Search Results
function FilterableList() {
const [parent] = useAutoAnimate();
const [query, setQuery] = useState('');
const allItems = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'];
const filtered = allItems.filter(item =>
item.toLowerCase().includes(query.toLowerCase())
);
return (
<div>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Filter..."
/>
<ul ref={parent}>
{filtered.map(item => (
<li key={item}>{item}</li>
))}
</ul>
</div>
);
}
Accordion
function Accordion({ items }) {
const [openIndex, setOpenIndex] = useState(null);
return (
<div>
{items.map((item, index) => (
<AccordionItem
key={item.id}
item={item}
isOpen={openIndex === index}
onToggle={() => setOpenIndex(openIndex === index ? null : index)}
/>
))}
</div>
);
}
function AccordionItem({ item, isOpen, onToggle }) {
const [parent] = useAutoAnimate();
return (
<div ref={parent}>
<button onClick={onToggle}>
{item.title}
</button>
{isOpen && <div className="content">{item.content}</div>}
</div>
);
}
Accessibility
AutoAnimate automatically respects prefers-reduced-motion:
@media (prefers-reduced-motion: reduce) {
/* AutoAnimate will disable animations */
}
To override this behavior:
autoAnimate(element, {
disrespectUserMotionPreference: true
});
Limitations
- Only animates direct children of the parent element
- Elements need stable keys/identity for move detection
- CSS animations on children may conflict
- Very rapid DOM changes may cause visual glitches
Tips
- Always use keys in React/Vue/Svelte for proper element tracking
- Apply to parent, not individual items
- Keep durations short (200-300ms) for snappy feel
- Disable when needed for performance-critical operations
- Combine with CSS for hover/focus states (AutoAnimate handles layout changes)