In the classic browser rendering pipeline (Critical Rendering Path), mutating geometric properties (width, height, top, left, margin, padding) is computationally expensive and hazardous to rendering performance. Any geometric alteration forces the browser to execute a forced cascading recomputation of element coordinates across the document tree — Layout (Reflow).
Interleaving synchronous layout reads (element.offsetHeight, getBoundingClientRect) with subsequent geometry writes within a single animation frame triggers Layout Thrashing. As a result, rendering framerates collapse from 120 FPS down to a stuttering 20–30 FPS, and Interaction to Next Paint (INP) degrades into the critical red zone (> 200 ms).
The modern Motion engine (starting in versions 11+, Framer Motion transitioned to the lean, modular motion package) radically alters the layout animation paradigm. Rather than mutating geometric properties, Motion implements the fundamental FLIP (First, Last, Invert, Play) pattern. The library measures the initial and final DOM coordinates, inverts the delta via transform matrices, and interpolates exclusively on the GPU Compositor Thread with zero Reflow overhead.
Traditional CSS animation of width/height or top/left properties unavoidably overwhelms the CPU with layout recalculations on every frame. The Motion engine translates layout shifts (item insertion, list filtering, shared element transitions) into hardware-accelerated GPU transforms (transform and opacity), guaranteeing 60–120 FPS fluidity and immediate tactile responsiveness to user gestures.
At the core of Motion is the four-phase execution pipeline of the FLIP algorithm coupled with a physical Spring simulator:
┌────────────────────────────────────────────────────────────────────────┐
│ MATHEMATICAL FLIP TECHNIQUE PIPELINE │
├────────────────────────────────────────────────────────────────────────┤
│ 1. FIRST: Measure initial coordinates: box1 = el.getBoundingClientRect()│
│ 2. LAST: Apply new state in DOM: box2 = el.getBCR() │
│ 3. INVERT: Calculate delta matrix for translation and scale: │
│ dx = box1.left - box2.left; sx = box1.width / box2.width │
│ Apply inversion: transform: translate(dx) scale(sx) │
│ 4. PLAY: Hardware removal of delta towards transform: identity (GPU)│
└───────────────────────────────────┬────────────────────────────────────┘
│
▼ (Compositor Thread)
┌────────────────────────────────────────────────────────────────────────┐
│ Result: 120 FPS, zero Layout recalculation, hardware smoothness │
└────────────────────────────────────────────────────────────────────────┘layout: Prop directing Motion to track geometric shifts on the node. If a card changes dimensions or positions due to a sibling being added to a CSS Flexbox/Grid container, Motion automatically smooths the transition via FLIP.if (isOpen)) and orchestrates the exit phase.velocity) upon gesture release.User clicks a card: setSelectedId(42). React 19 schedules a concurrent render for the new UI tree.
Motion synchronously samples thumbnail bounds (First), mounts the expanded card, and captures its target coordinates (Last).
Motion computes the scale/translate delta and applies it via CSS transform to the new node, snapping it visually to the thumbnail.
The spring integrator interpolates the transformation matrix back to identity frame-by-frame on the Compositor Thread at 120 FPS.
Why does animating an element's position and size via Motion's layout prop execute noticeably smoother (120 FPS) than an equivalent CSS transition like transition: width 0.3s, left 0.3s?
In mobile and web applications, selecting a product item must not produce screen flickering or blank states. The card smoothly expands from a grid tile into a full-screen modal, preserving focus, visual continuity, and smoothly revealing detailed content.
When switching active tabs, the indicator does not snap or re-render abruptly; instead, it glides underneath the active item using a single shared layoutId.
When dragging a task card across columns, neighboring cards fluidly part to clear space. Employing layout="position" guarantees zero-latency position adjustments even when rendering dense boards.
Sheets and toast notifications that users can interactively fling. Releasing a sheet at high velocity transfers kinetic energy (velocity) into the spring solver, closing the view with physical authenticity.
In traditional CSS animations, timing is rigid: transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1). If a user aborts an interaction mid-motion, CSS either resets velocity to zero or produces an abrupt visual discontinuity.
Motion models movement using a harmonic spring oscillator equation:
F = -k * x - c * vWhere:
k (stiffness): The restorative force pulling the object towards its target. Higher stiffness yields sharper acceleration;c (damping): Frictional resistance. Below the critical threshold, underdamped oscillation (bounce) occurs;m (mass): Inertial mass of the moving object;v (velocity): Velocity vector of the user's cursor or touch point at release.When scaling via transform: scale(), child typography stretches and blurs, while corner radiuses (border-radius) distort into elliptical shapes. Motion resolves this by decoupling spatial responsibilities:
[Parent Container]: layout (animates dimensions and position via FLIP)
│
└── [Child Typography]: layout="position" (compensates for parent scale,
translating without text distortion)In a notification stack, when a card is dismissed, lower siblings should immediately and smoothly shift up to fill the void while the exiting card fades and slides to the right. Which AnimatePresence mode achieves this?
Let's build two high-performance production components in React 19:
src/components/navigation/SlidingTabsNav.tsx: Tab navigation featuring a sliding physical indicator driven by layoutId with zero Layout Thrashing.src/components/gallery/ExpandableCardModal.tsx: A production gallery featuring Shared Element Transitions, spring-physics swipe-to-dismiss gestures, and typographic scale compensation.'use client';
import * as React from 'react';
import { motion } from 'motion/react';
export interface TabItem {
readonly id: string;
readonly label: string;
readonly badge?: number;
}
interface SlidingTabsNavProps {
readonly tabs: readonly TabItem[];
readonly defaultTabId?: string;
readonly onChange?: (tabId: string) => void;
}
export function SlidingTabsNav({
tabs,
defaultTabId,
onChange,
}: SlidingTabsNavProps) {
const [activeTab, setActiveTab] = React.useState<string>(
defaultTabId ?? tabs[0]?.id ?? ''
);
const handleSelect = (id: string) => {
setActiveTab(id);
onChange?.(id);
};
return (
<nav aria-label="Section tabs" className="inline-flex p-1.5 rounded-2xl bg-bg-surface border border-border shadow-inner">
<div className="flex items-center gap-1">
{tabs.map((tab) => {
const isActive = tab.id === activeTab;
return (
<button
key={tab.id}
type="button"
onClick={() => handleSelect(tab.id)}
className={`relative px-4 py-2 text-xs font-semibold rounded-xl transition-colors duration-200 outline-none focus-visible:ring-2 focus-visible:ring-accent-violet cursor-pointer ${
isActive ? 'text-white' : 'text-text-muted hover:text-text-primary'
}`}
>
{/* Floating active tab marker on GPU */}
{isActive && (
<motion.div
layoutId="active-tab-pill-marker"
className="absolute inset-0 rounded-xl bg-accent-violet/30 border border-accent-violet/50 shadow-sm"
transition={{
type: 'spring',
stiffness: 450,
damping: 32,
mass: 0.8,
}}
/>
)}
{/* Tab Content */}
<span className="relative z-10 flex items-center gap-2">
<span>{tab.label}</span>
{typeof tab.badge === 'number' && (
<span
className={`text-[10px] font-mono px-1.5 py-0.5 rounded-full ${
isActive
? 'bg-accent-violet text-white'
: 'bg-bg-elevated text-text-muted'
}`}
>
{tab.badge}
</span>
)}
</span>
</button>
);
})}
</div>
</nav>
);
}'use client';
import * as React from 'react';
import { motion, AnimatePresence, type PanInfo } from 'motion/react';
export interface CardItem {
readonly id: string;
readonly title: string;
readonly category: string;
readonly description: string;
readonly tag: string;
}
const SAMPLE_CARDS: readonly CardItem[] = [
{
id: 'card-1',
title: 'React 19 Concurrent Architecture',
category: 'Architecture',
tag: 'Fiber & Lanes',
description: 'Deep dive into the React Fiber scheduler, Lanes execution priority model, and selective hydration.',
},
{
id: 'card-2',
title: 'Motion FLIP Engine',
category: 'Graphics',
tag: 'Compositor 120 FPS',
description: 'Mathematical transformation pipeline inside the browser Compositor Thread with zero DOM reflow.',
},
{
id: 'card-3',
title: 'Local-First & CRDT Replication',
category: 'Databases',
tag: 'Yjs & Dexie',
description: 'Conflict-free document synchronization across browser IndexedDB stores via WebSockets and WebRTC.',
},
];
export function ExpandableCardModal() {
const [selectedId, setSelectedId] = React.useState<string | null>(null);
const activeCard = SAMPLE_CARDS.find((c) => c.id === selectedId);
// Close modal on downward gesture fling
const handleDragEnd = (_event: MouseEvent | TouchEvent | PointerEvent, info: PanInfo) => {
if (info.offset.y > 100 || info.velocity.y > 400) {
setSelectedId(null);
}
};
return (
<div className="relative max-w-4xl mx-auto p-6 space-y-6">
{/* Thumbnail Grid */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-5">
{SAMPLE_CARDS.map((card) => (
<motion.div
key={card.id}
layoutId={`card-container-${card.id}`}
onClick={() => setSelectedId(card.id)}
whileHover={{ y: -4, transition: { duration: 0.15 } }}
whileTap={{ scale: 0.98 }}
className="cursor-pointer p-6 rounded-2xl bg-bg-surface border border-border hover:border-accent-violet/40 transition-colors shadow-lg space-y-3"
transition={{
type: 'spring',
stiffness: 350,
damping: 28,
}}
>
<motion.div
layoutId={`card-badge-${card.id}`}
className="inline-block text-[10px] font-mono font-bold uppercase tracking-wider px-2 py-0.5 rounded-full bg-accent-violet/15 text-accent-violet border border-accent-violet/20"
>
{card.category}
</motion.div>
{/* layout='position' prevents font stretching during parent scale morphs */}
<motion.h3
layout="position"
layoutId={`card-title-${card.id}`}
className="font-bold text-base text-text-primary leading-snug"
>
{card.title}
</motion.h3>
<motion.p
layout="position"
className="text-xs text-text-secondary line-clamp-2"
>
{card.description}
</motion.p>
</motion.div>
))}
</div>
{/* Modal Overlay with Shared Element Transition */}
<AnimatePresence>
{activeCard && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
{/* Semi-transparent Backdrop */}
<motion.div
key="backdrop"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={() => setSelectedId(null)}
className="absolute inset-0 bg-black/75 backdrop-blur-md cursor-pointer"
/>
{/* Expanded Card with Dismiss Drag */}
<motion.div
layoutId={`card-container-${activeCard.id}`}
drag="y"
dragConstraints={{ top: 0, bottom: 0 }}
dragElastic={0.6}
onDragEnd={handleDragEnd}
className="relative z-10 w-full max-w-lg p-8 rounded-3xl bg-bg-surface border border-accent-violet/40 shadow-2xl space-y-6 touch-none"
transition={{
type: 'spring',
stiffness: 300,
damping: 30,
}}
>
{/* Swipe Indicator Pill */}
<div className="w-12 h-1.5 rounded-full bg-border mx-auto -mt-2 opacity-60" />
<div className="flex items-start justify-between gap-4">
<div className="space-y-2">
<motion.div
layoutId={`card-badge-${activeCard.id}`}
className="inline-block text-[10px] font-mono font-bold uppercase tracking-wider px-2.5 py-0.5 rounded-full bg-accent-violet/15 text-accent-violet border border-accent-violet/20"
>
{activeCard.category} • {activeCard.tag}
</motion.div>
<motion.h2
layout="position"
layoutId={`card-title-${activeCard.id}`}
className="text-2xl font-black text-text-primary tracking-tight"
>
{activeCard.title}
</motion.h2>
</div>
<button
type="button"
onClick={() => setSelectedId(null)}
className="px-3 py-1.5 text-xs font-mono rounded-xl bg-bg-elevated border border-border text-text-muted hover:text-text-primary transition-colors cursor-pointer"
>
ESC
</button>
</div>
{/* Detailed Content fading in post-expansion */}
<motion.div
initial={{ opacity: 0, y: 15 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 5 }}
transition={{ delay: 0.08, duration: 0.2 }}
className="space-y-4 pt-4 border-t border-border text-sm text-text-secondary leading-relaxed"
>
<p>{activeCard.description}</p>
<p className="text-xs text-text-muted">
Drag card down to dismiss or press ESC / click background. Transition executed via FLIP algorithm without layout reflow.
</p>
</motion.div>
</motion.div>
</div>
)}
</AnimatePresence>
</div>
);
}Attempting to manually animate animate={{ width: isOpen ? 400 : 100 }} forces the browser to trigger an expensive Layout recalculation on every animation frame.
Solution: Use the layout directive and toggle Tailwind sizing classes, allowing Motion to translate changes into GPU-accelerated FLIP matrices automatically.
If children within AnimatePresence lack a unique, stable key prop, React Fiber cannot track the departing node, causing the element to vanish instantly without playing its exit phase.
Solution: Always provide a stable unique key for all animated children under AnimatePresence.
Because FLIP uses transform: scale(), nested text stretches and blurs during morphological transitions.
Solution: Always add layout="position" to child typographic headings and paragraphs so they translate smoothly without undergoing scale distortion.
Importing import { motion } from 'framer-motion' pulls an outdated monolithic bundle exceeding 35 KB.
Solution: In Framer Motion 11+ codebases, import from the lean, modern entry point: import { motion } from 'motion/react'.
transform: translate / scale) on the Compositor Thread.layoutId prop links independent components across the React tree, providing seamless Shared Element Transitions between grid items and modal dialogs.AnimatePresence orchestrates the unmounting exit phase, with mode="popLayout" allowing neighboring elements to immediately collapse into vacated space via position: absolute.stiffness), damping (damping), and mass (mass), natively preserving gesture velocity (velocity).layout="position" on child elements, and import from motion/react to ensure optimal bundle footprint.