If you have ever tried to paste raw SVG markup into a React component and watched your terminal explode with errors, you are not alone. SVG and JSX look similar on the surface, but the differences are just enough to cause frustration. This guide walks through everything you need to know about using SVGs in React in 2026, from manual conversion to fully automated toolchains.
Why SVGs Belong in Your React Projects
Raster formats like PNG and JPEG have their place, but SVGs offer a set of advantages that make them the default choice for icons, logos, and illustrations in modern web apps:
- Resolution independence. SVGs scale to any size without quality loss. A single SVG icon looks crisp on a phone screen and a 5K monitor alike.
- Tiny file sizes. A typical icon SVG weighs 500 bytes to 2 KB, far smaller than an equivalent PNG at multiple resolutions.
- CSS and JS control. Because SVGs are XML-based, you can style individual paths with CSS, animate them with JavaScript, and change colors dynamically through props.
-
Accessibility. SVGs support
<title>and<desc>elements, letting you provide semantic meaning for screen readers. - Tree-shakeable when componentized. When each SVG is a React component, unused icons get stripped from your production bundle automatically.
In a React project, SVGs become first-class citizens. You can pass props to control size and color, compose them inside other components, and lazy-load them just like any other module.
The SVG-to-JSX Gap: What Actually Breaks
HTML and JSX are close relatives, but JSX follows JavaScript naming conventions. When you copy SVG code from a design tool or an icon library and drop it into a .jsx file, several things go wrong:
Attribute Name Differences
SVG uses hyphenated attribute names. JSX requires camelCase.
| SVG Attribute | JSX Equivalent |
|---|---|
stroke-width |
strokeWidth |
fill-rule |
fillRule |
clip-path |
clipPath |
font-size |
fontSize |
xmlns:xlink |
(remove entirely) |
The class vs className Problem
SVG elements exported from Figma or Illustrator often carry class attributes. In JSX, class is a reserved word. Every instance must be renamed to className.
Inline Styles Must Be Objects
SVG sometimes includes style="fill:#333;stroke:#000". In JSX, the style attribute expects a JavaScript object:
// Wrong
<circle style="fill:#333;stroke:#000" />
// Correct
<circle style={{ fill: '#333', stroke: '#000' }} />
Self-Closing Tags
Some SVG elements like <path> and <circle> may appear without self-closing syntax in raw SVG. JSX requires every element to be explicitly closed.
Namespace Attributes
Attributes like xmlns:xlink and xml:space are invalid in JSX. They need to be removed or converted to their JSX-safe equivalents.
When you are dealing with a single simple icon, fixing these by hand takes a minute. When you have 80 icons exported from your design system, manual conversion becomes a real bottleneck.
Manual Conversion: Step by Step
For those times when you need to convert one or two SVGs quickly, here is the process:
// Original SVG from a design tool
/*
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
class="icon" fill="none" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2L2 7l10 5 10-5-10-5z"/>
<path d="M2 17l10 5 10-5"/>
<path d="M2 12l10 5 10-5"/>
</svg>
*/
// Converted JSX Component
const LayersIcon = ({ size = 24, color = "currentColor", ...props }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width={size}
height={size}
className="icon"
fill="none"
stroke={color}
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
{...props}
>
<path d="M12 2L2 7l10 5 10-5-10-5z" />
<path d="M2 17l10 5 10-5" />
<path d="M2 12l10 5 10-5" />
</svg>
);
export default LayersIcon;
The changes: class became className, hyphenated attributes became camelCase, stroke and size became dynamic props, and all paths are self-closed. This approach works, but it does not scale.
SVGR: The Industry Standard for Automated Conversion
SVGR is the tool the React ecosystem has settled on for converting SVG files into React components at build time. It powers the SVG handling in Create React App, Next.js (via custom config), and Vite.
Installation
npm install --save-dev @svgr/cli
# or for webpack integration
npm install --save-dev @svgr/webpack
CLI Usage
Convert a single file:
npx @svgr/cli --icon --typescript icon.svg
Convert an entire directory into a component library:
npx @svgr/cli --icon --typescript --out-dir src/icons -- assets/svg/
This generates one .tsx file per SVG, each exporting a React component.
Webpack / Vite Integration
For Vite projects, use the vite-plugin-svgr plugin:
// vite.config.js
import svgr from 'vite-plugin-svgr';
export default {
plugins: [svgr()],
};
Then import SVGs directly as components:
import { ReactComponent as Logo } from './logo.svg';
// or with the newer default export approach
import Logo from './logo.svg?react';
function Header() {
return <Logo className="header-logo" aria-label="Company logo" />;
}
Custom SVGR Configuration
Create an .svgrrc.js file at your project root for fine-grained control:
// .svgrrc.js
module.exports = {
icon: true,
typescript: true,
replaceAttrValues: {
'#000': 'currentColor',
'#000000': 'currentColor',
},
svgProps: {
role: 'img',
},
plugins: ['@svgr/plugin-svgo', '@svgr/plugin-jsx', '@svgr/plugin-prettier'],
svgoConfig: {
plugins: [
{
name: 'preset-default',
params: {
overrides: {
removeViewBox: false,
},
},
},
'removeDimensions',
],
},
};
This configuration does several things at once:
- Replaces hardcoded black fills with
currentColorso icons inherit text color. - Adds
role="img"for accessibility. - Runs SVGO optimization to strip unnecessary metadata.
- Preserves
viewBox(critical for proper scaling) while removing fixedwidth/height.
Inline SVG vs. Component vs. Image Tag: Choosing the Right Approach
There are three common ways to use SVGs in React. Each has trade-offs.
1. Inline SVG (JSX directly in components)
function Alert() {
return (
<svg viewBox="0 0 24 24" width={20} height={20} fill="currentColor">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 ..." />
</svg>
);
}
Pros: Full styling control, no extra HTTP request, tree-shakeable.
Cons: Bloats component files, harder to maintain at scale.
2. SVG as a React Component (via SVGR)
import AlertIcon from '@/icons/AlertIcon';
function Alert() {
return <AlertIcon size={20} color="red" aria-label="Warning" />;
}
Pros: Clean imports, reusable, prop-driven, tree-shakeable.
Cons: Adds to JavaScript bundle size (each icon is a module).
3. SVG as an Image Source
function Logo() {
return <img src="/images/logo.svg" alt="Company Logo" width={120} />;
}
Pros: Cached by the browser, does not increase JS bundle.
Cons: No dynamic styling, no access to internal paths, extra HTTP request.
The recommendation for 2026: Use SVGR components for icons and interactive SVGs. Use <img> tags for large, static illustrations that do not need dynamic colors or animations. This gives you the best balance of performance and developer experience.
Optimization Tips for Production
Run SVGO Before Conversion
SVGs exported from design tools carry a lot of baggage: editor metadata, empty groups, redundant transforms, and unnecessary precision in path data. SVGO strips all of that.
npx svgo --multipass -f ./raw-svgs/ -o ./optimized-svgs/
The --multipass flag runs multiple optimization passes, often shrinking files by 30-60%.
Use currentColor for Themeable Icons
Replace hardcoded color values with currentColor. This lets your icons inherit the CSS color property of their parent element, making dark mode support trivial:
// The icon color follows the parent's text color
<button className="text-red-500">
<TrashIcon /> Delete
</button>
Lazy-Load Heavy SVG Illustrations
For complex SVGs with hundreds of paths (maps, detailed illustrations), use React.lazy:
const WorldMap = React.lazy(() => import('./WorldMap'));
function Dashboard() {
return (
<Suspense fallback={<div className="map-skeleton" />}>
<WorldMap />
</Suspense>
);
}
Add Proper Accessibility Attributes
Always include either aria-label or a combination of <title> and aria-labelledby:
const CheckIcon = ({ title = "Success", ...props }) => (
<svg role="img" aria-labelledby="check-title" {...props}>
<title id="check-title">{title}</title>
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" />
</svg>
);
For decorative icons that convey no meaning, use aria-hidden="true" instead.
Avoid Inlining Massive SVGs
If an SVG is over 10 KB of markup, consider keeping it as a static file served via <img> or an <object> tag. Inlining large SVGs increases your JavaScript parse time and defeats code-splitting.
Online Tools for Quick Conversions
When you need a fast, one-off conversion without setting up a build pipeline, online converters are the way to go. I keep a few bookmarked:
SVG to JSX Converter on VIADreams handles attribute conversion, self-closing tags, and optional TypeScript output right in the browser. Paste your SVG, get a ready-to-use React component. It is especially useful during prototyping when you are pulling icons from various sources and need instant results.
The SVG to JSX React Guide on VIADreams is a solid companion reference if you want to dive deeper into edge cases like gradient handling and animation preservation.
The SVGR Playground at react-svgr.com/playground lets you test different SVGR configurations interactively.
For production workflows, always pair these tools with a proper SVGR build integration so your entire team works from the same conversion pipeline.
Putting It All Together: A Recommended Setup
Here is a practical setup that works well for teams of any size:
-
Design exports go into a
raw-icons/directory as plain.svgfiles. - SVGO runs as a pre-build step to optimize them.
-
SVGR converts optimized SVGs into TypeScript React components under
src/icons/. -
A barrel file (
src/icons/index.ts) re-exports everything for clean imports. - Tree shaking ensures only the icons you actually use end up in the bundle.
// package.json scripts
{
"scripts": {
"icons:optimize": "svgo --multipass -f raw-icons -o optimized-icons",
"icons:generate": "svgr --icon --typescript --out-dir src/icons -- optimized-icons",
"icons": "npm run icons:optimize && npm run icons:generate"
}
}
Run npm run icons after updating any SVG assets, and your component library stays in sync with your design system automatically.
Wrapping Up
SVGs in React are straightforward once you understand the JSX conversion rules. For small projects, a quick online converter or manual fix is fine. For anything with more than a handful of icons, SVGR with SVGO gives you a reliable, automated pipeline that keeps your components clean and your bundles small.
The key takeaways:
- Always preserve
viewBoxand remove fixed dimensions for flexible scaling. - Use
currentColorto make icons theme-aware. - Automate conversion with SVGR in your build tool of choice.
- Optimize with SVGO before conversion for the smallest possible output.
- Add accessibility attributes to every icon component.
Start with the approach that fits your project size, and scale up as your icon library grows.
Top comments (0)