Style your SVGs with CSS. No build step. No framework lock-in. ~3.5 KB gzipped.
SVGInject replaces <img> elements with inline <svg> so you can target every path, circle, and group with CSS - colors, animations, hover effects, dark mode, all of it. One line of code, works everywhere.
![]()
onload="SVGInject(this)" to your <img> tags.Using v1? v2 is a drop-in upgrade - same API, no code changes needed. You get bug fixes, better accessibility, and a full test suite. Only downside: no more IE support. See what changed.
Vanilla - download or copy the file:
<script src="svg-inject.min.js"></script>
npm - versioned, update with npm update:
npm install @iconfu/svg-inject
<script src="node_modules/@iconfu/svg-inject/dist/svg-inject.min.js"></script>
Bundler - import and expose globally:
import { SVGInject } from '@iconfu/svg-inject';
window.SVGInject = SVGInject;
Then in your HTML:
<img src="icon.svg" onload="SVGInject(this)" />
<!-- The SVG gets injected and is styleable! -->
Without onload - inject from JavaScript instead (better for strict CSP):
document.addEventListener('DOMContentLoaded', () => {
SVGInject(document.querySelectorAll('img.injectable'));
});
React, Vue, or Svelte? See Frameworks.
SVGInject works best when you don’t have a build step - or don’t want one for your SVGs:
<script> tag, doneSVGInject is a runtime library. It loads and injects SVGs in the browser. No build step, no bundler, no Node.js required.
SVGInject is simpler and lighter than framework-specific packages for styling static SVGs with CSS. We don’t ship framework packages - SVGInject is one function, here’s how to wire it up.
If you need loading states, error boundaries, or dynamic src changes, look at tools like react-inlinesvg - or build your own on top of SVGInject’s API.
React - handler:
import { SVGInject } from '@iconfu/svg-inject';
const svgInject = (e) => SVGInject(e.currentTarget);
<img src="icon.svg" onLoad={svgInject} />
Vue - custom directive:
// main.js
import { SVGInject } from '@iconfu/svg-inject';
app.directive('svg-inject', {
mounted(el) { el.onload = () => SVGInject(el); }
});
<img src="icon.svg" v-svg-inject />
Svelte - action:
<script>
import { SVGInject } from '@iconfu/svg-inject';
function svgInject(node) { node.onload = () => SVGInject(node); }
</script>
<img src="icon.svg" use:svgInject />
~3.5 KB gzipped. Zero runtime dependencies. Tree-shakeable ESM. Ships with full TypeScript definitions.
SVGInject automatically sets the right ARIA attributes based on your <img>:
alt="descriptive text" - sets role="img" and aria-label on the SVGalt="" (decorative) - sets role="none" and aria-hidden="true"title attribute - becomes a <title> child element inside the SVGaria-labelledby, aria-describedby, etc.) are updated when IDs are made unique<!-- Meaningful icon -->
<img src="chart.svg" alt="Sales by quarter" onload="SVGInject(this)" />
<!-- Becomes: <svg role="img" aria-label="Sales by quarter"> ... </svg> -->
<!-- Decorative divider -->
<img src="divider.svg" alt="" onload="SVGInject(this)" />
<!-- Becomes: <svg role="none" aria-hidden="true"> ... </svg> -->
When multiple SVGs on the same page share IDs (common with gradient or clipPath definitions), SVGInject automatically makes all IDs unique by appending --inject-N suffixes. References in url(), href, xlink:href, and ARIA attributes are updated to match.
Each SVG URL is fetched once and cached for the page lifetime. Subsequent injections of the same URL reuse the cached SVG string but parse a fresh DOM element, so ID uniquification and sanitization are always applied.
SVGInject can strip <script> elements, <foreignObject>, on* event handler attributes, and javascript:/data: URIs before injection. Enable with sanitize: true. See Security for details.
Safe to import in Node.js, Next.js, Nuxt, SvelteKit, and any server-side environment. All DOM access is deferred to function call time - no top-level window or document references.
All attributes are copied from <img> to <svg> with these rules:
src, alt, onload, onerrortitle becomes a <title> child elementalt becomes aria-label (or triggers decorative mode if empty)style is merged with the SVG’s existing inline style (img values win on conflicts)viewBox and preserveAspectRatio are correctly mapped from their lowercased HTML formSet copyAttributes: false to disable and handle it yourself in beforeInject.
SVGInject(img, options?): Promise<void>
Injects the SVG for one or many <img> elements. Accepts a single element, an array, or a NodeList. Returns a Promise that resolves when all injections complete.
SVGInject.setOptions(options): void
Sets global default options for all subsequent injections.
SVGInject.create(name, options?): SVGInjectFunction
Creates an independent instance with its own cache and options. The name parameter sets the global variable name (used for onload attribute binding) and the flash-prevention CSS selector.
SVGInject.err(img, fallbackSrc?): void
Error handler for onerror on <img> elements. Optionally sets a fallback src.
| Property | Type | Default | Description |
|---|---|---|---|
useCache |
boolean |
true |
Cache SVG content per URL for the page lifetime |
copyAttributes |
boolean |
true |
Copy attributes from <img> to <svg> |
makeIdsUnique |
boolean |
true |
Append --inject-N suffix to all IDs to prevent collisions |
sanitize |
boolean |
false |
Strip <script>, <foreignObject>, event handlers, and dangerous URIs before injection |
injectStyleTag |
boolean |
true |
Inject a <style> tag to hide images before injection, preventing unstyled image flash. Set to false if you have a strict CSP |
beforeLoad |
(img) => string \| void |
Hook before loading. Return a string to override the URL | |
afterLoad |
(svg, svgString) => string \| SVGSVGElement \| void |
Hook after loading. Modify the SVG or return a new one. Called once per URL when caching is enabled | |
beforeInject |
(img, svg) => Element \| void |
Hook before injection. Return an element to inject instead | |
afterInject |
(img, svg) => void |
Hook after injection | |
onAllFinish |
() => void |
Called when all elements in a batch are done | |
onFail |
(img, status) => void |
Called on failure. Status is 'LOAD_FAIL', 'SVG_INVALID', or 'SVG_NOT_SUPPORTED' |
When using onload, the browser may briefly show the raw <img> before injection replaces it. SVGInject prevents this by default - it injects a CSS rule that hides injectable images until injection is complete.
This requires style-src 'unsafe-inline' in your Content Security Policy. If you have a strict CSP, disable it and add the rule to your own stylesheet instead:
SVGInject.setOptions({ injectStyleTag: false });
img[onload^="SVGInject("] { visibility: hidden; }
<img src="icon.svg"
onload="SVGInject(this)"
onerror="SVGInject.err(this, 'fallback.png')" />
Or use the onFail hook:
SVGInject.setOptions({
onFail(img, status) {
console.error('Injection failed:', status); // 'LOAD_FAIL', 'SVG_INVALID', or 'SVG_NOT_SUPPORTED'
}
});
SVG files can contain scripts. SVGInject includes built-in protections you can opt into:
SVGInject.setOptions({ sanitize: true }) or per call with SVGInject(img, { sanitize: true }). This strips <script>, <foreignObject>, on* event handlers, and javascript:/data: URIs before injection, catching the most common XSS vectors.afterLoad hook for comprehensive protection.fetch(). Cross-origin SVGs require CORS headers on the server.onload="..." attribute requires script-src 'unsafe-inline' (or a nonce/hash). If you use a strict CSP, call SVGInject from JavaScript instead (see Quick start).v2 is API-compatible with v1. Breaking changes:
| Change | Migration |
|---|---|
| IE9-11 no longer supported | Stay on v1.x for IE |
| Decorative images handled correctly | alt="" now sets role="none" + aria-hidden="true" |
alt converted to aria-label |
Accessibility improvement |
style merged instead of overwritten |
Better behavior when both img and SVG have inline styles |
Chrome, Firefox, Safari, Edge - all modern versions.