A React component that injects SVG into the DOM.
Background | When To Use This | Basic Usage | API | Live Examples | Installation | Security | FAQ | Contributing | License
This component uses @tanem/svg-injector to fetch an SVG from a given URL and inject its markup into the DOM (why?). Fetched SVGs are cached, so multiple uses of the same SVG only require a single request.
Injection costs a network request and two wrapper elements, and it earns that cost in one case: the SVG's URL isn't known until the app runs, and the markup has to be reachable by CSS. An <img> tag renders an SVG but its contents can't be styled, animated or scripted from the page.
- SVGs live in your repo and are known at build time. Reach for a build-time transform - SVGR, vite-plugin-svgr, or your bundler's SVG loader. They compile each file to a React component, so there's no runtime fetch, and unused icons are tree-shaken out.
- The URL is only known at runtime. SVGs from a CMS or an API, user uploads, a CDN-hosted icon set, or a path assembled from data. That's what this component is for.
- You only need to display the image. Use
<img src="icon.svg">. It's cheaper than either option above.
import { ReactSVG } from 'react-svg'
const App = () => <ReactSVG src="svg.svg" />| Prop | Type | Default |
|---|---|---|
src |
string |
required |
afterInjection |
(svg: SVGSVGElement) => void |
noop |
beforeInjection |
(svg: SVGSVGElement) => void |
noop |
desc |
string |
'' |
evalScripts |
'always' | 'once' | 'never' |
'never' |
fallback |
React.ElementType |
none |
httpRequestWithCredentials |
boolean |
false |
loading |
React.ElementType |
none |
onError |
(error: unknown) => void |
noop |
renumerateIRIElements |
boolean |
true |
title |
string |
'' |
useRequestCache |
boolean |
true |
wrapper |
'div' | 'span' | 'svg' |
'div' |
Errors thrown from beforeInjection and afterInjection are routed to onError and render the fallback, the same as an error raised by the injection itself.
The SVG URL. Supports fetchable URLs (relative or absolute), data:image/svg+xml URLs (URL-encoded or base64), and SVG sprite sheets via fragment identifiers (e.g. sprite.svg#icon-star). See the data URL example and sprite usage example.
Called after the SVG is injected. svg is the injected SVG DOM element.
Called just before the SVG is injected. svg is the SVG DOM element which is about to be injected, so this is where to restyle, class or sanitise it - see Security.
String used for the SVG <desc> element content. If a <desc> exists it is replaced, otherwise a new one is created. When set, a unique id is added to the <desc> element and aria-describedby is set on the SVG for assistive technology. An empty string is a noop.
Whether to run script blocks found in the SVG: 'always', 'once' or 'never'. Leave it at 'never' for SVGs you don't control - see Security.
Rendered inside the wrapper if an error occurs. Can be a string, class component or function component. Nothing is rendered in its place when unset.
Whether cross-site Access-Control requests for the SVG are made using credentials.
Rendered inside the wrapper until the SVG is injected. Can be a string, class component or function component. Nothing is rendered in its place when unset.
Called if an error occurs. error is an unknown value.
Whether SVG IRI addressable elements are renumerated. When enabled, IDs on IRI-addressable elements (clipPath, linearGradient, mask, path, etc.) are made unique, and all references to them (presentation attributes, href/xlink:href, inline style attributes, and <style> element text) are updated. All matching element types are renumerated, not only those inside <defs>. Set to false if you need to query injected elements by their original IDs.
String used for the SVG <title> element content. If a <title> exists it is replaced, otherwise a new one is created. When set, a unique id is added to the <title> element and aria-labelledby is set on the SVG for assistive technology. An empty string is a noop.
Whether the SVG request cache is used. With it on, repeated uses of the same URL share a single request.
The element type used for the wrappers: 'div', 'span' or 'svg'.
Props not listed above are applied to the outermost wrapper element, so className, style, id, data-* attributes and DOM event handlers behave as they would on the underlying element.
A ref is forwarded to the outermost wrapper element, so ref.current is an HTMLDivElement, HTMLSpanElement or SVGSVGElement depending on wrapper. The exported WrapperType type covers all three.
Re-injection happens when src, wrapper, title, desc, evalScripts, httpRequestWithCredentials, renumerateIRIElements or useRequestCache changes. Other props don't affect the injected SVG, so changing them re-renders the wrapper without re-fetching. afterInjection, beforeInjection and onError are always called in their latest form, but changing them doesn't trigger a re-injection on its own, so they can be passed inline.
<ReactSVG
beforeInjection={(svg) => {
svg.classList.add('svg-class-name')
svg.setAttribute('style', 'width: 200px')
}}
className="wrapper-class-name"
desc="Description"
fallback={() => <span>Error!</span>}
loading={() => <span>Loading</span>}
onClick={() => {
console.log('wrapper onClick')
}}
onError={(error) => {
console.error(error)
}}
src="svg.svg"
title="Title"
wrapper="span"
/>Each name links to the example source, and the sandbox column opens it on CodeSandbox.
$ npm install react-svg
Requires React 16.8 or later, as a peer dependency.
Injected markup becomes part of your page, with the same privileges as anything else in it. That matters whenever src points at something you don't fully control - user uploads, a third-party host, a CMS anyone can write to. An SVG is an XML document that can carry scripts, event handlers and styles, not just shapes.
Scripts are off by default. evalScripts defaults to 'never', so <script> blocks inside a fetched SVG are not executed. Leave it that way for anything untrusted - 'always' and 'once' run whatever the file happens to contain.
Scripts aren't the only vector. Event-handler attributes such as onload and onclick, and href="javascript:..." on <a> elements, are inert to evalScripts but live once injected. For untrusted sources, sanitise the SVG element in beforeInjection, which runs after the fetch and before the element reaches the DOM:
import DOMPurify from 'dompurify'
import { ReactSVG } from 'react-svg'
const Icon = ({ src }) => (
<ReactSVG
beforeInjection={(svg) => {
DOMPurify.sanitize(svg, { IN_PLACE: true })
}}
src={src}
/>
)Sanitising the URL matters too. A javascript: or data:text/html value in src should never reach this component; validate the URL's scheme and origin before passing it in.
Injected content isn't isolated. A <style> element inside an SVG applies to the whole page, so a fetched file can restyle your app through a generic class name like .cls-1, and the last SVG injected wins (#2077). DOMPurify keeps <style> elements, so sanitising doesn't address this. Remove or rewrite them in beforeInjection if the SVGs aren't yours. Note that renumerateIRIElements (on by default) makes id attributes unique, but does nothing for class names.
Why are there two wrapping elements?
This module delegates its core behaviour to @tanem/svg-injector, which requires a parent node when swapping in the SVG element. The swap occurs outside of React flow, so we don't want React updates to conflict with the DOM nodes @tanem/svg-injector is managing.
Example output, assuming a div wrapper:
<div>
<!-- The wrapper, managed by React -->
<div>
<!-- The parent node, managed by @tanem/svg-injector -->
<svg>...</svg>
<!-- The swapped-in SVG, managed by @tanem/svg-injector -->
</div>
</div>See:
Related issues and PRs:
Can I use data URIs or inline SVG strings?
data:image/svg+xml URLs are supported (both URL-encoded and base64-encoded). The underlying library parses the SVG content directly from the data URL using DOMParser, without making a network request. This is useful when bundlers like Vite inline small SVGs as data URIs. See the data URL example for details.
Inline SVG strings (raw markup passed directly as the src prop) are not supported. If you already have the SVG markup as a string (for example, a dynamically generated chart), consider parsing it with DOMParser and appending the result yourself, or rendering it with dangerouslySetInnerHTML. These approaches avoid the fetch step entirely and will also avoid the brief flash that occurs when react-svg re-injects on src change.
Security note: inserting SVG strings into the DOM bypasses React's built-in escaping and can expose your application to XSS if the content is not trusted. If the SVG originates from user input or a third party, sanitise it first with a library like DOMPurify before inserting it into the page. The same applies to fetched SVGs - see Security.
Issues and pull requests are welcome. npm run test:src is the development loop; npm test runs the full gate.
Repo conventions that aren't visible in the code - the PR labels that drive releases, the React version matrix policy, and how the examples/ dependencies are pinned - live in AGENTS.md. Coding agents read it from the repo root, so keep it in sync when a change invalidates something it states.
MIT