@cdx-ui/icons
v0.0.1-alpha.15
Published
Cross-platform icon library for CDX UI, built on [Material Symbols](https://fonts.google.com/icons) (outlined style, weight 400, optical size 24). Renders via `react-native-svg` and targets iOS, Android, and Web from a single codebase.
Readme
@cdx-ui/icons
Cross-platform icon library for CDX UI, built on Material Symbols (outlined style, weight 400, optical size 24). Renders via react-native-svg and targets iOS, Android, and Web from a single codebase.
~3,800+ Material Symbols icons with outlined/filled variants, plus support for custom SVGs.
Installation
pnpm add @cdx-ui/iconsPeer dependencies
| Package | Version |
| ------------------ | ----------------------------------- |
| react | ^18.2.0 \|\| ^19.0.0 |
| react-native | >=0.76.0 (optional) |
| react-native-svg | ^13.0.0 \|\| ^14.0.0 \|\| ^15.0.0 |
Usage
import { Home, Favorite, Settings } from '@cdx-ui/icons';
// Outlined (default)
<Home size={24} color="blue" />
// Filled variant
<Favorite size={24} color="red" filled />
// Toggle between variants
<Home filled={isActive} />Each icon is a forwardRef component that accepts all standard SvgProps plus the props below.
Props
| Prop | Type | Default | Description |
| -------------------- | ------------------ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| size | number \| string | 24 | Sets both width and height. |
| color | string | 'currentColor' | Fill color applied to the icon. |
| filled | boolean | false | Renders the filled variant when true. Falls back to outlined if no filled data exists. |
| accessibilityLabel | string | — | When provided, the icon becomes informative (accessible={true}, accessibilityRole="image"). When omitted, the icon is decorative by default. |
| data-testid | string | — | Forwarded to the root <Svg> element for testing. |
All other props are spread onto the root <Svg> element, including standard React Native accessibility props.
Naming conventions
Every icon is available under three export patterns:
| Pattern | Example | Use case |
| ---------- | ---------- | ----------------------------------------------- |
| PascalCase | Home | Primary import |
| Suffixed | HomeIcon | Avoids collision with your own Home component |
| Prefixed | CdxHome | Namespace when mixing multiple icon libraries |
import { Home } from '@cdx-ui/icons';
import { HomeIcon } from '@cdx-ui/icons';
import { CdxHome } from '@cdx-ui/icons';Aliases
Icons can have metadata-driven aliases defined in icon-metadata.json. Aliases let you reference the same underlying icon under alternative names — useful for legacy compatibility, domain-specific terminology, or aligning with names used by other icon sets. The generation script produces alias re-exports alongside the standard exports, so aliases are zero-cost at runtime.
Each entry in icon-metadata.json maps a base icon name (matching its SVG filename) to an array of aliases. An alias can be a plain string or an object with deprecated: true and a deprecationReason to flag names that should be migrated away from.
// icon-metadata.json
{
"home": {
"aliases": [
"house",
{
"name": "old-home-icon",
"deprecated": true,
"deprecationReason": "Renamed to Home",
},
],
},
"settings": {
"aliases": ["gear", "preferences"],
},
}The config above produces the following behavior:
House,HouseIcon, andCdxHouseall resolve to theHomeiconGearandPreferencesresolve to theSettingsiconOldHomeIconresolves toHomebut shows a@deprecated Renamed to HomeJSDoc warning in your editor, nudging consumers toward the canonical name
import { House } from '@cdx-ui/icons'; // alias → Home
import { Gear } from '@cdx-ui/icons'; // alias → Settings
import { OldHomeIcon } from '@cdx-ui/icons'; // deprecated alias → Home (editor warning)Aliases that collide with an existing base icon name are silently skipped during generation to avoid duplicate exports.
Accessibility
Icons are decorative by default — hidden from assistive technology when no accessibilityLabel is provided. This is the correct behavior for icons next to text labels or inside labeled containers.
// Decorative (default) — screen readers skip this
<Home size={20} color={theme.colors.icon} />
// Informative — screen readers announce "Home, image"
<Home size={20} accessibilityLabel="Home" />| Scenario | What to do |
| --------------------------------- | ----------------------------------------------------------------- |
| Icon next to a text label | Leave as default (decorative) |
| Icon inside a labeled Button | Leave as default — the button's label covers it |
| Standalone icon conveying meaning | Add accessibilityLabel describing the icon's purpose in context |
| Icon-only button | Label the Pressable wrapper, not the icon |
Cross-platform behavior:
- iOS (VoiceOver): Announced as "{label}, image"
- Android (TalkBack): Announced as "{label}, graphic"
- Web (React Native Web): Renders
role="img"+aria-label
React Native >=0.76 aliases aria-label to accessibilityLabel, so either prop works.
For full details, see 08-accessibility.md.
Custom icons
Place custom SVGs in icons/custom/. The generation script reads from both icons/material/outlined/ and icons/custom/, so custom icons are treated identically to Material Symbols icons.
- Use
{name}.svgfor the outlined variant - Optionally add
{name}-fill.svgfor a filled variant - Custom icons should use
viewBox="0 -960 960 960"for consistency, or the generation script will use the SVG's own viewBox
Development
Downloading Material Symbols SVGs
SVGs are fetched directly from the Google Fonts CDN by a custom download script (scripts/download-icons.ts). The script queries Google's metadata API for the active icon list, then downloads only the outlined style (weight 400, opsz 24) with both fill states. Downloaded SVGs are not checked into git.
pnpm --filter @cdx-ui/icons download-iconsThis downloads ~7,600 SVGs (~3,800 icons × 2 fill states) to icons/material/outlined/. Pass --clean to re-download from scratch instead of skipping existing files.
Generating icon components
The generation script (scripts/generate-icons.ts) reads SVGs from both icons/material/outlined/ and icons/custom/, parses them with svgson, and writes TypeScript files to src/icons/ and src/aliases/.
pnpm --filter @cdx-ui/icons generateGenerated files include:
src/icons/{name}.ts— one file per icon with both outlined and filled path datasrc/icons/index.ts— barrel export of all iconssrc/aliases/aliases.ts— metadata-driven legacy name mappingssrc/aliases/suffixed.ts—{Name}Iconexports for every icon and aliassrc/aliases/prefixed.ts—Cdx{Name}exports for every icon and alias
Each generated icon file includes a base64-encoded @preview JSDoc tag, so hovering over an icon import in your editor shows a visual preview.
Building
pnpm --filter @cdx-ui/icons buildThis runs clean -> generate -> bob build. Output goes to lib/ with three targets:
lib/commonjs/— CJS moduleslib/module/— ESM moduleslib/typescript/— per-file.d.tsdeclarations
The package is compiled by react-native-builder-bob, consistent with all other workspace packages. Metro resolves the react-native field (src/index.ts) during development, so rebuilding is only needed for publishing or non-Metro consumers.
Package structure
icons/
├── icons/
│ ├── material/outlined/ # Downloaded SVGs (not checked in)
│ └── custom/ # Hand-authored custom SVGs
├── scripts/
│ ├── download-icons.ts # Downloads outlined SVGs from Google Fonts CDN
│ └── generate-icons.ts # SVG → TypeScript generation script
├── src/
│ ├── icons/ # Generated icon components
│ ├── aliases/ # Generated alias re-exports
│ ├── createCdxIcon.ts # Factory function
│ ├── defaultAttributes.ts # SVG defaults (viewBox, fill)
│ ├── types.ts # TypeScript types
│ └── index.ts # Main entry point
├── icon-metadata.json # Alias and deprecation metadata
├── package.json
├── tsconfig.json
└── tsconfig.build.jsonsrc/icons/ and src/aliases/ contain generated files (gitignored except for .gitkeep sentinels).
Architecture
The package follows the patterns established by lucide-react-native, adapted for Material Symbols. Key differences:
- Fill-based icons — Material Symbols icons use
fillexclusively (nostrokeattributes). Thecolorprop maps tofill. - 960x960 coordinate system —
viewBox="0 -960 960 960"instead of Lucide's0 0 24 24. Thesizeprop controls rendered dimensions; the viewBox handles internal coordinates. - Outlined/filled variants — Each icon bundles both variants via the
filledprop, modeling Material Symbols' fill axis. This halves the export count vs. separate components. - Decorative by default — Icons are hidden from assistive technology unless
accessibilityLabelis provided. - OTA compatibility —
childDefaultAttributesexplicitly setsfillon every child SVG element, working around an inheritance bug in CodePush/expo-updates.
For the full research and design rationale, see docs/research/lucide-react-native/.
