svg-inject

npm version CI bundle size

SVGInject

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.

SVG Injection

Just add 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.

Quick start

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.

Who is it for

SVGInject works best when you don’t have a build step - or don’t want one for your SVGs:

SVGInject is a runtime library. It loads and injects SVGs in the browser. No build step, no bundler, no Node.js required.

Frameworks

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 />

Features

Tiny and dependency-free

~3.5 KB gzipped. Zero runtime dependencies. Tree-shakeable ESM. Ships with full TypeScript definitions.

Accessible by default

SVGInject automatically sets the right ARIA attributes based on your <img>:

<!-- 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> -->

ID conflict prevention

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.

Smart caching

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.

Built-in sanitization

SVGInject can strip <script> elements, <foreignObject>, on* event handler attributes, and javascript:/data: URIs before injection. Enable with sanitize: true. See Security for details.

SSR-safe

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.

Attribute handling

All attributes are copied from <img> to <svg> with these rules:

Set copyAttributes: false to disable and handle it yourself in beforeInject.

API

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.

Options

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'

Unstyled image flash

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; }

Error handling

<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'
  }
});

Security

SVG files can contain scripts. SVGInject includes built-in protections you can opt into:

Migrating from v1

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

Browser support

Chrome, Firefox, Safari, Edge - all modern versions.

License

MIT - Developed and maintained by INCORS.