A step-by-step guide for designers adding to the dot icon font. Illustrations, integration logos and task icons live in dot-illustrations.
The font is built from this repo, not from IcoMoon.
fonts/svg/ and one line per icon in icons.json.npm run build writes everything else: fonts/dot.woff2, dot.woff, dot.ttf and dot.svg, index.css, demo/icons.js and selection.json.selection.json. The build writes selection.json for one more release, as a way back to IcoMoon, and then stops. Font files copied from an IcoMoon export fail the check on every pull request.node --version.A clone of the repo, with its tools installed:
git clone https://github.com/digital-ai/dot-icons.git
cd dot-icons
npm ci
npm run add-icon, Upload New in the demo, npm run build and Check icons check every SVG against these rules, and stop one that breaks them with one of these messages:
| Message | What to do |
|---|---|
Strokes don't survive in a font. In Figma, use Outline stroke, then Flatten. |
Select the icon, use Outline stroke, then Flatten. Without this check, a stroke-only export builds an invisible icon. |
A font glyph has one colour. Give every shape the same fill. |
Give every shape the same fill. A shape with no fill of its own counts as a colour too: a browser hides it, but the font would fill it in. |
Embedded images can't go in a font. Export vector shapes only. |
Redraw the image as vector shapes. |
Gradients can't go in a font. Use a solid fill. |
Change the fill to a solid colour. |
Text has to be outlined. In Figma, use Outline text, then Flatten. |
Use Outline text, then Flatten. |
Clips and masks can't go in a font. Flatten the icon in Figma. |
Flatten the masked or clipped layers. The clip of the frame itself is fine. |
Transparency can't go in a font. Use full opacity. |
Set the layer and fill opacity to 100%. |
The SVG has no filled shape. |
Export the frame with the icon in it, and give its shapes a fill. |
The viewBox is 24 × 20. Icons must be square: export the square frame from Figma, such as 24 × 24. |
Export the square frame, not the shape inside it. Only add-icon, Upload New and Check icons check this, for new icons: npm run build doesn’t, and a redrawn icon keeps the width it has. |
The SVG has no viewBox. Icons must be square: export the square frame from Figma, such as 24 × 24. |
Export the frame itself as SVG, so the file has a viewBox. Checked for new icons, as above. |
The SVG is 210,512 bytes, over the 200 KiB limit. Export a simpler shape. |
Simplify the shape, and remove hidden layers before you export. |
The SVG links to an outside file. Export vector shapes only. |
Remove the linked file from the frame. |
The file isn't a valid SVG. Export it from Figma again. |
Export it again as SVG, and don’t edit the file by hand. |
One rule only warns, and the icon is still added: Holes may fill in, because fonts use the nonzero rule. Check the icon in the demo. Figma often writes fill-rule="evenodd". Look at the icon in the demo, and if a hole has filled in, ask in #dot-components.
Check the icon in the demo every time (step 4 below). The rules catch what a font can’t draw, not whether the icon looks right.
Start a branch from an up-to-date main. Never work on main itself.
git checkout main
git pull
git checkout -b feat/add-my-new-icon
Add the icon, with the exported file, its id and its category:
npm run add-icon -- ~/Downloads/my-new-icon.svg my-new-icon actions
icon-my-new-icon. See Naming rules.actions, ai, communication, devops, files, layout, logos, misc, navigation, people, security, status.add-icon checks the SVG against the rules above, copies it unchanged to fonts/svg/my-new-icon.svg, adds a line to icons.json with the next free code, and runs npm run build. Then it prints what it did:
Added my-new-icon
code \ea3b (next free)
category actions
glyph width 1024, bounds [85, 21, 939, 875]
warnings none
changed fonts/svg/my-new-icon.svg (new), icons.json, fonts/dot.svg,
fonts/dot.ttf, fonts/dot.woff, fonts/dot.woff2, index.css,
demo/icons.js, selection.json
Check it in the demo, then commit these files and open a pull request.
Nothing has been committed.
If anything is wrong, such as a taken id, an unknown category or an SVG that breaks a rule, it says what to fix and changes no file. It refuses to run on main.
To add the files by hand instead, save the SVG as fonts/svg/<id>.svg, add one line at the end of icons.json with a comma on the line before it, and run npm run build. The code is one above the last line’s code, in hex: after ea3a comes ea3b, after ea3f comes ea40.
{"id":"forward-outline","code":"ea3a","category":"navigation","aliases":[]},
{"id":"my-new-icon","code":"ea3b","category":"actions","aliases":[]}
Optional: run the pull request check on your laptop before you push:
git worktree add ../dot-icons-main origin/main
npm run check -- --base ../dot-icons-main
npm test
git worktree remove ../dot-icons-main
Icon check passed. means CI will pass too.
Look at the icon in the demo. Serve the repo and open http://localhost:8000/demo/, then search for your id:
python3 -m http.server 8000
Commit the SVG, icons.json and every file the build changed, then push and open a pull request:
git add fonts/svg/my-new-icon.svg icons.json fonts/dot.* index.css demo/icons.js selection.json
git commit -m "feat: add my-new-icon icon"
git push -u origin feat/add-my-new-icon
Commit messages use the type: description form, such as feat: add my-new-icon icon or fix: redraw the learning icon.
When Check icons is green, post the pull request in #dot-components on Slack for review. A maintainer merges it.
fonts/svg/<id>.svg. Keep the id and the code.npm run build, commit, push and open a pull request.<id> (\<code>) changed shape. If the redraw is intended, add the glyph-change label.glyph-change label to the pull request (in the right-hand sidebar, under Labels). Ask a maintainer if you can’t. The check runs again and passes, and its summary shows how far the outline moved.The label only allows a change of shape. Removing an icon, giving it another code or changing the font’s metrics fails with or without it.
An alias is another class name for the same glyph, such as expander for key. Add the name to the icon’s aliases list:
{"id":"edit","code":"e900","category":"actions","aliases":["pencil"]}
Then run npm run build, commit and open a pull request. Both icon-edit and icon-pencil work afterwards.
Don’t rename an id. Products use every id as a class name, so a published id can’t change. Changing learning to learning-path fails the check, even with learning kept as an alias. To give an icon a better name, add it as an alias.
my-new-icon. No capitals, spaces or underscores. A few old ids have capitals; new ones can’t.logos category holds Digital.ai product logos as one-colour glyphs, such as logo-release. Full-colour logos of the tools we integrate with go in dot-illustrations as integration logos.Check icons rebuilds the pull request and compares it with main. Its three steps fail for these reasons:
| Step | Message | What to do |
|---|---|---|
| Build is current | Generated files are out of date: <files>. Run npm run build and commit the result. |
Run npm run build and commit what changes. It also fails when a generated file was edited by hand. |
Nothing was built. Fix these SVGs first: and then fonts/svg/<id>.svg: with a message from Export the icon from Figma |
Fix the icon in Figma as that table says, export it again over the old file, and run npm run build. |
|
| Nothing removed or changed | fonts/svg/<id>.svg: The viewBox is 24 × 20. Icons must be square: … |
Export the square frame. Only new icons need a square viewBox. |
<id>: new ids must be lowercase kebab-case, like my-new-icon. |
Rename the SVG and the icons.json id. |
|
"my_icon" can't be a class name: use letters, digits and single hyphens. |
Same. | |
<id>: category "<name>" doesn't exist. Use one of: actions, ai, … |
Use a category from the list. | |
Code ea3b is used by both <id> and <id>. |
Take the next free code. | |
Code <code> is taken by <id>. Change it to <next> and run npm run build. |
Use the code it suggests. | |
icons.json must be sorted by code: … |
Move your line to the end of the file. | |
fonts/svg/<id>.svg has no entry in icons.json. |
Add the line to icons.json. |
|
<id> has no fonts/svg/<id>.svg. |
Add the SVG, or check that the file name matches the id. | |
<id> (\<code>) changed shape. If the redraw is intended, add the glyph-change label. |
Add the label if the redraw is intended; otherwise put the old SVG back. | |
icon-<name> (\<code>) was removed. Published icons can't be removed or renumbered. |
Put the icon back. | |
| Tests | A test name with not ok |
Ask in #dot-components; this one isn’t about your icon. |
A missing comma in icons.json stops the build with a line like Expected ',' or ']' after array element in JSON at position 21406 (line 318 column 1). The comma is missing at the end of the line before the one it names.
npm run build, then commit and push. The check runs again on its own.The Upwind checks on the same pull request are security scans and don’t look at icons.
CI: bumps version to 1.1.77 [skip ci]), makes a GitHub release and publishes @digital-ai/dot-icons to npm, about three minutes after the merge.main.@digital-ai/dot-icons. In React it’s <DotIcon iconId="my-new-icon" />.main is a release. Never merge a test or scratch pull request: it publishes to npm, and a published icon can’t be removed.Upload New adds an icon without a clone. It opens the same pull request add-icon gives you, minus the build, which someone runs afterwards.
add-icon-<id>: the SVG as fonts/svg/<id>.svg and one line at the end of icons.json, with the next free code. Then it opens the pull request and links it.Generated files are out of date, because the upload doesn’t build the font. Before it merges, you or the reviewer run npm run build on the branch and push the result; the pull request lists the commands. Then the check runs again.Besides the export messages above, the form can say:
| Message | What to do |
|---|---|
"My_Icon" can't be an id: use lowercase letters, digits and single hyphens, like my-new-icon. |
Change the id. See Naming rules. |
key is already an icon. Choose another id. |
Choose another id, or use the icon that’s there. |
expander is already a legacy name for key. Choose another id. |
Same: the name is taken by an alias. |
portfolio differs from Portfolio only in case, and their files would clash on macOS. Choose another id. |
Choose an id that differs in more than case. |
my-icon.png isn't an .svg file. Export the icon from Figma as SVG. |
Export the frame as SVG. |
A branch named add-icon-<id> already exists. Choose another id, or delete that branch on GitHub and try again. |
An earlier upload of the same id made that branch. Use its pull request, or open one from the branch, or delete the branch first. |
The branch add-icon-<id> was made, but the pull request wasn't (…). Open it on GitHub: <link> |
The commit is on its branch. Open the link and create the pull request there, with the build step from step 5. |
GitHub didn't accept the sign-in. Connect GitHub again. |
Select Connect GitHub again. |
Although you appear to have the correct authorization credentials, the digital-ai organization has enabled OAuth App access restrictions… |
The organization hasn’t approved the demo’s app yet. Use npm run add-icon, or the local demo below, and tell a maintainer. |
The hosted demo can open pull requests only once an owner of the digital-ai organization has approved its GitHub app. Until then, run the demo from your clone with your own gh sign-in (gh auth login). In two terminals, from the repo root:
node demo/local-proxy.js
python3 -m http.server 5174
Open http://localhost:5174/demo/. It connects through the proxy on its own, and Upload New works as above.