C4 diagrams: unified renderer migration and c4-beta - PR series tracker
This issue tracks the C4 diagram modernization effort, following the PR-split approach requested by @aloisklink in https://github.com/mermaid-js/mermaid/pull/7673#pullrequestreview and the TS-conversion handoff by @sidharthv96 in https://github.com/mermaid-js/mermaid/pull/7829#issuecomment-4686299579. The goal: replace the legacy row-grid C4 renderer with the unified rendering pipeline (real layout via dagre/ELK, themes, tags, icons, links, legend) - plus a new structurizr-inspired c4-beta syntax (RFC: https://github.com/orgs/mermaid-js/discussions/7844).
Strategy: following @aloisklink's review on #7842, Track 1 is an in-place migration sliced by rendering layer (shapes -> edges -> layout), each slice's visual change explicit in the regression suite. No opt-in flag. The end state converges on the Simon Brown-confirmed design in RFC #7844; the legacy C4Context/*_Ext syntax keeps working and renders in the same visual language as c4-beta.
The converged design (visual proof, Argos-free)
Full before/after + new-syntax galleries (rendered locally, no CI credits): converge-2026-07-02
| legacy renderer (pre-#7842) | converged end-state |
|---|---|
Note: both images are from 2026-07-02 and neither is
developtoday. The left is the legacy renderer, whichdevelopno longer uses now that #7842 has merged; the right is the full end-state, combining every phase below including the not-yet-open c4-beta/theme/legend work, so it is not what any single open PR produces either. Treat this as a direction-of-travel picture. For what a specific PR actually changes, its Argos run is authoritative - the per-PR galleries linked in older comments predate several rounds of review and should not be relied on.
How to try
Every PR build is installable from pkg-pr-new without a checkout, e.g. npm i https://pkg.pr.new/mermaid@7883, and each PR has a Netlify deploy preview (failing on roughly 78% of builds repo-wide since 2026-08-07 - see #8049 - so a red one here usually says nothing about the PR). Legacy C4Context/... already renders its elements through the unified shapes on develop (#7842, merged); relationships follow in #7883 and layout in #8042. c4-beta is the new syntax (#7843); themes follow the c4 theme variables (#7868).
The full PR sequence
Batched into phases so each merge (= one full Argos baseline re-upload) carries maximum reviewable value (~13 merges total, of which 8 have landed). Maintainers choose the pace and order; anything can be dropped per-phase or per-PR.
Where the release line falls: release/11.17.0 is cut at d93e9c88c, so most of the "Landed" list below - including the element-shape migration #7842 and the shapes from #7970 - ships in 11.17.0. #7874 is the exception: it merged on 2026-08-18, after the cut, so it lands in 11.18 despite appearing in that list. Every PR still open here also lands in 11.18 at the earliest, so none of them can affect the 11.17.0 baseline either.
Landed on develop
#7845 TypeScript conversion
#7847 fix: named attributes clobbered in earlier positional slots
#7876 characterization suite + feature matrix (baseline; reviewed by @pbrolin47, merged 2026-07-07)
#7937 fix(layout): a diagram's own
nodeSpacing/rankSpacingwin over the flowchart defaults in the unified dagre layout (merged 2026-07-07, closes #7932)#7842 migrate element shapes (in place, no flag) - reviewed by @aloisklink across six rounds (2026-07-15 through -07-27), approved 2026-07-27, merged 2026-07-30. C4 elements now render through the unified shapes: SVG-text multi-section labels, shared cylinder reuse for
*_db/*_queue, thepersonshape promoted to a general-purpose flowchart shape, CSSOM per-element font config, opt-in wrapping via the rootwrapconfig, and relationship endpoints placed via each shape'snode.intersect(replacing ~75 lines of legacy quadrant math). Existing element colours are preserved (_bg_colorfills,_border_colorstrokes) - the c4model.com outline redesign is split into #7991. History was tidied 38 -> 9 commits before merge at @aloisklink's suggestion, verified byte-identical.#7972 test(flowchart): the collate/hourglass alias pair added to the alias suite - merged 2026-07-31; closed a pre-existing coverage gap @pbrolin47 spotted during the #7970 review (
aliasSet40was declared but never added to the run list).#7970 general shapes: folder, bucket, console, browser - merged 2026-08-10. Reviewed by @pbrolin47 2026-07-17 and addressed the same day (bucket edges meet the drawn outline via polygon intersection; folder passes known bounds to skip a reflow). One correction owed here: this PR also carried an Argos sheet-batching commit that overlaps @sidharthv96's #7905, and I said it would be dropped once #7905 landed. #7905 is still open, so #7970 merged with that commit included. Since mermaid merges rather than squashes, it is now permanent history and cannot be dropped from a branch - I will open a small follow-up PR against
developto revert it when #7905 lands, so #7905 still applies cleanly. Flagging it rather than letting the commitment lapse quietly.#7874 fix: boundaries as relationship endpoints (#4864) (independent) - MERGED 2026-08-18 as
556d2a332, approved by @pbrolin47 on 2026-08-10. Second of the series to land, after #7842. It touchesc4Renderer.ts, so it conflicted with the four open PRs that also touch or delete that file (#7883, #8042, #7991, #8069); all four have mergeddevelopin and are mergeable again. The resolution that mattered is #8042's, which ismodify/delete: taking the deletion is right, but only because this fix has two halves.c4Db.getC4Shape's fallback to boundaries and theC4Elementtype live outside the renderer and survive; the boundaryintersectfunction has no counterpart in the unified pipeline, where a boundary is an ordinary group node and dagre routes the edge to the cluster. Verified by rendering the #4864 diagram through that pipeline before resolving, and covered there by a new unit test so the removal of this renderer cannot silently regress it. Earlier history: reviewed 2026-07-31, all three points addressed; the Cypress case his review asked for was initially red for an unrelated reason (a#in that test'stitleline breaks the C4 lexer, filed as #8030, fixed in #8039). After #7842 merged this needed a seconddevelopmerge, because #7842 madegetIntersectPointdelegate to each shape's ownintersectand throw when there is none - which would have thrown for exactly the boundary endpoints this PR adds. In review @pbrolin47 rendered the #4864 diagram and confirmed the output; his points produced a Cypress case covering a boundary endpoint and a stronger db assertion using identity with the boundaries array, since a boundary'stypetext issystemand so does not distinguish it from a System shape.#8100 fix(c4): stop wrapping non-text named attributes that land in a text slot - MERGED 2026-08-27 as
756ae42b1, closing #8099 - a named attribute lands in a positional slot when the argument before it is omitted, and the handlers for the text slots wrapped whatever arrived in{ text: value }whichever field it was for. SoSystem(s, "S", $tags="cylinder")storedtagsas an object,resolveNodeShapecalled.split(',')on it, and the diagram failed to render - while the same line with a description in front worked. This is a live crash ondevelop, not part of the renderer series; it is a db-layer fix and applies as-is. CI never caught it because the$tagscharacterization fixture supplies a description first, which is the form that works. Found while building the tag slice below, where the broken form is the natural one to write.#8144
docs(c4): say that the example's red styling is a demonstration- MERGED 2026-08-27. Closes #7491, which was not a bug: the reporter was reading the documentation example, which sets$fontColor="red"oncustomerAon purpose. The example now says so.
Phase 1 - Foundation (open now)
Listed in recommended merge order (live status 2026-08-20). Note on the labels GitHub shows: #7859, #7883 and #7991 still display changes requested, because a bot review cannot be dismissed from this side. All three have had every point answered and pushed against - the label is stale, not live work.
- #7859 fix(layout): reserve cluster label height and center edge labels (independent; prerequisite for the layout slice + c4-beta deployment visuals). Reviewed 2026-08-10 (changes requested); all six points addressed the same day. The substantive one was correct and worth more than it looked: the reserve was only applied one level up, so a title in a nested subgraph could still overlap its children - now every ancestor's reserve accumulates, with e2e coverage at two margin sizes. Two points I pushed back on with evidence, and the AI-attribution trailer the review flagged is removed. 1 commit -> 4 files.
- #7883 migrate relationships/edges - relationships render through the unified edge renderer (dashed lines, arrowheads, labels,
UpdateRelStyleoffsets/colours). Standalone since #7842 merged: 5 files, +202/-42, 3 commits (the third is autofix.ci's one-line lint fix, see below). Reviewed 2026-08-10; all points addressed. The review traced the label pipeline end to end and found a real regression: withhtmlLabels: falsethe label markup rendered as literal characters, becausecreateText's non-markdown branch emits tags as normal words.buildEdgeLabelnow takes the effective setting and emits a plain form when HTML labels are off, with unit tests covering both forms and a hostile string. Also added: the changeset the review asked for, and colour values fromUpdateRelStyleare now validated withCSS.supports('color', ...)before they reach a style string. One of the review's findings was a comment of mine promising a fallback the code does not have - the code was right, the comment was wrong, and it has been rewritten. The colour validation then trippedunicorn/better-regexin CI; autofix.ci pushed the corrected pattern, and that commit is left in place rather than folded away, to avoid spending a screenshot run on a one-line reformat. - #8042 lay out C4 diagrams with the unified pipeline (the layout slice; stacked on #7883) - swaps the legacy grid for the unified dagre pipeline and deletes
c4Renderer.ts/svgDraw.ts/the measure-then-layout adapter, completing the in-place replacement. Boundaries become group nodes, relationships become edges,C4Dynamicstep numbering is preserved. A side effect worth knowing:$linkstarts working, because the unified renderer wraps a linked node insvg:a- so the characterization test that asserted$linkwas unsupported correctly failed and was retitled to assert the anchor instead. Re-pointed onto #7883's current tip on 2026-08-10 and reviewable; its own diff is 4 commits on top of that branch. - #7991 C4 c4model.com outline styling - the visual redesign (light fill + identity-coloured border/text) split out of #7842 per @aloisklink, so the shapes-migration PR kept a minimal, config-safe diff. 8 files, +324/-42, 1 commit. Reviewed 2026-08-12 (changes requested); every point addressed the same day, and the open question this PR used to carry is now answered rather than deferred. The reviewer said not to wait for #7868, which was right - but following it up showed the change as described would have shipped a bug: the old
ensureReadableclamped the identity colour darker, which is correct on a white fill and wrong the moment the fill follows the theme, sincetheme-dark's surface is dark. The palette-derived colours now come from the stylesheet, the one place with both the c4 config and the theme variables, and the colour is shifted whichever way actually reads against the surface. So dark themes are supported instead of knowingly broken, and there is no hard-coded white left. A follow-up review finding was also fixed: choosing the shift direction fromisDarksends mid-grey backgrounds the wrong way, because that test pivots at half luminance while the crossover sits nearer a fifth - both directions are now tried and the better result wins, which cannot regress. New:c4Colors.ts+ unit tests, a stylesheet test, and the first C4 Cypress case that sets a theme at all. Consequence to note:<type>_border_colorno longer affects rendering, and element bodies follow the theme, so existing diagrams change appearance - both stated in the changeset. #7991 is INDEPENDENT of #7883 (verified withgit merge-base --is-ancestor) - it can be reviewed and merged in any order, despite being narratively tied to the migration.
Phase 1b - small independent fixes and test work (open now)
These came out of the Phase-1 work and are independent of it. Some were filed as issues first; the rest were found while answering review and release questions:
- #8039 fix(c4): allow
#intitleandaccDescription(closes #8030) -c4Diagram.jisonexcluded#from the title's character class, but C4's comment syntax is%%and the grammar has no#comment rule, so the exclusion served no purpose. This is the bug that made #7874's requested Cypress case look red. Synced withdevelop2026-09-07 (d8b36b2ec);unit-testandlintgreen. Its earlier red was the load-sensitivestripAnyCommentsratio assertion, measured at 1 failure in 13 local runs (see Related). As of 2026-09-08 its only red ise2e-required, caused by the scoped-e2e timeout filed as #8235 - not a test failure and not this PR. - #8041 fix(c4): send
accTitleto the accessible title (closes #8040) -accTitle:was writing the visible title instead of the accessibility one, so an author setting both lost the visible title and screen-reader users got nothing. - #8056 fix(c4): break rows on
c4ShapeInRowinstead of the display width - C4 row breaking was seeded fromscreen.availWidth, the operating system display width, which was the only read of the physical display anywhere inpackages/mermaid/src. The same diagram therefore laid out differently per machine: 2 elements per row in the headless-Chrome e2e container (800px virtual screen), 3 under Electron/Xvfb (1280), 4 on a typical monitor, and 1 under jsdom (availWidthis hard-coded to 0, so unit tests could never exercise C4 layout at all). Rows now break onc4ShapeInRowalone, so the layout is a function of the diagram and its config. Found while answering @pbrolin47's release-candidate question on #7842; that question was about the width change from #7842, which is separate and expected. This deliberately moves screenshots - diagrams get wider and shorter, nested boundaries stop stacking one per row, andUpdateLayoutConfig($c4ShapeInRow=...)above 4 is honoured instead of being silently capped. Also adds an eslint guard so layout cannot start reading the display again. - #8058 test(c4): assert the shape and sprite characterizations per element -
CHAR.update-element-shapeandCHAR.spritecounted paths and rects across the whole diagram, so both passed whether or not the right element got the right shape: one path and two rects among three elements is equally true if the cylinder and folder overrides are swapped. Each element is now asserted by its own label. No behaviour change, and Argos confirms zero visual diff, so it costs no visual review. This is the follow-up owed from a #7991 review thread, deferred at the time because the assertions belonged to #7842's characterization commit; both blockers have since merged. It deliberately does not rewire$shape/$spriteto the shapes #7970 added - that would be a new mermaid extension rather than C4-PlantUML parity (SHAPE_KEYWORDSis undocumented mermaid behaviour,c4.mdlists no$shapevalues, and the canonicalRoundedBoxShape()/EightSidedShape()are still unimplemented), so it is put as a question in the PR instead. - #8143 fix(c4): keep every relationship that repeats a from/to pair (closes #7183) -
c4Db.addRelreused any existing relationship with the samefrom/to, so a secondRel(a, b, ...)overwrote the first. In a C4Dynamic diagram, where interactions are numbered and repeated pairs are the normal case, only the last survived. The ambiguity this creates forUpdateRelStyle- which resolves its target the same way - is resolved by applying to every relationship in the named pair, and that choice is put as a question in the PR rather than settled silently. Held back from 08-20 to 08-27 waiting on exactly that answer. @knsv mergeddevelopinto this branch himself on 2026-09-02, which took itse2efrom red (the #8102 scope bug, fixed upstream in #8112) to green; onlyargos(1 changed) remains. - #8145 feat(c4): add
c4.showStereotypesto hide element and boundary stereotypes (closes #6082) - the<<system>>/<<person>>stereotype line is unconditional today. Adds one boolean to thec4config namespace, off nothing by default, so existing diagrams are unchanged; a boundary honours it too, which is the half the issue did not ask for but would have been the next report. Synced withdevelop2026-09-04 (36dd2759a, 214 commits, clean) which clears its two stale CI reds; the two new Argos cases now sit at the end ofc4-characterization.spec.js, per @knsv on build 8148 - inserting them ahead ofCHAR.descr-wrappinghad renumbered the sheets after them.
Phase 2 - c4-beta (open drafts; reshaped locally to their final design)
Correction (2026-09-10): an earlier status comment said these five were rebased and no longer conflicting. That is wrong - GitHub reports all five
CONFLICTINGtoday (#7843, #7866, #7867, #7868, #7869), each ~393 commits behind and about to be further behind after the 12.0.0 merge-back. They are not assessable, let alone landable, until someone does a rebase pass.
Correction to earlier notes: the five drafts are no longer conflicting. They were rebased onto
developand re-pointed in early August, after #7842 superseded thec4-personshape they were originally built on with the general-purposepersonshape. They remain drafts because Phase 2 opens after the Phase-1 layout slice; theargosred on each is the expected unapproved-visual-diff state, not breakage.
They form a tree, not a chain - #7843 is the shared base and the others branch off it:
- #7843 base prototype (locally extended with:
landscapekind, duplicate-id/undefined-endpoint warnings, human-readable legend labels, label-readability layout fixes) - the base all four below build on - #7866 config schema + #7867 grammar polish (siblings on #7843; will be batched into one merge)
- #7868 theme variables (sibling on #7843; final design: 14 c4 vars incl
c4Infrastructure*, defined in all themes, driving the outline look) - #7869 auto-generated legend (theme-resolved swatch colours; stacked on #7868)
The AI-attribution trailers that were on these commit messages are gone as of 2026-08-11, across all twelve open PRs in this series - message-only rewrites with byte-identical trees, each announced in its own thread. Since develop does not squash-merge, that had to happen before merge rather than after.
Phase 3 - legacy parity slices (built + verified local
Source: mermaid-js/mermaid