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
bash
# 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
tsx
import { Icons } from "./icons";
<Icons.Check className="size-4 text-green-500" />
<Icons.X className="size-4 text-red-500" />
Commands
Initialize a Denji project. Creates
config and icons template.
bash
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 |
|---|
| , , , , , |
| Output path for icons file/folder |
| (single file) or (one file per icon) |
| / | TypeScript or JavaScript (default: TS) |
| , , , , or (no a11y attrs) |
| / | Use forwardRef (React/Preact only) |
| / | Track Iconify source via attr |
| Working directory |
Missing flags trigger interactive prompts.
Add icons from Iconify. Icons use
format.
bash
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 |
|---|
| Custom component name (single icon only) |
| Override a11y strategy for this icon |
| Preview what would be generated without writing any files |
| Working directory |
Icon naming:
becomes
(PascalCase). Override with
.
Adding an existing icon updates it in place.
skips file writes and hooks but still validates icon names,
, 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
Remove icons by component name. Aliases:
,
,
.
bash
npx denji remove Check
npx denji rm Check Home ArrowRight
| Flag | Description |
|---|
| Working directory |
List all icons in your project.
bash
npx denji list
npx denji list --display json
npx denji list --display toon
| Flag | Description |
|---|
| Output mode: (human-readable), , or |
| Working directory |
Shows component names and Iconify source (if
).
Default output:
Found 3 icon(s) in ./src/icons.tsx
Icons:
• Check (lucide:check)
• HomeOutline (mdi:home-outline)
• ArrowRight (lucide:arrow-right)
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 (
) uses
TOON format for machine-readable binary encoding.
Export a JSON manifest of all tracked icons.
bash
npx denji export # print to stdout
npx denji export --output icons.json
npx denji export --output # writes to denji-export.json
| Flag | Description |
|---|
| Write to file (default: if no path given) |
| Working directory |
Output format:
json
{
"version": 1,
"framework": "react",
"output": "./src/icons.tsx",
"icons": [
{ "name": "Home", "source": "mdi:home" },
{ "name": "Check", "source": "lucide:check" }
]
}
is included only when
(the default).
Bulk-add icons from a manifest JSON file, a plain text file, or stdin.
bash
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 |
|---|
| Preview without writing files |
| Working directory |
Icons without
format are skipped with a warning. JSON manifest entries without a
field are also skipped.
Remove all icons. Aliases:
,
.
bash
npx denji clear
npx denji clear --yes
| Flag | Description |
|---|
| Skip confirmation prompt |
| Working directory |
Config ()
The
field depends on how Denji is installed:
- Locally installed ():
"./node_modules/denji/configuration_schema.json"
- Not installed (using , , , ):
"https://denji-docs.vercel.app/configuration_schema.json"
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 |
|---|
| string | Required. , , , , , |
| string or object | Required. Path string or { type: "file"|"folder", path: "..." }
|
| boolean | Default: |
| string or false | , , , , |
| boolean | Default: . Adds attr |
| string[] | Restrict to specific Iconify prefixes. Empty/omitted = all allowed |
| object | Lifecycle hooks (see below) |
Output Modes
File mode (default for React, Preact, Solid, Qwik, Vue): All icons in one file.
json
{ "output": "./src/icons.tsx" }
Folder mode (required for Svelte, optional for others): One file per icon + barrel export.
json
{ "output": { "type": "folder", "path": "./src/icons" } }
Hooks
Run shell commands at lifecycle points:
json
{
"hooks": {
"preAdd": ["echo 'Adding icons...'"],
"postAdd": ["npx prettier --write ./src/icons.tsx"],
"preRemove": [],
"postRemove": [],
"preClear": [],
"postClear": [],
"preList": [],
"postList": []
}
}
Common Patterns
Dynamic Icons
tsx
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
tsx
// 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
json
{
"allowedLibraries": ["lucide"]
}
bash
npx denji add lucide:check # ✓ allowed
npx denji add mdi:home # ✗ Error: Icon "mdi:home" is not allowed. Allowed libraries: lucide
Formatting with Hooks
json
{
"hooks": {
"postAdd": ["npx biome check --write ./src/icons.tsx"],
"postRemove": ["npx biome check --write ./src/icons.tsx"]
}
}
Using forwardRef (React/Preact)
json
{
"framework": "react",
"react": { "forwardRef": true }
}
tsx
const ref = useRef<SVGSVGElement>(null);
<Icons.Check ref={ref} className="size-4" />
Framework Quick Reference
| Framework | Extensions | Default Output | Config Key | Notes |
|---|
| React | / | file | | option |
| Preact | / | file | | option (via ) |
| Solid | / | file | | Refs work natively as props |
| Qwik | / | file | | Uses in folder mode |
| Vue | / | file | | Uses render functions |
| Svelte | | folder (only) | | Svelte 5 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 |