---
title: "Iconography"
subtitle: "The wrapper, the shortcode and the rules the set holds to"
description: "How icons work on the web: the shortcode that inlines them, the wrapper that sizes them, and the invariants that every glyph in the set has to hold to."
author: "Studio"
section: "Website"
subsection: "Foundations"
order: 9
status: "published"
---
Icons are inlined as SVG at build time, not fetched. This page is the contract — how to place one, how it is sized and coloured, and what a new glyph has to satisfy. The 98 icons themselves are listed on the [Brand Book's iconography page](/brand/iconography.html), which is generated from the icon directory; to copy, recolour or export one, use the [Icon Library](/tools/icon-library.html).
## Placing an icon
Write the shortcode in any `cms/` page and the generator inlines the file.
```html
{{icon:close}}
```
It renders as the wrapper plus the glyph, with the accessibility attribute already set:
```html
…
```
{{icon:close}} {{icon:search}} {{icon:arrow-right}} {{icon:download}} {{icon:copy}} {{icon:settings}} {{icon:info}} {{icon:menu}}
Three things are worth knowing about that output. The `` is set to 100 % of its wrapper, so **the wrapper controls the size**. `data-icon` carries the name, and it is a real CSS hook rather than a label — some component rules select on it. And `aria-hidden="true"` is applied automatically, which is right for a decorative glyph and means **an icon-only control must carry its own label**.
## The wrapper
| Property | Value | Why |
| --- | --- | --- |
| `width` / `height` | `var(--icon-size)` | One token sizes the icon; the SVG fills whatever the wrapper is |
| `aspect-ratio` | `1 / 1` | Every glyph is square, so the box stays square whatever the size token becomes |
| `display` | `flex`, centred | Centres a glyph that does not fill its own viewBox |
| `margin` | `-0.175em` | Optical correction, so an icon beside text sits on the same line rather than pushing it |
`--icon-size` is `1.3rem` by default. Inside a `.button` it becomes `0.9em`, so an icon scales with the button's text rather than staying a fixed size — which is why a small button's icon looks right without anything being set. The exception is `[data-icon-only]`, which puts it back to a fixed `1.3rem` so a square icon button keeps a legible glyph at every button size.
## Colour
Every glyph is filled with `currentColor`, so an icon takes the colour of the text around it and needs nothing set on it.
{{icon:info}} Primary — the icon follows the text colour
{{icon:info}} Secondary
{{icon:info}} Accent
- **Never set `fill` on an icon.** Colour it by colouring its container; a hardcoded fill stops following the theme and will be wrong in dark mode.
## What a glyph has to satisfy
All 98 icons in the set hold to four invariants, and the build checks the set for duplicates on every run.
| Invariant | Why it matters |
| --- | --- |
| A `0 0 24 24` viewBox | The wrapper assumes a square; a different viewBox renders at the wrong optical size beside its neighbours |
| `fill="currentColor"` on every path | This is what makes colour inherit |
| No `stroke` anywhere | A stroke-only path comes out solid when the colour pass runs over it, and scales its weight when the icon is resized |
| A unique drawing | Two names for one drawing is a warning at build time. Derive a variant by baking the change into the coordinates, never by copying a file or wrapping it in a `transform` |
| `fill="none"` on the `` root | The fill belongs on the paths; a root fill paints the whole box |
| No `xmlns` attribute | The file is inlined into a page, never served standalone, and the generator does not strip it — so a hand-added file has to arrive without one |
Stems are 2 units throughout, which measures 1.98 perpendicular on a 45° bar. A new glyph that does not match that weight will read as a different family at small sizes.
## Accessibility
An icon is decorative until it is the only thing carrying the meaning.
```html
{{icon:close}}
```
- **An icon-only control needs `aria-label`.** The glyph is `aria-hidden`, so without one the control announces as "button" and nothing else.
- **An icon beside its own text needs nothing.** The text is the label; the glyph is decoration and should stay hidden.
## Naming
Names are lowercase, hyphenated, and describe the drawing rather than the job it is doing — `close`, not `dismiss-dialog`, because the same glyph closes other things too. Size variants carry the size last: `close`, `close-small`, `close-large`.
## An engine gap worth knowing
Six synced artefacts draw their own glyphs in CSS or in a JavaScript string rather than reading the icon directory: `design-system.css` carries a `check`, `docs-kit/docs.css` a `check` and a `copy`, both `copy-button.js` files build a `check`, `docs-copy-chrome.js` builds `check`, `copy` and `download`, and `toast.js` builds a `close`.
So every code-block copy button, every toast dismiss and every styled checkbox shows the **engine's** artwork rather than BlackDoctor's, and it cannot be fixed here — those files are overwritten on `npm install`. It is recorded under *Engine gaps* in `docs/figma-mapping.md` and the fix belongs upstream.
## Rules
- **Never inline a glyph's path data by hand.** Use `{{icon:...}}` so the page tracks the icon directory; a pasted copy drifts the moment the file changes, and the build cannot see it happen.
- **Never add an icon by editing the Brand Book page.** That page is generated by `tools/icon-registry.py` from the directory — add the file, add its name to the script's categories, then re-run it.
- **Check a new name against the set first.** A second name for an existing drawing warns at build time and splits the vocabulary in two.