How to build a codepoint manifest from an icon name list
- Step 1Treat your icon name list as a tracked input — Keep the ordered names in a version-controlled file, one per line. This file is the input your build feeds to the assigner.
- Step 2Generate the mapping at the fixed start — Run the assigner with your canonical start (usually U+E000). The output is a `.css` file containing the CSS rules and the JSON manifest comment.
- Step 3Extract the JSON manifest — Parse the JSON object out of the trailing `/* JSON manifest: ... */` comment. It carries `start_hex`, `count`, and the `mappings` array.
- Step 4Fan the manifest out to consumers — Feed the `mappings` to your font builder (name + codepoint), your stylesheet generator (cssClass + cssEscape), and your docs generator (name + hex + htmlEntity). All three now agree by construction.
- Step 5Commit the manifest and diff it on every change — Check the manifest into version control. On each change, diff it — an intentional addition shows new entries and a higher `count`; an unexpected change to existing entries signals the name list was reordered.
- Step 6Fail the build on overflow or drift — If the name list overruns U+F8FF the tool throws — surface that as a build failure. Also assert the manifest diff is additive-only to keep codepoints stable across releases.
Manifest schema
The exact shape of the JSON object emitted in the trailing comment of the .css file.
| Field | Type | Example |
|---|---|---|
start_hex | String | U+E000 |
count | Number | 4 |
mappings[].name | String (original) | home |
mappings[].unicode | Number (decimal) | 57344 |
mappings[].hex | String | U+E000 |
mappings[].cssClass | String | .icon-home |
mappings[].cssEscape | String | \e000 |
mappings[].htmlEntity | String |  |
Which consumer reads which field
Each downstream artifact pulls the field that fits its format from the same manifest.
| Consumer | Reads | Why |
|---|---|---|
| Font builder config | name + unicode (or hex) | Maps a glyph source to a codepoint |
| Stylesheet generator | cssClass + cssEscape | Writes .icon-x::before { content } rules |
| Docs / cheat-sheet | name + hex + htmlEntity | Human-readable reference table |
| JS/TS icon enum | name + unicode | Type-safe codepoint constants |
Determinism guarantees for CI
What is and isn't guaranteed run-to-run. Determinism holds only when these inputs are fixed.
| Property | Guaranteed? | Condition |
|---|---|---|
| Same output for same input | Yes | Same ordered name list + same start |
| Stable existing codepoints | Yes | Append-only edits |
| No hidden state between runs | Yes | Tool is stateless |
| Stable codepoints after reorder | No | Reordering renumbers |
Cookbook
Integration recipes. Pseudocode shows how a build step parses and fans out the manifest; the manifest shape is exactly what the tool emits.
Extracting the manifest from the .css output
ExampleThe JSON lives inside a trailing comment. Slice it out and parse.
const css = await readGeneratedCss(); const m = css.match(/JSON manifest:\n([\s\S]*?)\n\*\//); const manifest = JSON.parse(m[1]); // manifest.start_hex, manifest.count, manifest.mappings
Generating a TypeScript icon enum
ExampleEmit type-safe codepoint constants from the manifest so app code can't reference a non-existent icon.
// codegen
let out = 'export const Icon = {\n';
for (const x of manifest.mappings) {
const key = x.name.replace(/[^A-Za-z0-9]/g, '_');
out += ` ${key}: ${x.unicode}, // ${x.hex}\n`;
}
out += '} as const;';
// Icon.home === 57344Feeding a font builder config
ExampleMany font builders accept a name -> codepoint map. Build it straight from the manifest.
const codepoints = Object.fromEntries(
manifest.mappings.map(x => [x.name, x.unicode])
);
// { home: 57344, user: 57345, ... }
// pass to your svgs-to-font / fantasticon configCI guard: assert additive-only changes
ExampleFail the build if a release moved an existing codepoint instead of appending.
const prev = JSON.parse(readFile('icons.lock.json'));
for (let i = 0; i < prev.mappings.length; i++) {
const a = prev.mappings[i], b = manifest.mappings[i];
if (!b || a.name !== b.name || a.unicode !== b.unicode)
fail(`codepoint moved at index ${i} (${a.name})`);
}Catching overflow as a build failure
ExampleIf the list outgrows the PUA the tool throws; wrap the call so CI reports it clearly.
try {
generateMapping(names, 'E000');
} catch (e) {
// 'Ran out of PUA space at U+F900 ...'
fail(`icon set exceeds BMP PUA: ${e.message}`);
}Edge cases and what actually happens
Every row below was probed against the live API. Some documented requirements (alphabetical axis order, numerical tuple order) are not actually enforced in practice — useful to know if you've been blaming the wrong thing for a 400.
Empty name list in the pipeline
Error — rejectedAn empty input throws Paste at least one icon name. In CI, guard against an empty source file so the failure message is yours, not a confusing tool error.
Manifest's unicode is decimal, not hex
Caution — type mismatchmappings[].unicode is a decimal integer (57344), while hex is U+E000. A builder expecting hex must read hex or cssEscape. Reading unicode as if it were hex corrupts the mapping.
CSS escape backslash inside JSON
Handled — but mind re-encodingIn the manifest, cssEscape serializes as \\e000 (escaped backslash in JSON). After JSON.parse it's the single-backslash \e000 your CSS needs. Don't double-unescape it when writing to a stylesheet.
Name list reordered by an auto-formatter
Breaking — silent renumberA tool that sorts lines (or an import sorter applied to the wrong file) reorders names and renumbers codepoints. Exclude the icon list from auto-formatters and assert additive-only diffs in CI.
Two names slugify to one class
Caution — collision in CSS outputDuplicate cssClass values produce colliding CSS selectors even though codepoints differ. Add a CI check that cssClass values are unique across the manifest.
List grows past U+F8FF
Error — out of spaceCrossing U+F8FF throws an out-of-space error naming the overflow codepoint. Treat it as a hard build failure and split the icon set rather than ignoring it.
Start hex read from config as a number
Caution — pass as stringThe start is parsed from a hex string. If your config stores E000 as a number it may be read as decimal or fail. Pass the start as the literal hex string E000 (a U+ prefix is also accepted).
Non-deterministic name source
Caution — breaks reproducibilityIf names come from a directory listing whose order isn't stable across OSes, the mapping changes between machines. Sort/canonicalize the source order ONCE and freeze it; don't re-sort each build.
Manifest committed without the CSS, or vice versa
Caution — drift riskBoth come from one generation, but if a developer hand-edits only the CSS the two diverge. Regenerate both together and let the manifest be the authority; lint the CSS against it.
Expecting the tool to emit a font or SVGs
Misconception — out of scopeThis step produces the addressing manifest and CSS only. The glyph artwork and the font binary come from your own font-build step; this tool never sees them.
Frequently asked questions
What's in the JSON manifest?
An object with start_hex, count, and a mappings array. Each mapping carries the original name, decimal unicode, U+ hex, cssClass, cssEscape, and htmlEntity. It lives in a trailing comment of the generated .css file.
Is the output deterministic enough for CI?
Yes. Assignment is sequential from a fixed start with no hidden state, so the same ordered name list and start always produce byte-identical output. That makes manifest diffs meaningful.
Why is unicode decimal and hex a string?
Different consumers want different forms — font builders often take integers, docs and CSS want hex. The manifest provides both so each reads what it needs without converting.
How do I extract the manifest from the .css file?
Match the /* JSON manifest: ... */ comment and JSON.parse the contents. A small regex slice is enough; see the cookbook example.
Can I generate a TypeScript icon enum from it?
Yes — map each mappings entry to a constant keyed by a sanitized name with its decimal unicode value. The cookbook shows the codegen.
How do I stop a release from silently moving codepoints?
Commit the manifest as a lockfile and assert in CI that existing entries are unchanged (additive-only). Any moved codepoint fails the build.
What happens if the icon set overflows the PUA?
The tool throws an out-of-space error at the overflow codepoint. Wrap the call and surface it as a build failure, then split the set.
Does this generate the font binary?
No. It produces the name-to-codepoint manifest and CSS. The font binary and glyph artwork come from your own font-build tooling. To inspect a built font, use glyph-inspector.
How should I pass the start codepoint from config?
As a hex string like E000 (a U+ prefix is accepted and stripped). Don't store it as a number — it's parsed as base-16.
What if two icon names collide into one CSS class?
They get distinct codepoints but a duplicate .icon-* selector. Add a CI assertion that all cssClass values in the manifest are unique.
Can I subset a real font to only the icons in my manifest?
Yes — feed the codepoints to font-subsetter to strip everything else, or check coverage with character-coverage-map.
Does the order of names in the file matter?
Critically. Order determines codepoints. Freeze a canonical order, append new names, and exclude the file from auto-formatters that might re-sort it.
Privacy first
Every JAD Font tool runs entirely in your browser using opentype.js and the wawoff2 WASM Brotli encoder. Your fonts never leave your device — verified by zero outbound network requests during processing.