SVG Animation in React — CSS, SMIL, and Framer Motion Compared
React gives you three distinct paths for animated SVG: CSS keyframes on inline SVG, SMIL baked into the file, and a motion library like Framer Motion. Each trades off bundle size, capability, and complexity differently. Here is when to reach for each.
Quick Comparison
| Approach | Runtime | Bundle | Best for |
|---|---|---|---|
| CSS @keyframes (inline SVG) | None | 0 KB | UI icons, loaders, decorative motion |
| SMIL (baked into SVG file) | None | 0 KB | Standalone SVGs, emails, img tags |
| Framer Motion | Yes | ~45 KB gzipped | Complex sequences, gestures, layout animation |
Approach 1 — CSS @keyframes on Inline SVG
The simplest approach. Render the SVG inline in JSX, add a className to any element inside it, and write standard CSS. No extra package. No runtime cost. Works with CSS Modules, Tailwind, or plain stylesheets.
When to use this
Always, for looping or simple keyframe animations on icons and loaders. Zero cost, works with SSR, no hydration issues.
Approach 2 — SMIL Exported as a React Component
SMIL animations are declared inside the SVG with <animate> and <animateTransform> elements. When you export from CSSVG as React JSX, you get a component where the animation is entirely self-contained — no CSS file needed.
When to use this
When you want a self-contained component with no external CSS dependency — ideal for component libraries and design systems where encapsulation matters.
Approach 3 — Framer Motion
Framer Motion gives you orchestration, gesture-driven animation, exit animations, and layout transitions that CSS keyframes cannot do. But it adds ~45 KB gzipped to your bundle and requires a client component in Next.js App Router.
"use client";
import { motion } from "framer-motion";
export function CheckIcon({ checked }: { checked: boolean }) {
return (
<svg viewBox="0 0 24 24" width="24" height="24">
<motion.path
d="M4 13 L9 18 L20 7"
fill="none"
stroke="#6366f1"
strokeWidth="2"
strokeLinecap="round"
initial={{ pathLength: 0, opacity: 0 }}
animate={checked
? { pathLength: 1, opacity: 1 }
: { pathLength: 0, opacity: 0 }}
transition={{ duration: 0.4, ease: "easeOut" }}
/>
</svg>
);
}When to use this
State-driven transitions (checked/unchecked, open/close), gesture responses (drag, hover with spring physics), or exit animations. Not worth the bundle cost for looping icons.
Generating the SVG Code Without Hand-Coding
For CSS and SMIL approaches, CSSVG lets you design the animation visually and then export directly as a React component — no manual keyframe math required.
- →Import your SVG icon — each shape becomes its own layer in the timeline
- →Set keyframes by scrubbing the timeline and adjusting properties in the Inspector
- →Export → React JSX gets you a self-contained component with SMIL animations
- →Export → CSS gets you the SVG plus a CSS file you can import into your component
Decision Guide
Looping icon or loader with no JS interaction?
CSS @keyframes — zero cost, works everywhere.
Self-contained component for a design system?
SMIL via CSSVG React export — no CSS dependency.
Animation driven by React state or user gesture?
Framer Motion — accept the bundle size tradeoff.
SVG needs to animate in an email or img tag?
SMIL only — CSS is sandboxed in those contexts.
Export Animated SVG for React — Free
Design your animation visually and export a ready-to-paste React component. No hand-coding required.
Open the Editor — it's free