denji
Managing SVG Icons with Denji
Denji converts Iconify SVG icons into typed framework components. Icons are fetched, optimized (SVGO), and generated as native components for your framework.
Core Workflow
# 1. Initialize project
npx denji init --framework react --output ./src/icons.tsx
# 2. Add icons (prefix:name format from Iconify)
npx denji add lucide:check lucide:x lucide:arrow-right
# 3. Use in your code
import { Icons } from "./icons";
<Icons.Check className="size-4 text-green-500" />
<Icons.X className="size-4 text-red-500" />
Commands
denji init
Initialize a Denji project. Creates denji.json config and icons template.
npx denji init
npx denji init --framework react --output ./src/icons.tsx
npx denji init --framework svelte --output ./src/icons --output-type folder
npx denji init --framework react --no-typescript --output ./src/icons.jsx
npx denji init --a11y hidden --forward-ref
| Flag | Description |
|---|---|
--framework <name> |
react, preact, solid, qwik, vue, svelte |
--output <path> |
Output path for icons file/folder |
--output-type <type> |
file (single file) or folder (one file per icon) |
--typescript / --no-typescript |
TypeScript or JavaScript (default: TS) |
--a11y <strategy> |
hidden, img, title, presentation, or false (no a11y attrs) |
--forward-ref / --no-forward-ref |
Use forwardRef (React/Preact only) |
--track-source / --no-track-source |
Track Iconify source via data-icon attr |
-c, --cwd <path> |
Working directory |
Missing flags trigger interactive prompts.
denji add <icons...>
Add icons from Iconify. Icons use prefix:name format.
npx denji add lucide:check
npx denji add lucide:check mdi:home radix-icons:cross-2
npx denji add lucide:star --name FavoriteStar
npx denji add lucide:info --a11y img
# Preview without writing files
npx denji add lucide:check mdi:home --dry-run
| Flag | Description |
|---|---|
--name <name> |
Custom component name (single icon only) |
--a11y <strategy> |
Override a11y strategy for this icon |
--dry-run |
Preview what would be generated without writing any files |
-c, --cwd <path> |
Working directory |
Icon naming: lucide:arrow-right becomes ArrowRight (PascalCase). Override with --name.
Adding an existing icon updates it in place.
--dry-run skips file writes and hooks but still validates icon names, allowedLibraries, and config. Useful for CI checks and PR previews.
◇ denji add
│
○ [dry-run] Would add Check → ./src/icons.tsx
○ [dry-run] Would add Home → ./src/icons.tsx
│
◇ Dry run complete — 2 icon(s) previewed, no files written
denji remove <icons...>
Remove icons by component name. Aliases: rm, delete, del.
npx denji remove Check
npx denji rm Check Home ArrowRight
| Flag | Description |
|---|---|
-c, --cwd <path> |
Working directory |
denji list
List all icons in your project.
npx denji list
npx denji list --display json
npx denji list --display toon
| Flag | Description |
|---|---|
--display <mode> |
Output mode: default (human-readable), json, or toon |
-c, --cwd <path> |
Working directory |
Shows component names and Iconify source (if trackSource: true).
Default output:
Found 3 icon(s) in ./src/icons.tsx
Icons:
• Check (lucide:check)
• HomeOutline (mdi:home-outline)
• ArrowRight (lucide:arrow-right)
JSON output (--display json):
{
"count": 3,
"output": "./src/icons.tsx",
"icons": [
{ "name": "Check", "source": "lucide:check" },
{ "name": "HomeOutline", "source": "mdi:home-outline" },
{ "name": "ArrowRight", "source": "lucide:arrow-right" }
]
}
TOON output (--display toon) uses TOON format for machine-readable binary encoding.
denji export
Export a JSON manifest of all tracked icons.
npx denji export # print to stdout
npx denji export --output icons.json
npx denji export --output # writes to denji-export.json
| Flag | Description |
|---|---|
--output [path] |
Write to file (default: denji-export.json if no path given) |
-c, --cwd <path> |
Working directory |
Output format:
{
"version": 1,
"framework": "react",
"output": "./src/icons.tsx",
"icons": [
{ "name": "Home", "source": "mdi:home" },
{ "name": "Check", "source": "lucide:check" }
]
}
source is included only when trackSource: true (the default).
denji import
Bulk-add icons from a manifest JSON file, a plain text file, or stdin.
npx denji import icons.json # from denji export manifest
npx denji import icons.txt # one prefix:name per line
echo "mdi:home\nlucide:check" | npx denji import # from stdin
npx denji import icons.json --dry-run
| Flag | Description |
|---|---|
--dry-run |
Preview without writing files |
-c, --cwd <path> |
Working directory |
Icons without prefix:name format are skipped with a warning. JSON manifest entries without a source field are also skipped.
denji clear
Remove all icons. Aliases: clr, reset.
npx denji clear
npx denji clear --yes
| Flag | Description |
|---|---|
-y, --yes |
Skip confirmation prompt |
-c, --cwd <path> |
Working directory |
Config (denji.json)
The $schema field depends on how Denji is installed:
- Locally installed (
npm i -D denji):"./node_modules/denji/configuration_schema.json" - Not installed (using
npx,bunx,pnpx,yarn dlx):"https://denji-docs.vercel.app/configuration_schema.json"
{
"$schema": "./node_modules/denji/configuration_schema.json",
"framework": "react",
"output": "./src/icons.tsx",
"typescript": true,
"a11y": "hidden",
"trackSource": true,
"allowedLibraries": ["lucide"],
"react": {
"forwardRef": true
},
"hooks": {
"postAdd": ["npx biome check --write ./src/icons.tsx"],
"postRemove": ["npx biome check --write ./src/icons.tsx"]
}
}
Key fields:
| Field | Type | Description |
|---|---|---|
framework |
string | Required. react, preact, solid, qwik, vue, svelte |
output |
string or object | Required. Path string or { type: "file"|"folder", path: "..." } |
typescript |
boolean | Default: true |
a11y |
string or false | hidden, img, title, presentation, false |
trackSource |
boolean | Default: true. Adds data-icon attr |
allowedLibraries |
string[] | Restrict to specific Iconify prefixes. Empty/omitted = all allowed |
hooks |
object | Lifecycle hooks (see below) |
Output Modes
File mode (default for React, Preact, Solid, Qwik, Vue): All icons in one file.
{ "output": "./src/icons.tsx" }
Folder mode (required for Svelte, optional for others): One file per icon + barrel export.
{ "output": { "type": "folder", "path": "./src/icons" } }
Hooks
Run shell commands at lifecycle points:
{
"hooks": {
"preAdd": ["echo 'Adding icons...'"],
"postAdd": ["npx prettier --write ./src/icons.tsx"],
"preRemove": [],
"postRemove": [],
"preClear": [],
"postClear": [],
"preList": [],
"postList": []
}
}
Common Patterns
Dynamic Icons
import { Icons, type IconName, type IconProps } from "./icons";
function DynamicIcon({ name, ...props }: { name: IconName } & IconProps) {
const Icon = Icons[name];
return <Icon {...props} />;
}
<DynamicIcon name="Check" className="size-4" />
Accessibility
// Decorative icon (hidden from screen readers)
<button>
<Icons.Check aria-hidden="true" />
Save
</button>
// Semantic icon (announced by screen readers)
<Icons.Check role="img" aria-label="Success" />
// Icon-only button
<button aria-label="Close">
<Icons.X aria-hidden="true" />
</button>
Restricting Icon Sources
{
"allowedLibraries": ["lucide"]
}
npx denji add lucide:check # ✓ allowed
npx denji add mdi:home # ✗ Error: Icon "mdi:home" is not allowed. Allowed libraries: lucide
Formatting with Hooks
{
"hooks": {
"postAdd": ["npx biome check --write ./src/icons.tsx"],
"postRemove": ["npx biome check --write ./src/icons.tsx"]
}
}
Using forwardRef (React/Preact)
{
"framework": "react",
"react": { "forwardRef": true }
}
const ref = useRef<SVGSVGElement>(null);
<Icons.Check ref={ref} className="size-4" />
Framework Quick Reference
| Framework | Extensions | Default Output | Config Key | Notes |
|---|---|---|---|---|
| React | .tsx/.jsx |
file | react |
forwardRef option |
| Preact | .tsx/.jsx |
file | preact |
forwardRef option (via preact/compat) |
| Solid | .tsx/.jsx |
file | solid |
Refs work natively as props |
| Qwik | .tsx/.jsx |
file | qwik |
Uses component$() in folder mode |
| Vue | .ts/.js |
file | vue |
Uses h() render functions |
| Svelte | .svelte |
folder (only) | svelte |
Svelte 5 $props() runes |
Deep-Dive References
| Reference | Content |
|---|---|
| references/configuration.md | Full config schema, all framework options, output normalization |
| references/framework-patterns.md | Per-framework code examples, file vs folder imports, TypeScript types |
More from fellipeutaka/leon
docker
|
85commit-work
Create high-quality git commits: review/stage intended changes, split into logical commits, and write clear commit messages (including Conventional Commits). Use when the user asks to commit, craft a commit message, stage changes, or split work into multiple commits.
42solid
|
33ai-repo-setup
|
31tanstack-query
|
30motion
|
30