Theming

RenderConfig(theme = …) picks one of four palettes. Each is a ThemeColors record turned into a stylesheet: twenty CSS custom properties on :root, plus the rules that consume them.

Default

themed(ThemeName.Default)
hotcoldSourceRouteCacheCompute

Dark

themed(ThemeName.Dark)
hotcoldSourceRouteCacheCompute

Forest

themed(ThemeName.Forest)
hotcoldSourceRouteCacheCompute

Neutral

themed(ThemeName.Neutral)
hotcoldSourceRouteCacheCompute

The variables

Every colour and font in the output comes from one of these, so overriding one variable in your own stylesheet restyles everything that uses it:

VariableUsed by
--mermoid-main-bkgnode fill
--mermoid-node-bordernode stroke, subgraph frame
--mermoid-lineedge stroke, arrowheads, [*] markers
--mermoid-textnode, edge and subgraph labels
--mermoid-font-familyall text
--mermoid-font-sizenode and subgraph labels
--mermoid-edge-label-bgthe rect behind an edge label
--mermoid-note-bgnote fill
--mermoid-note-bordernote stroke and connector
--mermoid-note-textnote text
--mermoid-backgroundavailable for a page/container background
--mermoid-primary*, --mermoid-secondary*, --mermoid-tertiary*palette slots for your own rules

The primary/secondary/tertiary triples (colour, border, text) are not consumed by the built-in rules — they are there so a custom stylesheet can pick theme-consistent colours without hardcoding hexes.

{
  import _root_.mermoid.css.*
  val sheet = Theme.toStylesheet(ThemeName.Dark)
  (sheet.variables.size, sheet.variables.get("--mermoid-node-border"))
}
(20,Some(Color(#81B1DB)))

resolveVariables

RenderConfig.resolveVariables decides whether the rules reference the variables or the substituted values. It defaults to true.

  • truefill: #1f2020. Self-contained: the SVG renders identically wherever it lands, including contexts that strip <style> or don't cascade (some email clients, some image pipelines). The :root block is still emitted.

  • falsefill: var(--mermoid-main-bkg). Overridable: set the variable anywhere up the cascade and the diagram follows. This is what you want when the diagram is inline in a page you control.

{
  import _root_.mermoid.*
  val diagram  = MermaidParser.parse(sample).getOrElse(throw new AssertionError("unparseable"))
  val resolved = SvgRenderer.render(diagram, RenderConfig(theme = ThemeName.Dark))
  val varForm  = SvgRenderer.render(diagram, RenderConfig(theme = ThemeName.Dark, resolveVariables = false))
  List(
    s"resolved contains a literal hex fill: ${resolved.contains("fill: #1f2020")}",
    s"resolved contains var(): ${resolved.contains("fill: var(")}",
    s"var form contains var(): ${varForm.contains("fill: var(--mermoid-main-bkg)")}",
  ).mkString("\n")
}
resolved contains a literal hex fill: true
resolved contains var(): false
var form contains var(): true

Overriding a variable from the page, with resolveVariables = false:

.diagram-container {
  --mermoid-main-bkg: #eef6ff;
  --mermoid-node-border: #2b6cb0;
}

No re-render — the diagram already on the page restyles.

Beyond the four

The built-in themes are a convenience, not a ceiling. Theme.toStylesheet(colors: ThemeColors) accepts a palette you built yourself, and a whole stylesheet can be merged over any theme — see Custom CSS.

{
  import _root_.mermoid.css.*
  val mine  = Theme.colors(ThemeName.Neutral).copy(nodeBorder = "#d97706", lineColor = "#92400e")
  val sheet = Theme.toStylesheet(mine)
  sheet.variables.get("--mermoid-node-border")
}
Some(Color(#d97706))
{
  import _root_.mermoid.css.*
  val mine = Theme.colors(ThemeName.Neutral).copy(nodeBorder = "#d97706", lineColor = "#92400e")
  // Theme.toStylesheet(ThemeColors) is the whole extension point: a palette in, a stylesheet out,
  // merged over the chosen theme by customStylesheet.
  MermoidAscent.svgDiagram(
    sample,
    RenderConfig(theme = ThemeName.Neutral, customStylesheet = Some(Theme.toStylesheet(mine))),
  )
}
hotcoldSourceRouteCacheCompute