https://github.com/iconfu/svg-inject
A tiny, intuitive, robust, caching solution for injecting SVG files inline into the DOM.
https://github.com/iconfu/svg-inject
injection inline-svg svg svg-css svg-files svg-icons svg-images svg-inject
Last synced: 2 months ago
JSON representation
A tiny, intuitive, robust, caching solution for injecting SVG files inline into the DOM.
- Host: GitHub
- URL: https://github.com/iconfu/svg-inject
- Owner: iconfu
- License: mit
- Created: 2018-06-26T07:18:50.000Z (almost 8 years ago)
- Default Branch: master
- Last Pushed: 2024-01-03T17:43:40.000Z (over 2 years ago)
- Last Synced: 2025-07-08T16:52:06.763Z (11 months ago)
- Topics: injection, inline-svg, svg, svg-css, svg-files, svg-icons, svg-images, svg-inject
- Language: JavaScript
- Homepage:
- Size: 694 KB
- Stars: 806
- Watchers: 19
- Forks: 84
- Open Issues: 17
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
[](https://www.npmjs.com/package/@iconfu/svg-inject) [](https://github.com/iconfu/svg-inject/actions/workflows/ci.yml) [](https://bundlephobia.com/package/@iconfu/svg-inject)
# SVGInject
**Style your SVGs with CSS. No build step. No framework lock-in. ~3.5 KB gzipped.**
SVGInject replaces `
` elements with inline `` 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.

### Just add `onload="SVGInject(this)"` to your `
` 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](#migrating-from-v1).
## Quick start
**Vanilla** - [download](https://unpkg.com/@iconfu/svg-inject@2/dist/svg-inject.min.js) or copy the file:
```html
```
**npm** - versioned, update with `npm update`:
```bash
npm install @iconfu/svg-inject
```
```html
```
**Bundler** - import and expose globally:
```js
import { SVGInject } from '@iconfu/svg-inject';
window.SVGInject = SVGInject;
```
**Then in your HTML:**
```html

```
**Without `onload`** - inject from JavaScript instead (better for strict [CSP](#security)):
```js
document.addEventListener('DOMContentLoaded', () => {
SVGInject(document.querySelectorAll('img.injectable'));
});
```
**React, Vue, or Svelte?** See [Frameworks](#frameworks).
## Who is it for
SVGInject works best when you don't have a build step - or don't want one for your SVGs:
- **WordPress, CMS, static sites** - add a `` tag, done
- **Server-rendered pages** - PHP, Rails, Django, any backend template
- **Dynamic / third-party content** - HTML injected at runtime, CMS editors, widgets
- **Prototyping** - style SVGs with CSS without setting up tooling
- **Multi-framework projects** - one solution across jQuery, React, vanilla, whatever
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](https://github.com/gilbarbara/react-inlinesvg) - or build your own on top of SVGInject's [API](#api).
**React** - handler:
```jsx
import { SVGInject } from '@iconfu/svg-inject';
const svgInject = (e) => SVGInject(e.currentTarget);
<img src="icon.svg" onLoad={svgInject} />
```
**Vue** - custom directive:
```js
// main.js
import { SVGInject } from '@iconfu/svg-inject';
app.directive('svg-inject', {
mounted(el) { el.onload = () => SVGInject(el); }
});
```
```vue
<img src="icon.svg" v-svg-inject />
```
**Svelte** - action:
```svelte
<script>
import { SVGInject } from '@iconfu/svg-inject';
function svgInject(node) { node.onload = () => SVGInject(node); }
```
## 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 `
`:
- **`alt="descriptive text"`** - sets `role="img"` and `aria-label` on the SVG
- **`alt=""`** (decorative) - sets `role="none"` and `aria-hidden="true"`
- **`title` attribute** - becomes a `` child element inside the SVG
- **ARIA ID references** (`aria-labelledby`, `aria-describedby`, etc.) are updated when IDs are made unique
```html


```
### 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 `` elements, `<foreignObject>`, `on*` event handler attributes, and `javascript:`/`data:` URIs before injection. Enable with `sanitize: true`. See [Security](#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:
- **Excluded**: `src`, `alt`, `onload`, `onerror`
- **`title`** becomes a `<title>` child element
- **`alt`** becomes `aria-label` (or triggers decorative mode if empty)
- **`style`** is merged with the SVG's existing inline style (img values win on conflicts)
- **Case-sensitive SVG attributes** like `viewBox` and `preserveAspectRatio` are correctly mapped from their lowercased HTML form
Set `copyAttributes: false` to disable and handle it yourself in `beforeInject`.
## API
```ts
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.
```ts
SVGInject.setOptions(options): void
```
Sets global default options for all subsequent injections.
```ts
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.
```ts
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:
```js
SVGInject.setOptions({ injectStyleTag: false });
```
```css
img[onload^="SVGInject("] { visibility: hidden; }
```
## Error handling
```html
<img src="icon.svg"
onload="SVGInject(this)"
onerror="SVGInject.err(this, 'fallback.png')" />
```
Or use the `onFail` hook:
```js
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:
- **Built-in sanitization.** Enable with `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.
- **For untrusted SVGs** (user uploads, third-party URLs), consider adding [DOMPurify](https://github.com/cure53/DOMPurify) in the `afterLoad` hook for comprehensive protection.
- **Same-origin policy applies.** SVGs are loaded with `fetch()`. Cross-origin SVGs require [CORS headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) on the server.
- **CSP note.** The `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](#quick-start)).
## 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](https://github.com/iconfu/svg-inject/blob/master/LICENSE) - Developed and maintained by [INCORS](https://www.incors.com).