Icons
StyleScribe provides a powerful icon system using CSS mask-image and design tokens. Icons are defined as tokens, discovered via CLI, and rendered with CSS that inherits color from the parent element.
Why CSS Mask-Image Icons?
Traditional approaches have limitations:
- Inline SVG: Verbose HTML, hard to change colors dynamically
- Font icons: Require external CDN, limited styling options
- IMG tags: Can't change color, separate HTTP requests
The mask-image approach solves all of these:
- Icons defined as design tokens (single source of truth)
- Inherit color via
currentColor(style with CSS) - Build-time optimization (SVGs embedded as base64)
- Swappable between icon packages without code changes
How It Works
┌─────────────────────────────────────────────────────────────────────┐
│ 1. Define icon token │
│ tokens/design-tokens.json: │
│ "delete": { "$value": "~bootstrap-icons/icons/trash.svg" } │
│ │
│ 2. Build resolves to base64 data URI │
│ --assets-icons-actions-delete: url("data:image/svg+xml;...") │
│ │
│ 3. CSS uses mask-image │
│ .icon--delete { mask-image: var(--assets-icons-actions-delete) } │
│ │
│ 4. Color inherits from parent │
│ <button style="color: red"> │
│ <span class="icon icon--delete"></span> ← Red icon! │
│ </button> │
└─────────────────────────────────────────────────────────────────────┘
Icon Sources
StyleScribe can use any SVG file that's resolvable from your project. This includes:
npm Packages (with ~ prefix)
The ~ prefix resolves to node_modules:
"delete": { "$value": "~bootstrap-icons/icons/trash.svg", "$type": "asset" }
Local SVG Files (relative paths)
Use relative paths from your tokens file:
"logo": { "$value": "../assets/icons/logo.svg", "$type": "asset" }
"custom-icon": { "$value": "./icons/my-icon.svg", "$type": "asset" }
Any npm Package with SVGs
Not limited to icon packages - any npm package containing SVGs works:
"some-icon": { "$value": "~some-package/dist/icon.svg", "$type": "asset" }
CLI Icon Discovery
The CLI icons commands provide convenient discovery for popular icon packages. These commands know where icons are located in each package:
| Package | Install | Icons | Website |
|---|---|---|---|
| Bootstrap Icons | npm i bootstrap-icons |
2000+ | icons.getbootstrap.com |
| Lucide | npm i lucide-static |
1400+ | lucide.dev |
| Heroicons | npm i heroicons |
450+ | heroicons.com |
| Feather Icons | npm i feather-icons |
280+ | feathericons.com |
| Tabler Icons | npm i @tabler/icons |
4500+ | tabler-icons.io |
Note: The CLI discovery only works with these packages. For other packages or local SVGs, you'll need to find the paths manually and add them directly to your tokens.
Install one or more packages to use CLI discovery:
npm install bootstrap-icons lucide-static
CLI: Discovering Icons
StyleScribe includes CLI commands to find and add icons without leaving your terminal.
List Installed Packages
stylescribe icons list
Shows all installed icon packages with their icon counts:
Installed icon packages:
Bootstrap Icons (bootstrap-icons)
Icons: 2048
Pattern: ~bootstrap-icons/icons/{name}.svg
Website: https://icons.getbootstrap.com/
Lucide (lucide-static)
Icons: 1423
Pattern: ~lucide-static/icons/{name}.svg
Website: https://lucide.dev/
Search for Icons
stylescribe icons search -q "trash"
Search across all installed packages:
Found 4 icons matching "trash":
trash (bootstrap-icons)
~bootstrap-icons/icons/trash.svg
trash-fill (bootstrap-icons)
~bootstrap-icons/icons/trash-fill.svg
trash (lucide-static)
~lucide-static/icons/trash.svg
trash-2 (lucide-static)
~lucide-static/icons/trash-2.svg
Discover All Icons in a Package
stylescribe icons discover -p bootstrap-icons --limit 100
Lists all icons in a specific package:
Bootstrap Icons (2048 icons)
Path pattern: ~bootstrap-icons/icons/{name}.svg
Website: https://icons.getbootstrap.com/
Icons:
0-circle 0-circle-fill 0-square 0-square-fill
1-circle 1-circle-fill 1-square 1-square-fill
...
Get Token Path for an Icon
stylescribe icons path -p bootstrap-icons -i trash
Outputs the exact token format to add to your design tokens:
Icon path:
~bootstrap-icons/icons/trash.svg
Token format (W3C DTCG):
{
"assets": {
"icons": {
"your-category": {
"trash": {
"$value": "~bootstrap-icons/icons/trash.svg",
"$type": "asset",
"$description": "Description here"
}
}
}
}
}
Show All Supported Packages
stylescribe icons supported
Adding Icons to Your Design System
Step 1: Find the Icon
stylescribe icons search -q "edit"
Step 2: Get the Token Path
stylescribe icons path -p lucide-static -i pencil
Step 3: Add to Design Tokens
Edit tokens/design-tokens.json:
{
"assets": {
"icons": {
"actions": {
"edit": {
"$value": "~lucide-static/icons/pencil.svg",
"$type": "asset",
"$description": "Edit action icon"
},
"delete": {
"$value": "~bootstrap-icons/icons/trash.svg",
"$type": "asset",
"$description": "Delete action icon"
}
},
"navigation": {
"home": {
"$value": "~lucide-static/icons/home.svg",
"$type": "asset"
},
"menu": {
"$value": "~lucide-static/icons/menu.svg",
"$type": "asset"
}
}
}
}
}
Step 4: Use in Your Icon Component
The build generates CSS variables like --assets-icons-actions-edit. Use them in your icon component:
.ds-icon {
&--edit {
mask-image: var(--assets-icons-actions-edit);
-webkit-mask-image: var(--assets-icons-actions-edit);
}
&--delete {
mask-image: var(--assets-icons-actions-delete);
-webkit-mask-image: var(--assets-icons-actions-delete);
}
}
Creating an Icon Component
Here's a complete icon component using the mask-image technique:
/**
* @title Icon
* @description Stylable icons using CSS mask-image
* @group Primitives
* @variations delete, edit, add, search, home, menu
*/
.ds-icon {
/* ===== Component Tokens ===== */
--icon-size: 1em;
--icon-color: currentColor;
/* ===== Base Styles ===== */
display: inline-block;
width: var(--icon-size);
height: var(--icon-size);
vertical-align: middle;
flex-shrink: 0;
/* Icon inherits color from parent */
background-color: var(--icon-color);
/* Common mask properties */
mask-size: contain;
mask-repeat: no-repeat;
mask-position: center;
-webkit-mask-size: contain;
-webkit-mask-repeat: no-repeat;
-webkit-mask-position: center;
/* ===== Icon Variations ===== */
&--delete {
mask-image: var(--assets-icons-actions-delete);
-webkit-mask-image: var(--assets-icons-actions-delete);
}
&--edit {
mask-image: var(--assets-icons-actions-edit);
-webkit-mask-image: var(--assets-icons-actions-edit);
}
&--add {
mask-image: var(--assets-icons-actions-add);
-webkit-mask-image: var(--assets-icons-actions-add);
}
&--search {
mask-image: var(--assets-icons-actions-search);
-webkit-mask-image: var(--assets-icons-actions-search);
}
/* ===== Size Modifiers ===== */
&--sm { --icon-size: 0.75em; }
&--lg { --icon-size: 1.5em; }
&--xl { --icon-size: 2em; }
}
Using Icons in HTML
Basic Usage
<span class="icon icon--delete"></span>
<span class="icon icon--edit"></span>
<span class="icon icon--search"></span>
With Sizes
<span class="icon icon--home icon--sm"></span> <!-- Small -->
<span class="icon icon--home"></span> <!-- Default (1em) -->
<span class="icon icon--home icon--lg"></span> <!-- Large -->
<span class="icon icon--home icon--xl"></span> <!-- Extra large -->
Colored Icons
Icons inherit color from their parent. Style with CSS:
<!-- Inherit parent color -->
<button style="color: red;">
<span class="icon icon--delete"></span> Delete
</button>
<!-- Direct styling -->
<span class="icon icon--success" style="color: green;"></span>
<span class="icon icon--warning" style="color: orange;"></span>
<span class="icon icon--error" style="color: red;"></span>
In Buttons
<button class="btn btn--primary">
<span class="icon icon--add"></span>
Add Item
</button>
<button class="btn btn--danger">
<span class="icon icon--delete"></span>
Delete
</button>
Icon Swappability
A key benefit of the token approach: swap icon packages without changing components.
// Original: using Bootstrap Icons
"delete": { "$value": "~bootstrap-icons/icons/trash.svg" }
// Switch to Lucide: same token name, different source
"delete": { "$value": "~lucide-static/icons/trash.svg" }
// Switch to Heroicons
"delete": { "$value": "~heroicons/24/outline/trash.svg" }
Your components stay the same:
&--delete {
mask-image: var(--assets-icons-actions-delete);
}
Organizing Icon Tokens
Structure your icons by category for better maintainability:
{
"assets": {
"icons": {
"actions": {
"add": { "$value": "~lucide-static/icons/plus.svg", "$type": "asset" },
"edit": { "$value": "~lucide-static/icons/pencil.svg", "$type": "asset" },
"delete": { "$value": "~lucide-static/icons/trash.svg", "$type": "asset" },
"save": { "$value": "~lucide-static/icons/save.svg", "$type": "asset" },
"cancel": { "$value": "~lucide-static/icons/x.svg", "$type": "asset" },
"search": { "$value": "~lucide-static/icons/search.svg", "$type": "asset" }
},
"navigation": {
"home": { "$value": "~lucide-static/icons/home.svg", "$type": "asset" },
"back": { "$value": "~lucide-static/icons/arrow-left.svg", "$type": "asset" },
"forward": { "$value": "~lucide-static/icons/arrow-right.svg", "$type": "asset" },
"menu": { "$value": "~lucide-static/icons/menu.svg", "$type": "asset" },
"external": { "$value": "~lucide-static/icons/external-link.svg", "$type": "asset" }
},
"status": {
"success": { "$value": "~lucide-static/icons/check-circle.svg", "$type": "asset" },
"warning": { "$value": "~lucide-static/icons/alert-triangle.svg", "$type": "asset" },
"error": { "$value": "~lucide-static/icons/x-circle.svg", "$type": "asset" },
"info": { "$value": "~lucide-static/icons/info.svg", "$type": "asset" },
"loading": { "$value": "~lucide-static/icons/loader.svg", "$type": "asset" }
},
"social": {
"user": { "$value": "~lucide-static/icons/user.svg", "$type": "asset" },
"users": { "$value": "~lucide-static/icons/users.svg", "$type": "asset" },
"mail": { "$value": "~lucide-static/icons/mail.svg", "$type": "asset" },
"share": { "$value": "~lucide-static/icons/share.svg", "$type": "asset" }
}
}
}
}
This generates CSS variables:
--assets-icons-actions-add--assets-icons-actions-edit--assets-icons-navigation-home--assets-icons-status-success- etc.
Animated Icons
Add CSS animations for loading spinners:
.ds-icon {
&--loading {
mask-image: var(--assets-icons-status-loading);
-webkit-mask-image: var(--assets-icons-status-loading);
animation: icon-spin 1s linear infinite;
}
}
@keyframes icon-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
MCP Tools for Icons
If using Claude Code with MCP integration, these tools are available:
| Tool | Description |
|---|---|
stylescribe_icons_list() |
List installed icon packages |
stylescribe_icons_search(query) |
Search for icons by name |
stylescribe_icons_discover(package) |
List all icons in a package |
stylescribe_icons_get_path({ package, icon }) |
Get token path for an icon |
Example workflow:
> stylescribe_icons_search("arrow")
Found: arrow-left, arrow-right, arrow-up, arrow-down...
> stylescribe_icons_get_path({ package: "lucide-static", icon: "arrow-left" })
Path: ~lucide-static/icons/arrow-left.svg
Best Practices
Use Semantic Names
// Good: semantic names
"delete": { "$value": "~bootstrap-icons/icons/trash.svg" }
"edit": { "$value": "~lucide-static/icons/pencil.svg" }
// Avoid: icon-specific names
"trash": { "$value": "~bootstrap-icons/icons/trash.svg" }
"pencil": { "$value": "~lucide-static/icons/pencil.svg" }
Use em Units for Sizing
Icons scale with text when using em:
.ds-icon {
--icon-size: 1em; // Scales with parent font-size
}
// In a heading, icons are larger
h1 { font-size: 2rem; } // Icons inside = 2rem
// In body text, icons match
p { font-size: 1rem; } // Icons inside = 1rem
Always Include -webkit- Prefix
Safari requires the prefix:
mask-image: var(--assets-icons-actions-delete);
-webkit-mask-image: var(--assets-icons-actions-delete); // Required for Safari
Accessibility
For decorative icons, no additional markup needed. For meaningful icons:
<!-- Decorative (no label needed) -->
<button>
<span class="icon icon--delete"></span>
Delete
</button>
<!-- Icon-only button needs aria-label -->
<button aria-label="Delete item">
<span class="icon icon--delete"></span>
</button>
<!-- Or use visually-hidden text -->
<button>
<span class="icon icon--delete"></span>
<span class="visually-hidden">Delete</span>
</button>