Skip to content

Latest commit

 

History

History
258 lines (177 loc) · 10.4 KB

File metadata and controls

258 lines (177 loc) · 10.4 KB

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:

  • WordPress, CMS, static sites - add a <script> 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 - 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>:

  • 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 <title> child element inside the SVG
  • ARIA ID references (aria-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> -->

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:

  • 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

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:

  • 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 in the afterLoad hook for comprehensive protection.
  • Same-origin policy applies. SVGs are loaded with fetch(). Cross-origin SVGs require CORS headers 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).

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.