Mermaid Scoped Theming Implementation #

Date: 2025-10-08 #

Problem Statement #

All 6 Mermaid diagrams in content/archive/JavaBasic/_index.md repeated the same classDef declarations:

classDef site stroke: none, fill: none;
classDef section fill: none;
classDef group stroke: none, fill: none;

This caused:

Solution: 3-Tier Scoped Theming Architecture #

Mermaid’s Configuration Hierarchy #

Mermaid supports configuration at three levels:

Site-level (mermaid.initialize)
    ↓ overrides
Page-level (%%{init}%% directive)
    ↓ overrides
Diagram-level (classDef in diagram)

Key Insights from Mermaid Documentation:

  1. mermaid.initialize() is called once and applies globally
  2. Per-diagram config via %%{init: {...}}%% directive overrides site config
  3. themeCSS in config applies custom CSS to all diagrams
  4. classDef in diagram source is most granular control

Hugo Integration: 3-Tier Cascade #

hugo.yaml (Site defaults)
    ↓ inherited
Front Matter (Page/Section overrides)
    ↓ inherited
%%{init}%% (Diagram overrides)

Implementation #

Tier 1: Site-Level Defaults #

File: hugo.yaml

params:
  mermaid:
    theme: neutral
    # Optional site-wide themeCSS or themeVariables

Tier 2: Page-Level Configuration #

File: layouts/_default/baseof.html

<!-- Load Mermaid, if necessary. -->
{{ if .Page.Store.Get "hasMermaid" }}
  {{- $mermaidJS := resources.Get "lib/mermaid/mermaid.min.js" | fingerprint -}}
  <script defer src="{{ $mermaidJS.RelPermalink }}" integrity="{{ $mermaidJS.Data.Integrity }}"></script>
  <script>
    document.addEventListener("DOMContentLoaded", function () {
      // Site-level defaults
      const config = {
        startOnLoad: true,
        theme: {{ .Site.Params.mermaid.theme | default "neutral" | jsonify }},
        {{ with .Site.Params.mermaid.themeVariables }}
        themeVariables: {{ . | jsonify }},
        {{ end }}
        {{ with .Site.Params.mermaid.themeCSS }}
        themeCSS: {{ . | jsonify }}
        {{ end }}
      };

      // Page-level overrides
      {{ with .Params.mermaid }}
      const pageConfig = {{ . | jsonify }};
      Object.assign(config, pageConfig);
      {{ end }}

      mermaid.initialize(config);
    });
  </script>
{{ end }}

How it works:

  1. Site defaults from hugo.yaml via .Site.Params.mermaid
  2. Page overrides from front matter via .Params.mermaid
  3. Object.assign() merges page config over site config
  4. jsonify converts Hugo data to JavaScript safely

Tier 3: Diagram-Level Overrides #

Usage in Markdown (optional, for exceptional cases):

\`\`\`mermaid
%%{init: {'theme':'dark'}}%%
flowchart LR
    A --> B
\`\`\`

Applied Solution for JavaBasic #

Before: Repeated classDef #

Each diagram had:

flowchart TB
    classDef site stroke: none, fill: none;
    classDef section fill: none;
    classDef group stroke: none, fill: none;

    node1(Example)
    class node1 site

After: Page-Level Configuration #

File: content/archive/JavaBasic/_index.md

---
title: Java Basic
type: book
layout: all
weight: 200
mermaid:
  themeCSS: |
    .site { stroke: none; fill: none; }
    .section { fill: none; }
    .group { stroke: none; fill: none; }    
---

Diagrams simplified to:

flowchart TB
    node1(Example)
    class node1 site  # Still need class assignment

Result:

Benefits of This Architecture #

1. Separation of Concerns #

LevelPurposeLocation
SiteDefault theme for all pageshugo.yaml
Page/SectionPage-specific stylesFront matter
DiagramExceptional overridesInline %%{init}%%

2. DRY Principle #

3. Flexibility #

4. Maintainability #

Scoping Rules Summary #

When to use each level: #

Site-level (hugo.yaml):

Page-level (Front matter):

Diagram-level (%%{init}%%):

Technical Details #

How themeCSS Works #

Mermaid’s themeCSS option injects custom CSS into diagram rendering:

mermaid.initialize({
  themeCSS: `
    .site { stroke: none; fill: none; }
    .section { fill: none; }
  `
});

This CSS applies to all diagrams on the page after class assignment:

flowchart TB
    node1(Example)
    class node1 site  # Applies themeCSS .site styles

Hugo Template Functions Used #

Future Enhancements #

Section-Level Configuration #

Hugo doesn’t auto-inherit front matter, but we could add logic in baseof.html:

{{ $mermaidConfig := .Site.Params.mermaid }}
{{ with .CurrentSection.Params.mermaid }}
  {{ $mermaidConfig = merge $mermaidConfig . }}
{{ end }}
{{ with .Params.mermaid }}
  {{ $mermaidConfig = merge $mermaidConfig . }}
{{ end }}

This would create: site → section → page cascade.

Per-Diagram Class Definitions #

For truly unique diagram styles, keep using classDef inline:

flowchart TB
    classDef special fill: red, stroke: blue;
    node1(Special)
    class node1 special

This doesn’t conflict with page-level themeCSS.

Testing #

Hugo dev server running at http://localhost:1313/

Test URL: http://localhost:1313/archive/javabasic/

Expected Result:

Verification Steps:

  1. ✅ Hugo builds without errors (134ms build time)
  2. ✅ Server running at http://localhost:1313/
  3. Visit JavaBasic page to verify rendering
  4. Check browser console for Mermaid errors
  5. Verify all node styles match previous version

Files Modified #

  1. layouts/_default/baseof.html (lines 15-41)

    • Added site and page-level config support
    • JavaScript config object with cascade logic
  2. content/archive/JavaBasic/_index.md (lines 1-11)

    • Added mermaid.themeCSS to front matter
    • Removed 18 lines of redundant classDef from diagrams

Maintenance Notes #

To change styles site-wide: #

# hugo.yaml
params:
  mermaid:
    theme: neutral
    themeCSS: |
      .site { stroke: red; fill: yellow; }      

To override for one page: #

# content/some-page/_index.md
---
title: My Page
mermaid:
  theme: dark
  themeCSS: |
    .custom { fill: blue; }    
---

To override one diagram: #

%%{init: {'theme':'forest'}}%%
flowchart LR
    A --> B

Adding new class styles: #

  1. Add to page front matter:
mermaid:
  themeCSS: |
    .newclass { fill: green; stroke: black; }    
  1. Use in diagram:
flowchart TB
    node1(Example)
    class node1 newclass

References #