Components and hooks for layout animations and enter/exit animations with shared layout transitions.
Enables exit animations for components leaving the DOM and manages component presence lifecycle.
/**
* Wrapper component that enables exit animations for its children
* @param props - AnimatePresence configuration
* @returns JSX element managing presence animations
*/
function AnimatePresence(props: AnimatePresenceProps): JSX.Element;
interface AnimatePresenceProps {
/**
* Child components to manage presence for
*/
children?: React.ReactNode;
/**
* Whether to animate on initial mount (default: true)
*/
initial?: boolean;
/**
* Custom data passed to all child animations
*/
custom?: any;
/**
* Called when all exit animations complete
*/
onExitComplete?: () => void;
/**
* Animation mode for multiple children
* - "sync": All children animate simultaneously (default)
* - "wait": Wait for exit to complete before animating new children
* - "popLayout": Exiting children are removed from layout flow immediately
*/
mode?: "sync" | "wait" | "popLayout";
/**
* Root element for presence calculations (default: nearest positioned ancestor)
*/
root?: React.RefObject<Element>;
/**
* Whether presence affects layout calculations (default: true)
*/
presenceAffectsLayout?: boolean;
/**
* Propagate presence context to nested AnimatePresence components
*/
propagate?: boolean;
/**
* Exit animation anchor point
*/
anchorX?: number;
}Usage Examples:
import { AnimatePresence, motion } from "framer-motion";
import { useState } from "react";
// Basic presence animation
function BasicPresenceExample() {
const [isVisible, setIsVisible] = useState(true);
return (
<div>
<button onClick={() => setIsVisible(!isVisible)}>
Toggle
</button>
<AnimatePresence>
{isVisible && (
<motion.div
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.8 }}
transition={{ duration: 0.3 }}
className="w-32 h-32 bg-blue-500 rounded mt-4"
>
Animated content
</motion.div>
)}
</AnimatePresence>
</div>
);
}
// Wait mode for sequential animations
function WaitModeExample() {
const [currentItem, setCurrentItem] = useState(0);
const items = ["First", "Second", "Third"];
return (
<div>
<button
onClick={() => setCurrentItem((prev) => (prev + 1) % items.length)}
>
Next Item
</button>
<AnimatePresence mode="wait" onExitComplete={() => console.log("Exit complete")}>
<motion.div
key={currentItem}
initial={{ x: 300, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
exit={{ x: -300, opacity: 0 }}
transition={{ duration: 0.5 }}
className="w-64 h-32 bg-green-500 rounded mt-4 flex items-center justify-center text-white"
>
{items[currentItem]}
</motion.div>
</AnimatePresence>
</div>
);
}
// List with exit animations
function ListPresenceExample() {
const [items, setItems] = useState([1, 2, 3, 4, 5]);
const removeItem = (id: number) => {
setItems(items.filter(item => item !== id));
};
const addItem = () => {
setItems([...items, Math.max(...items, 0) + 1]);
};
return (
<div>
<button onClick={addItem} className="mb-4 px-4 py-2 bg-blue-500 text-white rounded">
Add Item
</button>
<AnimatePresence>
{items.map(item => (
<motion.div
key={item}
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: "auto" }}
exit={{ opacity: 0, height: 0 }}
transition={{ duration: 0.3 }}
className="bg-gray-200 p-4 mb-2 rounded overflow-hidden"
>
<div className="flex justify-between items-center">
<span>Item {item}</span>
<button
onClick={() => removeItem(item)}
className="px-2 py-1 bg-red-500 text-white rounded text-sm"
>
Remove
</button>
</div>
</motion.div>
))}
</AnimatePresence>
</div>
);
}Enables shared layout animations between components and coordinates layout transitions.
/**
* Provides shared layout animation context for its children
* @param props - LayoutGroup configuration
* @returns JSX element providing layout group context
*/
function LayoutGroup(props: LayoutGroupProps): JSX.Element;
interface LayoutGroupProps {
/**
* Child components to share layout animations
*/
children: React.ReactNode;
/**
* Unique identifier for this layout group
*/
id?: string;
/**
* Inherit layout group from parent (default: true)
*/
inherit?: boolean;
}Usage Examples:
import { LayoutGroup, motion } from "framer-motion";
import { useState } from "react";
// Shared layout animations
function SharedLayoutExample() {
const [selected, setSelected] = useState(0);
const items = ["Tab 1", "Tab 2", "Tab 3"];
return (
<LayoutGroup>
<div className="flex space-x-2 mb-4">
{items.map((item, index) => (
<motion.button
key={index}
onClick={() => setSelected(index)}
className={`px-4 py-2 rounded relative ${
selected === index ? "text-white" : "text-gray-600"
}`}
style={{
WebkitTapHighlightColor: "transparent"
}}
>
{selected === index && (
<motion.div
layoutId="activeTab"
className="absolute inset-0 bg-blue-500 rounded"
transition={{ type: "spring", bounce: 0.2, duration: 0.6 }}
/>
)}
<span className="relative z-10">{item}</span>
</motion.button>
))}
</div>
<motion.div
key={selected}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="p-4 bg-gray-100 rounded"
>
Content for {items[selected]}
</motion.div>
</LayoutGroup>
);
}
// Card expansion example
function CardExpansionExample() {
const [selectedId, setSelectedId] = useState<string | null>(null);
const items = [
{ id: "1", title: "Card 1", subtitle: "First card" },
{ id: "2", title: "Card 2", subtitle: "Second card" },
{ id: "3", title: "Card 3", subtitle: "Third card" }
];
return (
<LayoutGroup>
<div className="grid grid-cols-3 gap-4">
{items.map(item => (
<motion.div
key={item.id}
layoutId={item.id}
onClick={() => setSelectedId(item.id)}
className="bg-white rounded-lg shadow-md p-4 cursor-pointer"
whileHover={{ scale: 1.05 }}
>
<motion.h3 layoutId={`title-${item.id}`} className="font-bold">
{item.title}
</motion.h3>
<motion.p layoutId={`subtitle-${item.id}`} className="text-gray-600">
{item.subtitle}
</motion.p>
</motion.div>
))}
</div>
{selectedId && (
<motion.div
className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
onClick={() => setSelectedId(null)}
>
<motion.div
layoutId={selectedId}
className="bg-white rounded-lg shadow-lg p-8 max-w-md w-full mx-4"
onClick={(e) => e.stopPropagation()}
>
{items.find(item => item.id === selectedId) && (
<>
<motion.h3
layoutId={`title-${selectedId}`}
className="font-bold text-2xl mb-4"
>
{items.find(item => item.id === selectedId)?.title}
</motion.h3>
<motion.p
layoutId={`subtitle-${selectedId}`}
className="text-gray-600 mb-4"
>
{items.find(item => item.id === selectedId)?.subtitle}
</motion.p>
<p>Expanded content goes here...</p>
<button
onClick={() => setSelectedId(null)}
className="mt-4 px-4 py-2 bg-blue-500 text-white rounded"
>
Close
</button>
</>
)}
</motion.div>
</motion.div>
)}
</LayoutGroup>
);
}Hooks for accessing presence state and managing component lifecycle within AnimatePresence.
/**
* Access presence state within AnimatePresence
* @returns Tuple of [isPresent, safeToRemove]
*/
function usePresence(): [boolean, () => void];
/**
* Check if component is currently present
* @returns Boolean indicating presence state
*/
function useIsPresent(): boolean;
/**
* Access presence context data
* @returns Presence context information
*/
function usePresenceData(): PresenceContextData | null;
interface PresenceContextData {
/**
* Whether component is present
*/
isPresent: boolean;
/**
* Function to call when safe to remove component
*/
onExitComplete?: () => void;
/**
* Custom data passed from AnimatePresence
*/
custom?: any;
/**
* Whether initial animation should be performed
*/
initial?: boolean;
}Usage Examples:
import { usePresence, useIsPresent, motion } from "framer-motion";
import { useEffect } from "react";
// Custom exit animation component
function CustomExitComponent({ children }: { children: React.ReactNode }) {
const [isPresent, safeToRemove] = usePresence();
useEffect(() => {
if (!isPresent) {
// Perform custom exit logic
console.log("Component is exiting");
// Call safeToRemove when ready to be removed from DOM
const timer = setTimeout(() => {
safeToRemove();
}, 1000);
return () => clearTimeout(timer);
}
}, [isPresent, safeToRemove]);
return (
<motion.div
animate={isPresent ? "enter" : "exit"}
variants={{
enter: { opacity: 1, scale: 1 },
exit: { opacity: 0, scale: 0.8 }
}}
transition={{ duration: 0.5 }}
>
{children}
</motion.div>
);
}
// Conditional rendering based on presence
function ConditionalComponent() {
const isPresent = useIsPresent();
return (
<motion.div
animate={{
backgroundColor: isPresent ? "#3B82F6" : "#EF4444",
scale: isPresent ? 1 : 0.9
}}
className="p-4 rounded text-white"
>
{isPresent ? "I'm here!" : "I'm leaving..."}
</motion.div>
);
}Props for automatic layout animations on size and position changes.
interface LayoutAnimationProps {
/**
* Enable automatic layout animations
* - true: Animate both position and size changes
* - "position": Animate only position changes
* - "size": Animate only size changes
*/
layout?: boolean | "position" | "size";
/**
* Unique identifier for shared layout animations
*/
layoutId?: string;
/**
* Mark element as layout root for optimization
*/
layoutRoot?: boolean;
/**
* Enable layout animations during scroll
*/
layoutScroll?: boolean;
/**
* Dependencies that trigger layout recalculation
*/
layoutDependency?: any;
/**
* Called when layout animation starts
*/
onLayoutAnimationStart?: () => void;
/**
* Called when layout animation completes
*/
onLayoutAnimationComplete?: () => void;
}Usage Examples:
import { motion } from "framer-motion";
import { useState } from "react";
// Automatic layout animations
function LayoutAnimationExample() {
const [isExpanded, setIsExpanded] = useState(false);
return (
<motion.div
layout
onClick={() => setIsExpanded(!isExpanded)}
className={`bg-blue-500 text-white p-4 rounded cursor-pointer ${
isExpanded ? "w-96 h-48" : "w-32 h-16"
}`}
transition={{ type: "spring", damping: 20, stiffness: 300 }}
onLayoutAnimationStart={() => console.log("Layout animation started")}
onLayoutAnimationComplete={() => console.log("Layout animation completed")}
>
<h3>Click to expand</h3>
{isExpanded && (
<motion.p
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="mt-2"
>
This content appears when expanded!
</motion.p>
)}
</motion.div>
);
}
// Shared layout ID animations
function SharedLayoutIdExample() {
const [selectedItem, setSelectedItem] = useState<string | null>(null);
const items = ["item1", "item2", "item3"];
return (
<div>
<div className="grid grid-cols-3 gap-4 mb-4">
{items.map(item => (
<motion.div
key={item}
layoutId={item}
onClick={() => setSelectedItem(item)}
className="bg-green-500 text-white p-4 rounded cursor-pointer"
whileHover={{ scale: 1.05 }}
>
{item}
</motion.div>
))}
</div>
{selectedItem && (
<motion.div
layoutId={selectedItem}
className="bg-green-500 text-white p-8 rounded text-center"
onClick={() => setSelectedItem(null)}
>
Selected: {selectedItem}
<p className="mt-2 text-sm">Click to deselect</p>
</motion.div>
)}
</div>
);
}Utility hooks and functions for advanced layout animation control.
/**
* Perform instant layout transitions without animation
* @param callback - Function to execute during instant transition
*/
function useInstantLayoutTransition(): (callback: () => void) => void;
/**
* Reset projection calculations for layout animations
*/
function useResetProjection(): () => void;
/**
* Enable instant transitions temporarily
* @returns Function to trigger instant transition
*/
function useInstantTransition(): (callback: () => void) => void;
/**
* Disable instant transitions for a callback
* @param callback - Function to execute with instant transitions disabled
*/
function disableInstantTransitions(callback: () => void): void;Usage Examples:
import {
motion,
useInstantLayoutTransition,
useResetProjection
} from "framer-motion";
import { useState } from "react";
function LayoutUtilitiesExample() {
const [items, setItems] = useState([1, 2, 3, 4, 5]);
const instantTransition = useInstantLayoutTransition();
const resetProjection = useResetProjection();
const shuffleItems = () => {
// Shuffle without animation
instantTransition(() => {
setItems([...items].sort(() => Math.random() - 0.5));
});
};
const resetLayout = () => {
// Reset layout calculations
resetProjection();
};
return (
<div>
<div className="mb-4 space-x-2">
<button
onClick={shuffleItems}
className="px-4 py-2 bg-blue-500 text-white rounded"
>
Instant Shuffle
</button>
<button
onClick={resetLayout}
className="px-4 py-2 bg-green-500 text-white rounded"
>
Reset Layout
</button>
</div>
<div className="grid grid-cols-5 gap-2">
{items.map(item => (
<motion.div
key={item}
layout
className="bg-purple-500 text-white p-4 rounded text-center"
transition={{ type: "spring", damping: 25, stiffness: 300 }}
>
{item}
</motion.div>
))}
</div>
</div>
);
}Staggered Exit Animations:
<AnimatePresence>
{items.map((item, index) => (
<motion.div
key={item.id}
exit={{
opacity: 0,
x: -100,
transition: { delay: index * 0.1 }
}}
>
{item.content}
</motion.div>
))}
</AnimatePresence>Nested Presence:
<AnimatePresence>
{isVisible && (
<motion.div exit={{ opacity: 0 }}>
<AnimatePresence mode="wait">
<motion.div key={currentTab} exit={{ x: -100 }}>
Tab content
</motion.div>
</AnimatePresence>
</motion.div>
)}
</AnimatePresence>Custom Exit Timing:
function CustomExitTiming() {
const [isPresent, safeToRemove] = usePresence();
useEffect(() => {
if (!isPresent) {
// Custom async exit logic
performAsyncCleanup().then(() => {
safeToRemove();
});
}
}, [isPresent, safeToRemove]);
// Component implementation...
}