Audit Motion and Framer Motion exit/presence patterns with practical fixes for AnimatePresence usage.
Real-world examples
Live HTML demos for this skill — rendered directly in the page. 4 examples.
- 01
Wrapped modal with symmetric exit
AnimatePresence wraps a conditional overlay; exit mirrors initial (opacity + y) so the dialog leaves the same way it entered — exit-requires-wrapper, exit-prop-required, exit-matches-initial.
- 02
Keyed list with popLayout
Stable item.id keys (never index) plus mode="popLayout" so removals don't fight siblings for space — exit-key-required, mode-pop-layout-for-lists, mode-sync-layout-conflict.
- 03
Wait-mode panel swap
mode="wait" serializes exit→enter; transitions stay at ~150ms so the round-trip stays snappy — mode-wait-doubles-duration.
- 04
Nested exits + safe interactions
Outer/inner AnimatePresence with propagate, coordinated durations, and useIsPresent disabling clicks while exiting — nested-propagate-required, nested-consistent-timing, presence-disable-interactions.
Skill markdown
# Mastering AnimatePresence
Review Motion code for AnimatePresence and exit animation best practices.
## How It Works
1. Read the specified files (or prompt user for files/pattern)
2. Check against all rules below
3. Output findings in `file:line` format
## Rule Categories
| Priority | Category | Prefix |
|----------|----------|--------|
| 1 | Exit Animations | `exit-` |
| 2 | Presence Hooks | `presence-` |
| 3 | Mode Selection | `mode-` |
| 4 | Nested Exits | `nested-` |
## Rules
### Exit Animation Rules
#### `exit-requires-wrapper`
Conditional motion elements must be wrapped in AnimatePresence.
**Fail:**
```tsx
{isVisible && (
<motion.div exit={{ opacity: 0 }} />
)}
```
**Pass:**
```tsx
<AnimatePresence>
{isVisible && (
<motion.div exit={{ opacity: 0 }} />
)}
</AnimatePresence>
```
#### `exit-prop-required`
Elements inside AnimatePresence should have exit prop defined.
**Fail:**
```tsx
<AnimatePresence>
{isOpen && (
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} />
)}
</AnimatePresence>
```
**Pass:**
```tsx
<AnimatePresence>
{isOpen && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
/>
)}
</AnimatePresence>
```
#### `exit-key-required`
Dynamic lists inside AnimatePresence must have unique keys.
**Fail:**
```tsx
<AnimatePresence>
{items.map((item, index) => (
<motion.div key={index} exit={{ opacity: 0 }} />
))}
</AnimatePresence>
```
**Pass:**
```tsx
<AnimatePresence>
{items.map((item) => (
<motion.div key={item.id} exit={{ opacity: 0 }} />
))}
</AnimatePresence>
```
#### `exit-matches-initial`
Exit animation should mirror initial for symmetry.
**Fail:**
```tsx
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ scale: 0 }}
/>
```
**Pass:**
```tsx
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 20 }}
/>
```
### Presence Hook Rules
#### `presence-hook-in-child`
useIsPresent must be called from child of AnimatePresence, not parent.
**Fail:**
```tsx
function Parent() {
const isPresent = useIsPresent(); // Wrong location
return (
<AnimatePresence>
{show && <Child />}
</AnimatePresence>
);
}
```
**Pass:**
```tsx
function Child() {
const isPresent = useIsPresent(); // Correct location
return <motion.div data-exiting={!isPresent} />;
}
```
#### `presence-safe-to-remove`
When using usePresence, always call safeToRemove after async work.
**Fail:**
```tsx
function AsyncComponent() {
const [isPresent, safeToRemove] = usePresence();
useEffect(() => {
if (!isPresent) {
cleanup(); // Never calls safeToRemove
}
}, [isPresent]);
}
```
**Pass:**
```tsx
function AsyncComponent() {
const [isPresent, safeToRemove] = usePresence();
useEffect(() => {
if (!isPresent) {
cleanup().then(safeToRemove);
}
}, [isPresent, safeToRemove]);
}
```
#### `presence-disable-interactions`
Disable interactions on exiting elements using isPresent.
**Fail:**
```tsx
function Card() {
const isPresent = useIsPresent();
return <button onClick={handleClick}>Click</button>;
// Button clickable during exit
}
```
**Pass:**
```tsx
function Card() {
const isPresent = useIsPresent();
return (
<button onClick={handleClick} disabled={!isPresent}>
Click
</button>
);
}
```
### Mode Selection Rules
#### `mode-wait-doubles-duration`
Mode "wait" nearly doubles animation duration; adjust timing accordingly.
**Fail:**
```tsx
<AnimatePresence mode="wait">
<motion.div transition={{ duration: 0.3 }} />
</AnimatePresence>
// Total time: ~600ms (too slow)
```
**Pass:**
```tsx
<AnimatePresence mode="wait">
<motion.div transition={{ duration: 0.15 }} />
</AnimatePresence>
// Total time: ~300ms (acceptable)
```
#### `mode-sync-layout-conflict`
Mode "sync" causes layout conflicts; position exiting elements absolutely.
**Fail:**
```tsx
<AnimatePresence mode="sync">
{items.map(item => (
<motion.div exit={{ opacity: 0 }}>{item}</motion.div>
))}
</AnimatePresence>
// Exiting and entering elements compete for space
```
**Pass:**
```tsx
<AnimatePresence mode="popLayout">
{items.map(item => (
<motion.div exit={{ opacity: 0 }}>{item}</motion.div>
))}
</AnimatePresence>
```
#### `mode-pop-layout-for-lists`
Use popLayout mode for list reordering animations.
**Fail:**
```tsx
<AnimatePresence>
{items.map(item => <ListItem key={item.id} />)}
</AnimatePresence>
// Layout shifts during exit
```
**Pass:**
```tsx
<AnimatePresence mode="popLayout">
{items.map(item => <ListItem key={item.id} />)}
</AnimatePresence>
```
### Nested Exit Rules
#### `nested-propagate-required`
Nested AnimatePresence must use propagate prop for coordinated exits.
**Fail:**
```tsx
<AnimatePresence>
{isOpen && (
<motion.div exit={{ opacity: 0 }}>
<AnimatePresence>
{items.map(item => (
<motion.div key={item.id} exit={{ scale: 0
…More from raphaelsalaja
- 12 Principles Of AnimationApply Disney's 12 animation principles to web interfaces to make motion feel natural, organic, and human.
- Generating Sounds With AiAudit Web Audio API code for procedural sound synthesis quality, UX decisions, and parameter best practices.
- Morphing IconsBuild icon components that morph between SVG shapes with smooth, line-based transformation.
- Pseudo ElementsAudit CSS pseudo-elements and View Transitions usage for hover effects, decorative layers, and transitions.
- Sounds On The WebAudit interface sound feedback for UX quality, accessibility, and practical implementation patterns.
- To Spring Or Not To SpringAudit animation timing choices to decide when springs versus easing curves produce better motion.