Skip to content

Icons and surfaces

FieldValue
TypeSkill Resource
Source~/.copilot/skills/technical-writing/references/mermaid/icons-and-surfaces.md
DescriptionNot 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:

  1. Always use inline FontAwesome icons in every type that renders them. The supported set is flowchart, stateDiagram-v2, classDiagram, mindmap, block-beta, and erDiagram (see the table below). Every actor, role, system, and outcome gets a consistent icon placed inline at the start of the node labelNode["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 --> Auto

Do 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.css that does @import '@fortawesome/fontawesome-free/css/all.min.css';, added to the Starlight customCss array in astro.config.mjs.
  • Design system — InteractiveMermaid — the component renders with vanilla Mermaid v11 and a monotone DS theme (theme: 'base', themeVariables mapped 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 inline fas: — 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 width
  • InteractiveMermaid does this in ensureDiagramFontsLoaded() before each render.
  • Starlight / any client-rendered mermaid — await the same faces in the script that calls mermaid.run/render (or gate the render on document.fonts.ready after touching an FA glyph). Build-time SSR renderers (rehype-mermaid via a headless browser) must load FontAwesome in the render page and wait for document.fonts.ready before 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.

ConceptFontAwesome (inline)Emoji (fallback)
Resident / citizen / userfas:fa-user🧑
Engineer / technologistfas:fa-laptop-code🧑‍💻
Caseworker / stafffas:fa-user-tie🧑‍💼
Web app / frontendfas:fa-desktop🖥️
API / servicefas:fa-gear⚙️
Case systemfas:fa-folder-open🗂️
Store / databasefas:fa-database🗄️
Cloud / platformfas:fa-cloud☁️
Paymentsfas:fa-credit-card💳
Notificationsfas:fa-envelope📨
Queuefas:fa-inbox📥
Find / discoveryfas:fa-magnifying-glass🔍
Apply / draftfas:fa-pen-to-square📝
Submitfas:fa-upload📤
Reviewfas:fa-magnifying-glass🔎
Update / reconcilefas:fa-arrows-rotate♻️
AI / automationfas:fa-robot🤖
Success / grantedfas:fa-circle-check
Refused / failurefas:fa-circle-xmark
Needs attentionfas:fa-triangle-exclamation⚠️
Permit / artefact issuedfas: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 typeInline fas:fa-*?How to place it
flowchartRendersNode["fas:fa-user Label"] — nodes, subgraph titles, diamonds
stateDiagram-v2Renderss1: fas:fa-circle-check Granted (description form)
classDiagramRendersquoted-label form class App["fas:fa-pen-to-square Application"]
mindmapRendersinline fas:fa-users Usersnot ::icon(fas fa-users) (see below)
block-betaRendersA["fas:fa-user Resident"]
erDiagramRenderson relationship labels: A ||--o{ B : "fas:fa-link submits"
sequenceDiagramNo — leaks literal textkeep labels plain
timelineNo — leaks literal textkeep labels plain
gitGraphNo — leaks literal textkeep labels plain
C4* (Context/Container/…)No — leaks literal textkeep labels plain
journeyNo — silently droppedkeep labels plain
ganttNo — silently droppedkeep labels plain
sankey-betaNo — no free-text labelsband width is the encoding
xychart-betaNoaxes are the encoding
quadrantChartBreaks the parser (: clash)never place a token
requirementDiagramBreaks the parser (: clash)never place a token
architecture-betan/a — built-in shape iconsuse (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.