CLI that bundles a project's SVG icons into a single sprite and generates the matching TypeScript definitions that power @uxf/ui's <Icon> component.
Use it in any UXF web project that renders icons through @uxf/ui/icon. From a declarative config (icons.config.js) of inline SVGs and/or Font Awesome Pro references it produces an SVG sprite, one standalone SVG per icon, and a generated icons.ts that:
ICONS (per-icon { w, h } map) and ICONS_VERSION (md5 of the sprite), and@uxf/ui/icon/theme's IconsSet interface, so IconName (keyof IconsSet) autocompletes every icon you declared.This is a build-time dev tool run via the icons-gen binary. The runtime <Icon> component itself lives in @uxf/ui/icon, not here.
yarn add -D @uxf/icons-generatorRequires Node >= 24. Peer dependency: @uxf/core (11.114.0). The Font Awesome Pro adapter needs no extra packages — it streams icons from the kit at generation time, so it only needs the @awesome.me scope to resolve through your registry (see Providers).
Create icons.config.js in your project root:
/** @type {import('@uxf/icons-generator/src/types').IconsConfig} */
module.exports = {
generatedDirectory: "/public/icons-generated/",
icons: {
flame: {
width: 43,
height: 48,
data: `<path fill="#fff" d="M30.84 20.51a1.51 1.51 0 0 0-1.16-.71..." />`,
},
},
};
Run the generator:
icons-gen
Wire the generated ICONS + sprite into @uxf/ui and render icons (see Integration).
icons-gen [options]
| Flag | Alias | Default | Description |
|---|---|---|---|
--configFile | -c | icons.config.js | Path to the config file, resolved from cwd. |
--help | -h | — | Print help and exit. |
icons-gen --configFile=custom.icons.config.js
The config file exports an IconsConfig object via module.exports.
| Key | Type | Default | Required | Description |
|---|---|---|---|---|
icons | Partial<Record<string, SimpleIcon | SizedIcon | IconFromProviderFunction>> | — | Yes | Icons to generate, keyed by icon name (see Icon types). |
generatedDirectory | string | "/public/icons-generated/" | No | Output dir (relative to cwd) for the sprite and standalone SVG files. Must start/end with /. |
configDirectory | string | "/src/config/" | No | Output dir (relative to cwd) for the generated icons.ts and provider fallbacks. Must start/end with /. |
spriteFileName | string | "_icon-sprite.svg" | No | Sprite file name written into generatedDirectory. |
typeName | string | "IconsSet" | No | Name of the keyof typeof ICONS type exported by the generated file. |
typescript | boolean | true | No | Emit icons.ts vs icons.js. See Gotchas. |
moduleDefinition | ModuleDefinition | false | augments @uxf/ui/icon/theme (see below) | No | Controls the declare module type augmentation; false disables it. |
customDefinitionContent | string | — | No | Extra content appended verbatim to the end of the generated definition file. |
A single-size icon.
type SimpleIcon = {
data: string;
height: number;
width: number;
};
flame: {
width: 43,
height: 48,
data: `<path fill="#fff" d="M30.84 20.51a1.51 1.51 0 0 0-1.16-.71..." />`,
}
Different SVG data per pixel size. Each size produces its own sprite symbol (icon-sprite--<name>_<size>) and standalone file (<name>_<size>.svg).
type SizedIcon = Record<number, string>;
logo: {
24: `<path fill="#fff" d="..." />`,
48: `<path fill="#fff" d="..." />`,
}
A function that resolves an icon from a provider (e.g. faPro.icon(...)). See Providers.
type IconFromProviderFunction = (config: _IconsConfig) => { width: number; height: number; path: string };
moduleDefinition)type ModuleDefinition = {
moduleName: string;
typeName: string;
format: "type" | "interface";
};
Defaults to { moduleName: "@uxf/ui/icon/theme", typeName: "IconsSet", format: "interface" }. With these defaults the generated file emits:
declare module "@uxf/ui/icon/theme" {
interface IconsSet {
flame: true;
// ...one line per icon
}
}
This augmentation is what makes @uxf/ui/icon's IconName (keyof IconsSet) aware of your icons. Set moduleDefinition: false to skip it.
The faPro adapter resolves icons from a Font Awesome kit package (@awesome.me/kit-…), the
only distribution channel that carries every style FA 7 offers. Nothing is installed: at generation
time the CLI streams the kit tarball, pulls out just the SVGs your config names, and discards the
rest. A ~110 MB archive therefore leaves behind a few kilobytes and never lands in node_modules
or a Docker image.
For each "<namespace>.<icon-name>" the adapter tries, in order:
@fortawesome/*-svg-icons package — deprecated, kept so existing projects keep
building. Using one prints a warning; support is removed in a future major.<configDirectory>/icons-fallbacks/faPro.json — the snapshot written on every successful
resolve, so a project without Font Awesome access can still rebuild the icons it already has.The monolithic @fortawesome/fontawesome-pro is no longer supported at all: the adapter throws if
it finds it installed.
Kits live in the @awesome.me scope on Font Awesome's own registry, so that scope has to resolve
through our Verdaccio (which caches the tarballs, keeping FA bandwidth costs down). See
docs/recipes/fontawesome-kit-verdaccio.md for the server side, and
docs/migration/fa-pro-kit-stream.md for what a consuming project has to change.
Registry and credentials are read from the same .npmrc and .yarnrc.yml files your package
manager uses — npm-based and Yarn-based projects both work without extra configuration.
A namespace is the style directory inside the package, so "regular.calendar-check" resolves to
regular/calendar-check.svg. For the classic families it matches what the per-style packages used:
brands, regular, solid, light, thin, duotone, duotone-regular, duotone-light,
duotone-thin, and the sharp- and sharp-duotone- variants of each. Styles a kit adds on top
appear under their own directory name.
The exhaustive list is the generated FaProIconName union in src/fa-pro-types.ts, regenerated by
npm run icon-types:gen in this monorepo.
const { faPro } = require("@uxf/icons-generator/src/providers/fa-pro");
module.exports = {
generatedDirectory: "/public/icons-generated/",
icons: {
// keeps the default name, e.g. "faPro_brands.linkedin"
...faPro.adapter(["brands.linkedin"]),
// or assign a custom name
twitter: faPro.icon("brands.twitter"),
},
};
faPro.adapter([...]) names each icon faPro_<namespace>.<name>, while faPro.icon(...) lets you
assign a custom key. Both register the icon for the prefetch that runs before generation, so they
must be called while the config module is being loaded — which is what the examples above do.
The kit and its version are pinned in src/utils/_faProTarballCache.ts, next to the generated
FaProIconName union they belong to. Both ship in this package, so a project gets a matched pair and
never has to configure a version: bumping @uxf/icons-generator is how you move to a newer kit.
That also means a warm run needs no registry access at all, and that a kit republished upstream cannot change anybody's sprite behind their back.
Adopting a new kit version is one commit here:
UXF_FA_PRO_VERSION=latest npx tsx packages/icons-generator/scripts/generate-fa-pro-types.ts # what is out there?
# set DEFAULT_VERSION in src/utils/_faProTarballCache.ts to that version
npm run icon-types:gen
The generator refuses to write a union built from a version other than the pinned one, so the two cannot drift.
Fetched SVGs and the package's icon-name index are cached under
~/.cache/uxf-icons-generator/fa-pro/v<schema>/<package>_<version>/ — the package and version are
slugified, so the real directory looks like v2/_awesome.me_kit-0618a3d496_1.0.2. A run that introduces no new icon
performs no network I/O. Point UXF_FA_PRO_CACHE_ROOT somewhere inside the build directory to let
CI cache it; a version bump simply misses the cache and refills it.
| Variable | Default | Purpose |
|---|---|---|
UXF_FA_PRO_PACKAGE | @awesome.me/kit-0618a3d496 | Source package. Accepts any kit, or @fortawesome/fontawesome-pro. |
UXF_FA_PRO_VERSION | the pinned DEFAULT_VERSION | Override the version. latest asks the registry — for discovery, not builds. |
UXF_FA_PRO_CACHE_ROOT | ~/.cache/uxf-icons-generator | Where cached SVGs and the index live. |
Running icons-gen (re)writes:
<generatedDirectory>/<spriteFileName> — the SVG sprite: one <symbol id="icon-sprite--<name>"> per icon (sized icons: icon-sprite--<name>_<size>).<generatedDirectory>/<name>.svg — one standalone SVG per icon (sized: <name>_<size>.svg). SVGs for icons removed from the config are cleaned up on the next run.<configDirectory>/icons.ts — the definition file (see below).<configDirectory>/icons-fallbacks/<provider>.json — cached provider icon data (e.g. faPro.json).The definition file exports:
// this file is generated automatically, do not change anything manually in the contents of this file
export const ICONS_VERSION = "<md5 of the sprite file>";
export const ICONS = {
flame: { w: 43, h: 48 },
logo: [24, 48],
// ...
} as const;
export type IconsSet = keyof typeof ICONS; // name comes from `typeName`
declare module "@uxf/ui/icon/theme" {
// omitted when moduleDefinition: false
interface IconsSet {
flame: true;
// ...
}
}
@uxf/ui/iconRun icons-gen (wire it into a gen/prebuild script).
Pass the generated ICONS and sprite path to @uxf/ui's UiContextProvider. Because generatedDirectory lives under public/, the browser URL drops that segment (/public/icons-generated/… → /icons-generated/…):
import { UiContextProvider, UiContextType } from "@uxf/ui/context";
import { ICONS, ICONS_VERSION } from "@/config/icons";
const uiConfig: UiContextType = {
icon: {
iconsConfig: ICONS,
spriteFilePath: `/icons-generated/_icon-sprite.svg?v=${ICONS_VERSION}`,
},
// ...other UI context options (colorScheme, localeConfig, rasterImage, translationFn)
};
Render icons via @uxf/ui's <Icon>. The name prop autocompletes every generated icon thanks to the module augmentation:
import { Icon } from "@uxf/ui/icon";
<Icon name="flame" size={24} />;
(Optional) Preload the sprite:
<link as="image" href={`/icons-generated/_icon-sprite.svg?v=${ICONS_VERSION}`} rel="preload" type="image/svg+xml" />
<Icon> runtime component is @uxf/ui/icon; this package just generates the sprite and types.cwd-relative and need slashes. configDirectory and generatedDirectory are joined onto process.cwd(), so both must start and end with /.typescript: false currently has no effect. The generator always emits icons.ts — the flag falls back to true internally.faPro provider is a deep import: @uxf/icons-generator/src/providers/fa-pro (the published files preserve the src/ layout). The IconsConfig type is at @uxf/icons-generator/src/types.faPro.icon(...) and
faPro.adapter([...]) register their names when the config module loads, and the CLI resolves
them all in one streamed pass. Building an icon name inside a callback that runs later than config
load will not be prefetched.moduleName (@uxf/ui/icon/theme) unless you intentionally augment a different module; changing it breaks the @uxf/ui IconName inference.@uxf/ui/icon (the <Icon> component and IconsSet/IconName types).