Nuxt-Compose-Icons
CI coverage
CI gates every PR on unit + e2e tests, lint, and a full build (module, playground, showcase, docs) across Node 22 & 24 — not just formatting. → Latest run
flowchart LR
L[Lint · Style · Format] --> B[Build<br/>module · playground · showcase · docs]
T[Unit + E2E Tests] --> B
B -->|push to main| D[Deploy docs & showcase]This module generates fully customizable Vue components from your initial raw SVG files at build time, and gives you:
- 🧩 The flexibility of raw SVG
- 🎨 Theming control:
- At build time via configuration
- At runtime via CSS variables
- 🏔
️Native Nuxt auto-import support
For building design systems or simply use in-house icons.
📖 Documentation and advanced configuration | 🗺 Potential features and Roadmap
📦 Installation
pnpm add nuxt-compose-iconsnpm install nuxt-compose-iconsyarn add nuxt-compose-icons🛠 Quick start
1. Add to nuxt.config.ts:
export default defineNuxtConfig({
modules: ['nuxt-compose-icons'],
composeIcons: {
pathToIcons: './assets/icons',
},
});2. Drop your SVGs in ./assets/icons
3. Use them anywhere — no imports needed:
<template>
<ArrowUpIcon size="md" color="var(--primary)" />
<UserBadgeIcon size="lg" fill="currentColor" />
</template>That's it. Every .svg becomes a typed, auto-imported Vue component.
🎯 Motivation
Existing icon solutions often force trade-offs between DX, accessibility, and flexibility:
- Third-party libraries → limited customization
- Manual Vue components → repetitive and hard to scale
- SVG loaders → flexible but lack structure and typing
| Feature | Third-party Libraries | Manual Vue Components | SVG Loaders (vite-svg-loader) | Nuxt Compose Icons |
|---|---|---|---|---|
| Setup | ✅ Easy | ⚠️ Manual | ⚠️ Requires config | ✅ Minimal |
| Source of truth | External package | Vue files | SVG files | SVG files |
| SVG output | Clean (often wrapped) | Custom | Inline | Clean, no wrappers |
| SVG control | Often abstracted | ✅ Full | ✅ Full | ✅ Full |
| Theming | ⚠️ Prop-based, limited | ✅ Manual CSS | ✅ CSS-based | ✅ CSS variables + props |
| Naming consistency | Library-defined | Developer-defined | File-based | Deterministic, file-based |
| Typing | ✅ Provided | ✅ Manual | Depends on setup | ✅ Generated & inferred |
| Scaling | Dependent on library updates | Maintenance-heavy | Flexible but unstructured | Structured, build-generated |
| Nuxt integration | ✅ Works | ✅ Auto-importable | ⚠️ Requires configuration | ✅ Native auto-import |
@nuxt/icon is the most common example of the "Third-party Libraries" column above. nuxt-icons doesn't fit that column — it's bring-your-own-SVG like this module — but it's a runtime name-lookup (<nuxt-icon name="..." /> injecting SVG markup at render time) rather than generating standalone components, so it lands on the same side of that specific distinction as @nuxt/icon.
Nuxt also has an excellent official icon module, @nuxt/icon — for a huge, ready-made icon set with zero setup, use it. This module solves a different problem: turning your own SVG files into standalone, ownable Vue components, for a design system or an in-house icon library, without the trade-offs above.
→ How this compares to @nuxt/icon · Common Approaches
Features
- SVG to Vue Component at build time — one component per
.svgfile, named in PascalCase or kebab-case with optional prefix/suffix - Auto-registered in Nuxt — no manual imports, type-safe usage in
<template> - No wrappers — the component root is a single
<svg>element, original attributes preserved - Theming via CSS Custom Properties —
fill,stroke,stroke-widthbecomevar(--icon-*, original), overridable via props, cascading styles, or scoped CSS - Developer Experience — full autocompletion/type-checking, and Vue DevTools support since generated icons are real components
<!-- Input: user-badge.svg -->
<svg fill="#000" stroke="#fff" stroke-width="2">
<path d="..." />
</svg><!-- Output: UserBadgeIcon.vue -->
<template>
<svg
fill="var(--icon-fill, #000)"
stroke="var(--icon-stroke, #fff)"
stroke-width="var(--icon-stroke-width, 2)"
>
<path d="..." />
</svg>
</template><!-- Theme it -->
<template>
<UserBadgeIcon stroke="blue" fill="red" size="lg" />
</template>→ Full feature list with examples
Own your components
Each icon is a real, standalone Vue component — not a runtime lookup like @nuxt/icon's <Icon name="my:user-badge" /> or nuxt-icons' <nuxt-icon name="user-badge" />. That distinction matters once icons need to live outside a single Nuxt app:
- The generated
.vue/.tsfiles are committed to your repo, versioned like any other component - A UI library can ship
<UserBadgeIcon />directly — apps that consume the UI library never install or configurenuxt-compose-iconsthemselves (the UI library still does, as a normal dependency, since the generated components import from it) - Set
component.destDirand acompose-icons.cssfile is generated alongside the components, with your project's actual theme baked in — commit both, and that's everything a downstream consumer needs for full theming - Add
component.hasIndexFile: truefor anindex.tsbarrel that re-exports every icon — one import for the whole set - Add a new icon, rebuild — no separate publish/sync step to keep a design system's icon set current
→ Monorepo guide — with a live example repo (Nx + pnpm)