Icons
for production
Zero bundle size. 11 stroke weights.
Global Edge Delivery via CDN.
- 1700+
- icons
- 3 KB
- gzip runtime
- 11
- stroke weights
- 365d
- immutable cache
11 stroke weights · one icon
Single asset on the server, weight is a query param. Proportions and anti-aliasing handled server-side — zero client runtime for the stroke math.
Why traditional icons hurt
Bloated Bundles
NPM icon packages add megabytes to your JS and tank LCP. Our CDN means 0 bytes in your bundle.
DOM Pollution
Inline SVGs bloat the DOM and duplicate on SSR. Mask-image keeps your markup clean.
The Stroke Gap
Standard libraries ship one weight. We deliver 11 — from hairline to bold accents.
How It Works
Stroke Quantization
Any `stroke` value is rounded to a 0.25 step on the server. Visually identical, but `?stroke=1.52` and `?stroke=1.5` now share a cache key — clients can't fragment the CDN with arbitrary combinations.
Immutable Caching
Every URL is versioned: /1.17.0/heart.svg?stroke=1.5 The browser caches for 365 days with zero revalidation. To update — change the version in the URL. Old versions stay alive. New version = new URL = new cache slot. Zero invalidation, zero downtime.
Browser Support
Web Components + CSS mask-image. Works in every modern browser — baseline since 2019.
- Chrome 77+
- Firefox 63+
- Safari 10.1+
- Edge 79+
Framework Integration
Copy-paste ready snippets for any stack
<script src="https://icon.createui.dev/1.17.0/createui-icons.js" defer></script> <createui-icon name="heart" size="24" stroke="1.5"></createui-icon> <createui-icon name="arrow-right" aria-label="Next"></createui-icon>
import '@createui-dev/icons';
import '@createui-dev/icons/types/react';
export function Button() {
return (
<button>
<createui-icon name="heart" size={20} stroke={1.5} />
Like
</button>
);
} <script setup>
import '@createui-dev/icons';
</script>
<template>
<button>
<createui-icon name="heart" :size="20" :stroke="1.5" />
Like
</button>
</template>
// vite.config.ts: mark <createui-icon> as a custom element
// vue({ template: { compilerOptions: {
// isCustomElement: (tag) => tag === 'createui-icon',
// } } }) <script>
import '@createui-dev/icons';
</script>
<button>
<createui-icon name="heart" size={20} stroke={1.5} />
Like
</button> import '@createui-dev/icons';
import '@createui-dev/icons/types/solid';
export function Button() {
return (
<button>
<createui-icon name="heart" size={20} stroke={1.5} />
Like
</button>
);
} /* Raw URL — for cases where you can't ship JS */
.icon-heart {
display: inline-block;
width: 24px;
height: 24px;
background-color: currentColor;
mask: url('https://icon.createui.dev/1.17.0/heart.svg?stroke=1.5') no-repeat center / contain;
-webkit-mask: url('https://icon.createui.dev/1.17.0/heart.svg?stroke=1.5') no-repeat center / contain;
} Explore Icons
vs. the alternatives
Bundle cost compared at 50 production icons.
| Metric | lucide-react | @iconify/react | react-icons | CreateUI CDN |
|---|---|---|---|---|
| Bundle footprint | +250 KB | +4 KB + on-demand HTTP | +45 KB (tree-shaken) | 0 KB |
| Runtime payload | Full package in JS | Async JSON per icon | Full package in JS | 3 KB gzip + SVG on request |
| Stroke weights | 1 | 1 | 1 | 11 |
| DOM nodes per icon | 1 SVG + paths | 1 SVG + paths | 1 SVG + paths | 0 (mask-image) |
| Caching | — | per-icon HTTP | — | immutable 365 days |
| Framework-agnostic | React only | React/Vue/Svelte | React only | any (Web Component) |
lucide-react numbers from unpkg.com/lucide-react esm.sh bundle; react-icons estimated for 50 icons after tree-shake; @iconify/react is a runtime wrapper + async fetches. CreateUI CDN is the measured gzipped runtime bundle.
FAQ
What if the CDN goes down?
Bundle and SVG endpoints are plain nginx static on a single VDS, plus 365-day browser HTTP cache. Icons already loaded survive any downtime. Paranoid mode: use the CSS snippet from the Integration section — it reads SVGs directly via URL and works even if the web component fails to load.
Does it work with SSR (Next.js, Nuxt, Remix, Astro)?
Yes. `<createui-icon>` is a custom element — it renders as an empty tag during SSR and hydrates on the client. The mask-image approach works fully at SSR — URLs in CSS are safe for any render pipeline. This site is built with Astro and uses both.
What about tree-shaking? I don't use all 1700 icons.
You don't need to. Your bundle only contains the 3 KB gzipped runtime — it can render any icon by name. Each SVG is fetched on first use, then served from cache. You pay exactly for what the user actually sees on screen.
What if Lucide renames or removes an icon?
Every version URL is immutable — `/1.11.0/foo.svg` never changes. Even if Lucide drops `foo` in 1.12.0, your production pinned to 1.11.0 keeps working. When you upgrade, you migrate once.
Can I self-host, no CDN?
Yes. The SVG storage is a flat content-addressable store (`versions/{ver}/{name}.svg`) and the Go server in `server/` is self-hostable. Point `@createui-dev/icons` at your own origin via the `icon.ts` constant. URL format stays the same.
How is it licensed?
SVG files — Lucide ISC license. Our runtime (`@createui-dev/icons`) — MIT. CDN infrastructure — public, free, no signup. Source and config on GitHub.
How current is the icon set?
Synced with Lucide every Monday 06:00 UTC. When Lucide ships a new version, a matching `@createui-dev/icons` release goes out automatically. Current version: see the footer.
Support the project
CreateUI Icons is maintained as open-source infrastructure. The CDN runs on a personal VDS; domain and traffic are paid out of pocket. If the library solves your problem, help keep it online. A single coffee ≈ one month of domain and traffic.
- CDN version
- v1.16.0
- npm latest (@createui-dev/icons)
- v1.16.0
- Last sync
- 5h ago
- Next sync
- in 7d