The container

Every inline diagram in the estate sits in a <div class="cat-explainer"> with a caption paragraph beside it, or under it once the row runs out. The caption says what the diagram is saying, not what each line means. The canvas is 660 × 200 for a short diagram, a one-row umbrella or a simple flow, and 660 × 240 where the content needs the height: state machines, multi-row pipelines, captioned figures. A single-concept object, a ladder, a spectrum, a chain walked end to end, a side-by-side comparison, stays one figure at whatever height its structure needs: 660 × 400 to 660 × 480 covers most of them. An outer <rect> starts at (20, 20) and runs 620 wide, holding a consistent 20px margin off the viewBox edge. The box has a hairline of padding, so the border sits close to the drawn frame; the canvas's own 20-unit margin is the only breathing room.

site.css ships the container rules, so a site consuming the chrome writes no .cat-explainer CSS of its own. A tokens-only site carries the three rules locally. Tutti is the case: its own chrome collides with the canonical class names.

.cat-explainer { display: flex; flex-wrap: wrap; gap: var(--space-5); align-items: end; max-width: 100%; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: var(--space-2); margin: var(--space-4) 0 var(--space-5) 0; clear: both; }
.cat-explainer svg { display: block; width: var(--fig-w, 41.25rem); max-width: 100%; height: auto; margin: 0; }
.cat-explainer p { flex: 1 1 16rem; margin: 0; font-size: var(--text-sm); color: var(--text-2); }

The skeleton

Two nodes and one labelled connector, carrying every attribute the rules below turn on:

<svg viewBox="0 0 660 200" role="img" font-family="'IBM Plex Sans', system-ui, sans-serif"
     aria-label="The collector writes into the store, and nothing else reaches it.">
  <defs>
    <marker id="collect-arrow" markerUnits="userSpaceOnUse" markerWidth="9" markerHeight="7" refX="9" refY="3.5" orient="auto"><path d="M0,0 L9,3.5 L0,7 Z" fill="var(--text-2)"/></marker>
  </defs>
  <g font-family="'IBM Plex Sans', system-ui, sans-serif">
    <rect x="20" y="20" width="620" height="160" rx="6" fill="var(--bg)" stroke="var(--border)"/>
    <path d="M180,100 H300" fill="none" stroke="var(--text-2)" stroke-width="1.5" marker-end="url(#collect-arrow)"/>
    <rect x="216" y="76" width="48" height="16" fill="var(--bg)"/>
    <text x="240" y="88" text-anchor="middle" font-family="'IBM Plex Mono', monospace" font-size="8" fill="var(--text-2)">writes</text>
    <rect x="60" y="70" width="120" height="60" rx="6" fill="var(--accent)"/>
    <text x="120" y="88" text-anchor="middle" font-family="'IBM Plex Mono', monospace" font-size="8" letter-spacing="0.08em" fill="var(--accent-contrast)" opacity="0.8">SERVICE</text>
    <text x="120" y="105" text-anchor="middle" font-size="12" font-weight="600" fill="var(--accent-contrast)">collector</text>
    <text x="120" y="119" text-anchor="middle" font-family="'IBM Plex Mono', monospace" font-size="9" fill="var(--accent-contrast)" opacity="0.85">:8080</text>
    <rect x="300" y="70" width="120" height="60" rx="6" fill="var(--surface)" stroke="var(--border)"/>
    <text x="360" y="88" text-anchor="middle" font-family="'IBM Plex Mono', monospace" font-size="8" letter-spacing="0.08em" fill="var(--text-muted)">STORE</text>
    <text x="360" y="105" text-anchor="middle" font-size="12" font-weight="600" fill="var(--text-2)">disk</text>
    <text x="360" y="119" text-anchor="middle" font-family="'IBM Plex Mono', monospace" font-size="9" fill="var(--text-muted)">one writer</text>
  </g>
</svg>

Required attributes

  • role="img" on the root, plus a non-empty aria-label that is one sentence of content: what the diagram shows in the reader's terms, never shape by shape. Four sentences is worse than one.
  • An outer <rect> filling the canvas, so the artwork carries its own background and survives host CSS.
  • font-family on the <svg> root and on the wrapping <g>, so text inheritance survives.
  • <defs> marker IDs prefixed with a per-diagram slug, so two inlined SVGs on one page do not collide, each with markerUnits="userSpaceOnUse". The default scales with stroke width and overshoots the head into the target rect.
  • An SVG loaded through <img src> keeps the host's alt and carries no aria-label; the inner one is hidden and rots unread. Inline it.

No blank lines inside the markup

Every markdown processor the estate ships through (remark, MDX, GitHub's CommonMark, GitLab's) reads a blank line inside an inline <svg> as a paragraph break. It closes the SVG early and wraps the trailing elements in a <p> outside the diagram. Group with HTML comments instead, and check the built page:

curl -s "$url" | grep -E '<p><(text|path|rect|g|circle|line)'

Any hit is a broken SVG.

The rules

Trim before you add

Every node, arrow, and label in an estate diagram has to do work the surrounding prose can't. The four-tile FLOW/DISCIPLINE cluster at the top of every hero is this rule made literal: four facets, never six, never ten. Bairn's vendor-survives diagram carries one downstream direction plus three disruption arrows that terminate at a barrier, nothing else. Patinate's pipeline carries the four CLI commands and their three caches, no more.

Before adding a node to an existing diagram, ask which node it replaces. Diagrams that grew got worse; diagrams that shed got better.

When one is already over budget, cut in a fixed order: decoration, exact duplicates, leaf clusters, degree-1 sinks, cross-cutting infrastructure, and only then split the diagram. A collapsed cluster becomes one node carrying its count, named in the caption.

Fill and stroke rank the node

The focal node wears the accent. A service is surface on ink. A store, or anything outside the boundary, fades back to --surface behind a --border hairline. Identical boxes for every node erase the hierarchy the reader came for, and no amount of labelling puts it back.

The accent is a budget

Moss #4a6741 is the single brand accent across the estate. Inside a diagram it is a budget: one or two elements wear it, and they are the elements you want the reader to look at first. Everything else renders in --text, --text-2, --text-muted, or --border.

If you find three things needing accent in a single diagram, the diagram is doing two jobs: split it.

Coordinates snap

Estate diagram SVGs snap structural coordinates to a four-unit grid. Boxes start at x=20, never x=23. Gaps are 16, never 15. Heights and widths are multiples of 4. The exception is intra-element geometry (curve control points, arrowhead apices, path tangents), which lands where it needs to.

Stroke, radius, shadow

  • Borders and rules are 1px hairlines. Content lines (arrows, flow paths) are 1.5–2px.
  • Border-radius caps at var(--radius) (10px). Super-rounded corners snap straight into template-flavor territory.
  • No drop shadows. Depth comes from the contrast between --bg and --surface, never from blurred copies of the element.

Five roles, one type ramp

Size in a diagram is a role, not a taste call. At 660 wide the ramp is fixed, and sans carries meaning while mono carries technical fact.

  • Eyebrow. 8px IBM Plex Mono, tracked caps, --text-muted. What kind of thing this node is.
  • Name. 12px IBM Plex Sans 600, --text. The word the reader takes away.
  • Sublabel. 9px IBM Plex Mono, --text-2. Port, path, command, count.
  • Arrow label. 8px IBM Plex Mono, --text-2, sitting on its own opaque mask.
  • Caption. --text-sm in --text-2, prose under the SVG, never a legend.

A node stacks at most three of these rows. A fourth row is a sublabel that wants to be its own node.

Arrows stay traceable

A reader has to follow any one connector from source to target without losing it. No two connectors share a path or an attach point: fan the arrows leaving a box across its edge instead of stacking them on one anchor, hold parallel runs 12px apart end to end, and where two lines must cross, hop the less important one.

Nodes sharing no axis connect through a rounded right-angle elbow at r=6, never a diagonal, and return legs route around boxes along the row's bottom edge rather than through them. A diagonal that clips a box is the line the reader loses.

Fan the arrows leaving one edge at L*k/(N+1), and open parallel runs to 14px where a label has to sit between them.

Three arrow kinds, no fourth

Muted for an internal hop, moss for the one focal path, dashed for optional, async, return or transit. A fourth colour would be a second accent, and the budget is already spent.

Every connector ends in an arrowhead. A line with no marker-end is a wire, not a flow, and the reader has to guess which way the thing moves.

Paint order is fixed

Background, zones, arrows, labels, nodes. Arrows before boxes is what makes every label rule checkable: a connector that would have crossed a node is visibly clipped by it instead of floating over the top, so the routing error shows up while you are drawing.

Arrow labels get an opaque mask

Each arrow label sits on its own opaque rect: 8px between mask edge and stroke, clearing any node painted after it, and clear of the arrowhead's ~10px range. Length is the constraint the mask cannot fix: "GET descriptor" fits beside its arrow, "GET descriptor.xml" overruns.

Detail-rich diagrams ship as a pair

Anything carrying more than six labeled elements at hero scale ships as a .diagram.figure-pair: a wide horizontal SVG for desktop, a stacked vertical one for mobile. The chrome swaps at the 900px breakpoint. Patinate's pipeline diagram is the worked example.

A 660×200 canvas tops out near six nodes and eight arrows. Past that, a diagram of two mechanisms the prose treats separately ships as a pair, or two figures with a caption each, never a denser canvas. A single-concept object, a ladder, a spectrum, a matrix, a chain the post walks end to end, a side-by-side comparison, is not two mechanisms: it stays one figure however many nodes it carries, growing the canvas height instead of splitting.

Every specimen, the four below and the rest in the gallery, is single-figure by design: each one shows a type at its simplest legible density, so none of them reaches the threshold that earns a pair.

The specimens

Four types, drawn against the rules above: one structural, one flow, one comparison, one estate-original. The gallery carries the rest, one line of context each, plus these same four in its index.

Architecture

HTTP SQL FRONTEND browser renders the page API server serves the routes DATABASE postgres holds the rows

Components and their connections. Use when the reader needs to understand what exists and how it wires together, not how it runs.

Fall back: Without connections between the boxes, the components are a tile-grid.

Flowchart

yes no start PROCESS validate input valid? continue error

Decision logic with branches. Use when a yes/no gate or conditional path is the substance of the explanation.

Fall back: Without a branch, the steps are a state-flow.

Quadrant

effort impact LOW EFFORT, HIGH IMPACT A B C D E

Items positioned on two independent axes. Use when the goal is comparative placement, not ranking.

Fall back: Without a second independent axis, the items rank as a tile-list.

Vendor-survives

estate-original
BARRIER outage rate-limit auth-fail writes VENDOR famly SERVICE bairn sync STORE disk

What disruption stops at our boundary. Use when the diagram's job is to show what a vendor outage cannot reach. Canonical example: bairn's Famly outages terminating at the disk barrier.

Fall back: Without disruption stopping at a barrier, the boundary is an architecture.

Acknowledgement

The single-accent budget, the structural-grid discipline, and the framing of trim-before-add were sharpened by reading cathrynlavery/diagram-design, which builds a deeper diagram-typology skill for general use, with worked examples for each type shipped as separate HTML assets rather than inside the type files themselves. The gallery now carries her full typology, every type drawn in the estate's own convention, alongside the originals grown from diagrams actually shipping across tutti, bairn, patinate, and pipeline. The editorial rules above are the ones that survived being tried against real artifacts. Cathryn's work helped clarify which ones were load-bearing.

The node-treatment ramp, the connector rules, the per-role type scale and the accessible-figure test above were sharpened by a second reading of cathrynlavery/diagram-design at commit 4451ead; the rules are restated here for a 660-wide inline SVG, and upstream's canvas, palette and fonts are not adopted.