SVGInject swaps img tags for inline SVG so CSS can reach them
A tiny, intuitive, robust, caching solution for injecting SVG files inline into the DOM.
At a glance
- What is it?
- SVGInject is a browser runtime library that replaces img elements with inline svg elements at load time, which makes individual paths styleable with CSS. It suits teams without a build step, and it has sharp edges around sanitization, ID collisions and framework use.
- Who is it for?
- SVGInject fits teams serving plain HTML, CMS output or server rendered templates that need CSS control over SVG internals without adding a build pipeline. It does not fit React applications that need loading states and error boundaries, where react-inlinesvg or a custom wrapper around the exported API is the better starting point.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Activity is slowing. The repository last received commits 6 months ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 25, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Why anyone would replace a working img element
An SVG loaded through an img element is a black box. The browser paints it as an image, so a stylesheet cannot select a path inside it, a circle cannot change fill on hover, and a single file cannot follow a dark mode switch. SVGInject addresses that specific gap. It runs in the browser, fetches the SVG file, and swaps the img element for an inline svg element whose children sit directly in the page DOM, where your CSS can target them.
The library is positioned for situations where a build step is missing or unwanted. The project description names WordPress themes, CMS content, static sites, server rendered templates such as PHP, Rails or Django output, and HTML that arrives at runtime from editors or widgets. Prototyping is another stated use, because styling an icon with CSS without standing up a bundler is the immediate win. The project also describes itself as useful in codebases that mix jQuery, React and plain JavaScript, because the core is a single exported function rather than a framework wrapper.
SVGInject is a runtime library. There is no build step, no bundler and no Node.js requirement for the pages that use it, which is the whole point for a CMS plugin or a template file. The package itself is written in TypeScript and ships TypeScript definitions.
What the SVGInject function does to each element
The exported entry point is a function that takes one or many img elements and an optional options object, and returns a Promise that resolves when every injection has completed. It accepts a single element, an array or a NodeList, so the same call covers one icon and a page full of them.
The interesting work is attribute transfer. The src, alt, onload and onerror attributes are excluded from the copy. A title attribute on the img becomes a title child element inside the new svg. An alt attribute becomes an aria label, or switches the element into a decorative mode when the value is empty. A style attribute is merged with any inline style already present on the SVG, with the values taken from the img winning on conflicts.
Case sensitivity is handled too. SVG attributes such as viewBox and preserveAspectRatio are spelled with capital letters in SVG but arrive lowercased through HTML parsing, so the library maps them back. If that behaviour is not wanted, copyAttributes: false disables the whole copy step and lets you take over in a beforeInject hook.
The call can be made in two ways. The inline form binds the function directly to the load event of the image, and the script form collects elements after the document is ready. The second form is the one the project points at for strict Content Security Policy setups, since it avoids inline event handler attributes.
The inject suffix that keeps gradients from colliding
Duplicate IDs are the classic failure mode of inline SVG. A gradient, a clipPath or a mask defined inside an icon file keeps the same identifier in every copy, so a page with twenty icons can end up with twenty elements claiming to be gradient one, and the browser resolves references to whichever comes first in the document.
SVGInject rewrites identifiers on injection. When several SVGs on the same page share IDs, it appends an inject suffix with a counter to each one, producing names in the form inject and a number. References are then rewritten to match, and the project lists the places it touches: url() values, href attributes, xlink:href attributes and ARIA attributes.
This is the part of the library that earns its keep on real pages rather than in demos. It also means the IDs in your final DOM are not the IDs in your source files, which matters if anything else in the stack, a test selector or a script that queries by identifier, expects the original names. The rewriting is applied at every injection, so a page that swaps icon markup dynamically gets consistent handling without a separate step.
Caching reuses the fetched text but never the node
Every URL is fetched once and kept for the lifetime of the page. Injects of the same file afterwards reuse the cached SVG string instead of hitting the network again, which matters when a listing page shows the same icon in forty rows.
The caching design has a detail worth knowing about: the cached string is reused, but the SVG is parsed into a fresh DOM element on each injection. Because the element is rebuilt each time, ID uniquification and sanitization are always applied. A cached copy is therefore not a shared node that later injections could mutate, and it is not a snapshot of the file taken before your sanitization rules changed. The trade-off is that parsing cost is paid per injection while network cost is paid once, so a page with hundreds of icons pays for CPU and saves on requests.
The cache is per instance. SVGInject.create builds an independent instance with its own cache and its own options, and the name argument sets both the global variable used for onload binding and the CSS selector used for flash prevention. SVGInject.setOptions changes the default options for every later injection in that instance.
How alt text turns into ARIA on the injected svg
The accessibility handling is derived entirely from the attributes you already wrote on the img, which means there is no extra configuration step if your markup is already reasonable.
A meaningful alt value makes the resulting element an image with a label. An empty alt value marks the graphic as decorative, and the resulting svg carries a role of none along with aria-hidden set to true. A title attribute on the img becomes a title child inside the SVG, so it is exposed as the accessible name in the usual way. When identifiers are made unique for collision avoidance, ARIA references such as aria-labelledby and aria-describedby are rewritten to point at the new names, so labelling still resolves after the rename.
There is an error path as well. SVGInject.err is described as the error handler for onerror on img elements, and it can optionally set a fallback src. That gives a page a way to swap in a placeholder when an icon file is missing, without a separate error handling block around every element.
The sanitize option and the CSP friendly call path
Because the library pulls remote or local files and injects their contents into the page, it also offers a sanitizing mode. Setting sanitize: true strips script elements, foreignObject elements, on-prefixed event handler attributes, and javascript: and data: URIs before the content is inserted.
That switch is optional, so the default behaviour is the permissive one. If your page accepts SVG content from users, from a CMS editor, or from any third party, turning sanitization on is the difference between a decorative icon and an injection vector. The README describes the capability but does not document a configurable allow list, so the on or off choice is the only control described.
The other security note in the project concerns Content Security Policy. The inline onload form of the call uses an inline event handler attribute, which strict CSP setups tend to block. The alternative is to call the function from script after the document is ready, selecting elements with a class. That path needs no inline handler and no build step, which makes it the safer default for a hardened site even though the one line attribute form is the more eye catching example.
Installing the package and running a first injection
The quickest route needs no package manager. The project links a minified browser build that you can download and copy into your project, then load with a script tag. The script exposes a global SVGInject function, and the markup pattern is the same everywhere afterwards: an img element pointing at an SVG file, with onload calling the function on itself.
<script src="svg-inject.min.js"></script><img src="icon.svg" onload="SVGInject(this)" />If you prefer a versioned dependency that you can update with npm update, install the scoped package and reference the built file inside node_modules.
npm install @iconfu/svg-inject<script src="node_modules/@iconfu/svg-inject/dist/svg-inject.min.js"></script>In a bundler setup, import the named export and assign it to the window so the onload attribute can still find it.
import { SVGInject } from '@iconfu/svg-inject';
window.SVGInject = SVGInject;When inline handlers are not an option, run the same work from script after the document is ready.
document.addEventListener('DOMContentLoaded', () => {
SVGInject(document.querySelectorAll('img.injectable'));
});For single file framework setups, the project shows one handler for React, a directive for Vue and an action for Svelte. The Vue version is a directive that assigns onload to the element when it mounts.
// 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 />Where SVGInject stops being the right tool
The project itself names the boundary. SVGInject is described as simpler and lighter than framework specific packages for styling static SVGs with CSS, and it ships no framework packages at all. If you need loading states, error boundaries or handling of a dynamic src attribute, the project points at tools such as react-inlinesvg, or at building your own layer on top of the exported API. The choice is about who owns the lifecycle. SVGInject reacts to an image load event; a component library owns it, and a component library is what you want when a source can change after mount.
Other limits are visible in the project layout. The repository holds src, dist, tests, examples and resource directories, and the package files field ships dist only, so source files are not part of the published tarball. The library also dropped Internet Explorer support in v2, which the project calls a drop-in upgrade with the same API and no code changes, the only downside being that IE is gone. The v1 to v2 gap is large in the release history: version 1.2.3 shipped on 2019-03-12 and version 2.0.0 on 2026-04-01, followed by version 2.0.1 on 2026-04-02. The last push to the repository was on 2026-04-11, so the project is not archived but it is not showing frequent commit activity either, and the gap between the v1 line and the v2 rewrite means most examples in the wild are a major version behind.
The licence is MIT, and the package author is listed as INCORS GmbH. Read the LICENSE file in the repository for the exact terms before shipping it in a product.
Editorial conclusion
SVGInject fits teams serving plain HTML, CMS output or server rendered templates that need CSS control over SVG internals without adding a build pipeline. It does not fit React applications that need loading states and error boundaries, where react-inlinesvg or a custom wrapper around the exported API is the better starting point. Before adopting it, check that the SVGs you serve are same origin files you control, decide whether sanitize: true is required for your inputs, and confirm that the browser you target does not need Internet Explorer support.
Frequently asked questions
What does SVGInject do with an img element?
It replaces the img element with an inline svg element built from the fetched file, so the paths, circles and groups inside become selectable with CSS. The src, alt, onload and onerror attributes are not copied across, while title becomes a title child and alt becomes an aria label.
How do I install SVGInject and inject an SVG without a build step?
Download the minified browser build from the unpkg link in the project and load it with a script tag, then add onload="SVGInject(this)" to the img element. For a versioned dependency, npm install @iconfu/svg-inject and point a script tag at node_modules/@iconfu/svg-inject/dist/svg-inject.min.js.
How does SVGInject avoid ID conflicts between inline SVGs?
When SVGs on the same page share IDs, the library appends an inject suffix with a counter to each identifier. References in url(), href, xlink:href and ARIA attributes are rewritten to match, and the cached SVG string is still parsed into a fresh element for every injection so the rewriting runs each time.
Does SVGInject sanitize SVG content?
Only when you ask it to. With sanitize: true it strips script elements, foreignObject elements, on-prefixed event handler attributes and javascript: and data: URIs before injection. The option is off by default, so pages that accept SVG from users should turn it on explicitly.
When should I not use SVGInject in a React project?
The project states it fits static SVGs styled with CSS and points to react-inlinesvg, or to your own wrapper on the API, when you need loading states, error boundaries or handling of a dynamic src. SVGInject reacts to the image load event and does not own the component lifecycle.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/iconfu-svg-inject)Was this useful?