Icons and surfaces
| Field | Value |
|---|---|
| Type | Skill Resource |
| Source | ~/.copilot/skills/technical-writing/references/mermaid/icons-and-surfaces.md |
| Description | Not specified |
Source Content
Icons and surfaces
The house style is monotone-first, inline FontAwesome icons, no emoji — the same on every surface. Design system and docs render diagrams identically. This reference gives the one syntax, the loading requirement per surface, and the concept→token map.
The two rules
Use the same style on every diagram, on every surface:
- Always use inline FontAwesome icons in every type that renders them. The
supported set is
flowchart,stateDiagram-v2,classDiagram,mindmap,block-beta, anderDiagram(see the table below). Every actor, role, system, and outcome gets a consistent icon placed inline at the start of the node label —Node["fas:fa-user Label"]. This is not optional: an icon plus a short text label is far faster to understand than text alone — the glyph is decoded at a glance and the words confirm it. A node without an icon, in a type that supports them, is a miss.
Never emoji: emoji vary by OS, fall back to tofu where no emoji font exists, and read as AI-generated. FontAwesome Free is the single icon standard for diagrams on every surface (design system and docs) — one self-hosted monotone set, no switching between packs. Use only icons in the FREE set (fas: solid, fab: brands); never a Pro-only glyph, or it renders blank.
2. Monotone-first colour. Default every node, border, edge, and icon to
neutral. Add colour only when it teaches — the people/automation boundary, success vs. failure, or the one punchline node — or when the reader asks. A wall of colour reads as machine output; restraint reads as care.
Inline, not icon-nodes
Use the inline form — the icon token sits at the front of the label and the node keeps its box, fill, and text:
flowchart LR subgraph People["fas:fa-user What people do"] FRAME["fas:fa-pen-to-square Frame the question"] end subgraph Auto["fas:fa-robot What AI does"] DRAFT["fas:fa-gear Draft the analysis"] end People --> AutoDo not use the @{ shape: icon } icon-node form. It replaces the node body with a bare icon and drops the label underneath as loose text, which destroys the box-and-fill design and reads as an unfinished sketch. Inline icons keep the colour zones, shapes, and grouping intact. Inline works in node labels, subgraph titles, and decision diamonds alike.
Brands use the fab: prefix (fab:fa-github, fab:fa-microsoft); everything
else is fas: (solid).
Loading FontAwesome per surface
Inline fas:/fab: tokens render only when the FontAwesome stylesheet is loaded
in the DOM where the diagram renders. Both surfaces self-host
@fortawesome/fontawesome-free (web fonts are allowed — see the amended ADR-003).
- Astro Starlight (docs) — a
src/styles/mermaid-icons.cssthat does@import '@fortawesome/fontawesome-free/css/all.min.css';, added to the StarlightcustomCssarray inastro.config.mjs. - Design system —
InteractiveMermaid— the component renders with vanilla Mermaid v11 and a monotone DS theme (theme: 'base',themeVariablesmapped to neutral/foreground tokens). FontAwesome must be present wherever the component mounts (import its CSS in the component or the consuming app, and in Storybook preview). No icon-pack registration is needed for inlinefas:— only the stylesheet. The ASCII view still renders through beautiful-mermaid.
Load the font BEFORE you render, or icon nodes clip their text
Mermaid sizes every node box to the width it measures for the label. If an inline fas:/fab: icon’s font has not loaded when mermaid measures, the icon contributes ~0px, so the box comes out about one glyph too narrow — then the font loads, the glyph paints at its real width, and the end of the label is clipped (Start applicati…, Answer questio…). This is a first-render race, not a markup error, and it bites on any surface.
Fix it by awaiting the FontAwesome faces before the first mermaid.render, and
re-rendering if they arrive late:
// v7 family names — update on a major @fortawesome bump. A missing family resolves// immediately; a network failure is swallowed so render never blocks.await Promise.all([ document.fonts.load('900 1em "Font Awesome 7 Free"'), // fas — solid document.fonts.load('400 1em "Font Awesome 7 Brands"'), // fab — brands]).catch(() => {});// now mermaid.render(...) — every icon measures at full widthInteractiveMermaiddoes this inensureDiagramFontsLoaded()before each render.- Starlight / any client-rendered mermaid — await the same faces in the script
that calls
mermaid.run/render(or gate the render ondocument.fonts.readyafter touching an FA glyph). Build-time SSR renderers (rehype-mermaid via a headless browser) must load FontAwesome in the render page and wait fordocument.fonts.readybefore serialising the SVG, for the same reason.
Concept to token map
One token per concept, the same everywhere. The emoji column is a labelled-fallback only, for surfaces where FontAwesome genuinely cannot load.
| Concept | FontAwesome (inline) | Emoji (fallback) |
|---|---|---|
| Resident / citizen / user | fas:fa-user | 🧑 |
| Engineer / technologist | fas:fa-laptop-code | 🧑💻 |
| Caseworker / staff | fas:fa-user-tie | 🧑💼 |
| Web app / frontend | fas:fa-desktop | 🖥️ |
| API / service | fas:fa-gear | ⚙️ |
| Case system | fas:fa-folder-open | 🗂️ |
| Store / database | fas:fa-database | 🗄️ |
| Cloud / platform | fas:fa-cloud | ☁️ |
| Payments | fas:fa-credit-card | 💳 |
| Notifications | fas:fa-envelope | 📨 |
| Queue | fas:fa-inbox | 📥 |
| Find / discovery | fas:fa-magnifying-glass | 🔍 |
| Apply / draft | fas:fa-pen-to-square | 📝 |
| Submit | fas:fa-upload | 📤 |
| Review | fas:fa-magnifying-glass | 🔎 |
| Update / reconcile | fas:fa-arrows-rotate | ♻️ |
| AI / automation | fas:fa-robot | 🤖 |
| Success / granted | fas:fa-circle-check | ✅ |
| Refused / failure | fas:fa-circle-xmark | ❌ |
| Needs attention | fas:fa-triangle-exclamation | ⚠️ |
| Permit / artefact issued | fas:fa-ticket | 🎫 |
| Repo / source (brand) | fab:fa-github | - |
Which diagram types render inline icons
Inline fas:/fab: tokens are converted to real icons only inside Mermaid’s unified text renderer (rendering-util/createText, gated on htmlLabels). Diagram types migrated to that pipeline render them; legacy-renderer types do not. This was verified by rendering each type through the shipping InteractiveMermaid config (Mermaid 11.16.0, theme: base, securityLevel: strict, FontAwesome-Free stylesheet loaded) and inspecting the SVG for an emitted <i class="fas fa-…">.
| Diagram type | Inline fas:fa-*? | How to place it |
|---|---|---|
flowchart | Renders | Node["fas:fa-user Label"] — nodes, subgraph titles, diamonds |
stateDiagram-v2 | Renders | s1: fas:fa-circle-check Granted (description form) |
classDiagram | Renders | quoted-label form class App["fas:fa-pen-to-square Application"] |
mindmap | Renders | inline fas:fa-users Users — not ::icon(fas fa-users) (see below) |
block-beta | Renders | A["fas:fa-user Resident"] |
erDiagram | Renders | on relationship labels: A ||--o{ B : "fas:fa-link submits" |
sequenceDiagram | No — leaks literal text | keep labels plain |
timeline | No — leaks literal text | keep labels plain |
gitGraph | No — leaks literal text | keep labels plain |
C4* (Context/Container/…) | No — leaks literal text | keep labels plain |
journey | No — silently dropped | keep labels plain |
gantt | No — silently dropped | keep labels plain |
sankey-beta | No — no free-text labels | band width is the encoding |
xychart-beta | No | axes are the encoding |
quadrantChart | Breaks the parser (: clash) | never place a token |
requirementDiagram | Breaks the parser (: clash) | never place a token |
architecture-beta | n/a — built-in shape icons | use (server), (database), (cloud), (disk) |
The ::icon() mindmap form does not render from the stylesheet alone. ::icon(fas fa-users) needs an icon pack registered via registerIconPacks — with only the FontAwesome CSS loaded (as both surfaces do) it renders nothing. Use the inline fas:fa-users Users form in mindmaps instead; it goes through createText and paints from the stylesheet like every other supported type.
For the “leaks”, “dropped”, “breaks”, and “n/a” rows, keep labels plain and monotone — not emoji. Each of those types already carries meaning its own way: sequence has time-order and message arrows, journey has its score column, sankey has band width, xychart has its axes. If a diagram would genuinely be clearer with icons on every node but its type can’t render them, that is a sign it should be modelled as a flowchart — do not reach for emoji to force a glyph in.