dot-icons

Adding and changing icons

A step-by-step guide for designers adding to the dot icon font. Illustrations, integration logos and task icons live in dot-illustrations.

What changed: no more IcoMoon

The font is built from this repo, not from IcoMoon.

Before you start (once)

Export the icon from Figma

  1. Draw the icon in a square frame, such as 24 × 24.
  2. Outline every stroke (Outline stroke) and all text (Outline text), then Flatten.
  3. Use one fill colour. No images, gradients, masks or transparency: a font glyph has one colour and no strokes.
  4. Export the frame as SVG. The clip Figma adds for the frame is fine; the build removes it.

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.

Add a new icon

  1. 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
    
  2. 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
    
    • The id becomes the class name, icon-my-new-icon. See Naming rules.
    • The category is one of 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":[]}
    
  3. 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.

  4. 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
    
  5. 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.

  6. When Check icons is green, post the pull request in #dot-components on Slack for review. A maintainer merges it.

Change an existing icon (redraw)

  1. Export the new drawing as above, and save it over fonts/svg/<id>.svg. Keep the id and the code.
  2. Run npm run build, commit, push and open a pull request.
  3. Check icons fails on purpose, with <id> (\<code>) changed shape. If the redraw is intended, add the glyph-change label.
  4. Add the 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.

Give an icon a second name (alias)

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.

Naming rules

What the pull request check does

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.

Reading a failure

  1. On the pull request, open Checks, then Check icons.
  2. The failing step has a red cross, and each error is shown in red with its message. The run’s summary page lists every problem under Icon check.
  3. Fix it on your branch, run 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.

After the merge

Add an icon from the demo (Upload New)

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.

  1. Open the demo, select Connect GitHub, and enter the code it shows on GitHub.
  2. Select Upload New. Enter the id, pick the category, and add the SVG exported from Figma.
  3. The form checks as you go and lists every problem under the file. Create Pull Request stays disabled until there are none.
  4. Select Create Pull Request. It makes one commit on a new branch, 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.
  5. Check icons fails on purpose with 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.
  6. Look at the icon in the demo, as in step 4 of Add a new icon, and post the pull request in #dot-components.

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 demo on your laptop

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.