iconify-static
Offline-first Iconify icons for Svelte and web components. Icons are downloaded at build time and served from your static assets, instead of an external CDN.
This project aims to be a drop-in replacement for the existing Iconify library.
Demo: https://drbscl.github.io/iconify-static/
Motivation
Iconify is a great way to add icons to your webapp, but the official library loads SVGs clientside via their CDN. This can be a limitation if:
- Their CDN goes offline.
- You are making an offline or offline-first app (e.g. a mobile app via Capacitor).
This library aims to address this, by providing an easy to use replacement that downloads and bundles the icons that your app uses.
Features
- Only downloads the icons you actually use.
- Icons stored locally in your assets directory; no runtime dependency on Iconify CDN.
- Automatic icon detection from source code.
- Vite plugin downloads new icons at build time.
- Works with Svelte components and vanilla web components.
Installation
Step 1: Install the package
npm install iconify-static
Step 2: Configure Vite
Add the plugin to your vite.config.ts:
import { sveltekit } from '@sveltejs/kit/vite'; import { iconifyStatic } from 'iconify-static/vite'; import { defineConfig } from 'vite'; export default defineConfig({ plugins: [ sveltekit(), // Only required for sveltekit; you don't need this if you're using web components iconifyStatic() ] });
Step 3: Use icons in your components
Svelte component:
<script> import { Icon } from 'iconify-static'; </script> <Icon icon="mdi:home" />
Web component:
<script> import { registerIconifyStatic } from 'iconify-static'; registerIconifyStatic(); </script> <iconify-icon icon="mdi:home"></iconify-icon>
Step 4: Build or run via Vite
Build or run with your usual command, e.g.
npm run dev npm run build
The plugin automatically detects icons used in your code and downloads them to static/_icons/.
Usage Examples
Svelte Component
<script> import { Icon } from 'iconify-static'; </script> <!-- Basic usage --> <Icon icon="mdi:home" /> <!-- With size --> <Icon icon="mdi:star" width="32" /> <Icon icon="mdi:star" width="2em" /> <!-- With color --> <Icon icon="mdi:heart" color="red" /> <Icon icon="mdi:heart" color="#ff6600" /> <!-- Inline with text --> <p>Click the <Icon icon="mdi:menu" inline /> icon to...</p> <!-- Transformations --> <Icon icon="mdi:arrow-right" hFlip /> <Icon icon="mdi:arrow-right" vFlip /> <Icon icon="mdi:arrow-right" rotate={1} /> <Icon icon="mdi:arrow-right" rotate="45deg" /> <!-- Multiple icon sets --> <Icon icon="ph:house" /> <Icon icon="carbon:home" /> <Icon icon="lucide:home" /> <Icon icon="tabler:home" />
Web Component
<!-- Basic usage --> <iconify-icon icon="mdi:home"></iconify-icon> <!-- With size --> <iconify-icon icon="mdi:star" width="32"></iconify-icon> <!-- With color --> <iconify-icon icon="mdi:heart" color="red"></iconify-icon> <!-- Inline with text --> <p>Click the <iconify-icon icon="mdi:menu" inline></iconify-icon> icon to...</p> <!-- Transformations --> <iconify-icon icon="mdi:arrow-right" flip="horizontal"></iconify-icon> <iconify-icon icon="mdi:arrow-right" rotate="1"></iconify-icon>
Props
Svelte Component Props
| Prop | Type | Default | Description |
|---|---|---|---|
icon |
string |
required | Icon name in "prefix:name" format (e.g., "mdi:home") |
width |
string | number |
"1em" |
Icon width (number or CSS unit) |
height |
string | number |
width |
Icon height (defaults to width) |
color |
string |
- | Icon color (any CSS color value) |
inline |
boolean |
false |
Align icon with text baseline |
hFlip |
boolean |
false |
Flip icon horizontally |
vFlip |
boolean |
false |
Flip icon vertically |
flip |
string |
- | "horizontal", "vertical", or "horizontal,vertical" |
rotate |
number | string |
- | Rotation: 0-3 for 90deg increments, or CSS angle like "45deg" |
onLoad |
function |
- | Callback when icon SVG loads |
Web Component Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
icon |
string |
required | Icon name in "prefix:name" format |
width |
string |
"1em" |
Icon width |
height |
string |
width |
Icon height |
color |
string |
- | Icon color |
inline |
(boolean attr) | - | Align icon with text baseline |
flip |
string |
- | "horizontal", "vertical", or "horizontal,vertical" |
rotate |
string |
- | Rotation: "0"-"3" for 90deg increments, or "45deg" |
Plugin Configuration
iconifyStatic({ // Directory where icons are saved (default: 'static/_icons') iconsDir: 'static/_icons', // Directory to scan for icon usage (default: 'src') sourceDir: 'src', // Remove unused icons from iconsDir (default: true) cleanup: true, // Log download/removal activity (default: false) verbose: false, // Scan for icons in all strings across your files with these prefixes (default: []) scanPrefixes: ['mdi', 'carbon'], })
Icon Detection
By default, the plugin detects icons used in the icon prop:
<Icon icon="mdi:home" /> <!-- Detected --> <Icon icon='carbon:search' /> <!-- Detected --> <Icon icon={`lucide:check`} /> <!-- Detected -->
Detecting Icons in Constants/Variables
For icons stored in constants or variables, use the scanPrefixes option:
// vite.config.ts iconifyStatic({ scanPrefixes: ['mdi', 'carbon'] })
This scans for any string matching "{prefix}:{name}" anywhere in your code:
// All of these are now detected when scanPrefixes includes 'mdi': const HOME_ICON = 'mdi:home'; const icons = { settings: 'mdi:cog', user: 'mdi:account' }; const list = ['mdi:star', 'mdi:heart'];
Preserving Unused Icons
By default, icons not found in source code are deleted. To preserve all icons:
iconifyStatic({ cleanup: false })
Deploying to a Subpath (GitHub Pages, etc.)
When deploying to a subpath (e.g., https://username.github.io/repo-name/), configure Vite's base option so icons are fetched from the correct URL.
SvelteKit (svelte.config.js):
const dev = process.argv.includes("dev"); export default { kit: { paths: { base: dev ? "" : "/repo-name", }, }, };
Plain Vite (vite.config.ts):
export default defineConfig({ base: process.env.NODE_ENV === "production" ? "/repo-name" : "/", plugins: [iconifyStatic()], });
Limitations
scanPrefixes limitations
The scanPrefixes option only detects string literals. These patterns are not detected:
// NOT detected - runtime concatenation const prefix = 'mdi'; const icon = prefix + ':home'; // NOT detected - template literal with variable const name = 'home'; const icon = `mdi:${name}`; // NOT detected - dynamic from API/database const icon = await fetchIconName();
For truly dynamic icons, either:
- Ensure all possible icons are referenced as string literals somewhere in code
- Use
cleanup: falseand manually manage icons instatic/_icons/
False positives
scanPrefixes may detect strings that look like icons but aren't:
// This would be detected if scanPrefixes includes 'mdi' const notAnIcon = 'mdi:this-is-not-real';
This results in a build error if the icon doesn't exist.
Supported Icon Sets
Any icon set available on Iconify works:
mdi- Material Design Iconscarbon- IBM Carbonlucide- Lucidetabler- Tabler Iconsph- Phosphor Iconsfluent- Fluent UI Icons- And many more...
Contributing & Raising Issues
Please feel free to raise issues for bugs or feature requests, but refrain from using AI to write them.
This project aims to be a drop-in replacement for Iconify's Svelte components, but may not be perfect; any differences are bugs, and detailed reports are appreciated.
PRs for fixes and improvements are welcomed, but I recommend raising an issue first if possible. AI generated changes may be accepted, but please make sure to validate, test, and check them for quality before raising a PR.
Support
Please make sure to support the Iconify team and the designers who make the icons you use!
License
MIT