Renders hierarchical Markdown as an interactive SVG mind map using markmap.js. Outputs a single self-contained HTML file that works offline. Useful for visualizing outlines, notes, plans, taxonomies, or Streamlit-embedded maps.
How this skill is triggered — by the user, by Claude, or both
Slash command
/mindmap-markmap-viewer:mindmap-markmap-viewerThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Render hierarchical Markdown as an interactive SVG mind map with **markmap.js**, plus three custom layers: **white-font CSS**, **expand-by-level control**, and **search that filters the tree**.
Render hierarchical Markdown as an interactive SVG mind map with markmap.js, plus three custom layers: white-font CSS, expand-by-level control, and search that filters the tree.
source.md ──► filter / set expand level (Python) ──► build_html() ──► vendored markmap libs (local, offline) ──► SVG + toolbar
(outline) (manipulate the text) (HTML + CSS) (transform + render in the browser) (screen)
Helper functions live in scripts/render_markmap.py; a minimal source file is in assets/example.md; regression tests are in evals/. Deeper docs: references/internals.md (how the helpers work) and references/lessons.md (real-world lessons + the adversarial counter-review behind the current code).
.html that works offline.markmap derives hierarchy from headings (#) and nested list items — -, *, +, or numbered (1. / 1)) — indented by 2 spaces per level. (Tabs work too; the filter treats one tab as one level.) A YAML frontmatter block controls behavior:
---
markmap:
colorFreezeLevel: 2 # freeze color from level 2 down (branches keep the parent color)
initialExpandLevel: 2 # levels kept open; root is level 1, so 2 = root + branches (-1 = all)
maxWidth: 380 # max node width in px (forces wrapping)
---
# Root <- root (level 1)
## Branch A <- level 2
### Sub-branch A1 <- level 3
- Leaf <- level 4 (bullet under a level-3 heading)
- Detail <- level 5 (bullet indented +2 spaces)
Presets (apply_presets). You don't have to hand-write the markmap: block — apply_presets(src, color=None, max_width=380) fills sensible defaults without overriding anything you set: colorFreezeLevel: 2, maxWidth, and an initialExpandLevel sized by node count (expand-all for ≤30 nodes, else level 2). Pass color=["#7fd1ff", "#ffd479"] for a custom palette. Any key already in your frontmatter wins, so you can set one value and let presets fill the rest.
Content rule — term → parent / description → child. Put the label on the node and its explanation as a child, not on one line. Prefer:
- Term
- description of the term
over - Term — description. This keeps nodes short and the tree scannable.
Node text may contain <, >, and & freely (a < b, List<String>, even </div>). build_html HTML-escapes the source before embedding it and the browser decodes it back, so markmap sees exactly what you wrote. (Earlier versions broke on a literal <; that footgun is gone — don't pre-escape to < yourself or it shows up literally.)
The renderer is: the vendored markmap <script>s (local files), an <svg class="markmap"> plus a hidden source <div> holding the Markdown, a small init script (transform → Markmap.create → toolbar), and the CSS. See build_html() / render_markmap() in scripts/render_markmap.py, with the rationale in references/internals.md.
Non-obvious points (these cost rework):
build_html inlines the vendored markmap stack (d3 + markmap-view/-lib/-toolbar, pinned exact in assets/vendor/) directly into the HTML — no CDN, no network, and no sibling files — so the one .html opens offline anywhere you move or share it. (That single-file default is the fix for maps that failed to load their libs when opened alone.) Pass inline=False for a smaller HTML that instead references a vendor/ folder via vendor="vendor", which must then travel beside it.window.katex, which isn't vendored, so $...$ / $$...$$ show as plain text. Everything else renders fully offline.toolbar=False to omit it. Expand/collapse set each node's fold then re-render with setData() and no argument — passing data re-derives fold from initialExpandLevel and would wipe the manual fold. Export snapshots the current fold state; the SVG inlines the white-font CSS + a dark backdrop so it stands alone, and PNG rasterizes at 2× (falling back to SVG if a browser refuses to rasterize the <foreignObject> labels — markmap draws node text as HTML-in-SVG, not <text>)..html opens on the browser's white default, and a Streamlit components.html iframe is white by default too — neither inherits the host's dark theme. So build_html paints its own dark backdrop (background="#0e1117" by default). Only pass background="transparent" when you know the host behind the iframe is already dark and you want a seamless blend. White font + dark background travel together — never set one without the other.!important. markmap draws text as SVG <text> and sometimes as <foreignObject> (HTML inside SVG). Style both or half the labels stay dark:
svg.markmap text { fill: #ffffff !important; }
svg.markmap foreignObject, svg.markmap foreignObject * { color: #ffffff !important; }
components.html in Streamlit, or a standalone .html file). Host CSS won't leak in and the map's CSS won't leak out, so the <style> goes inline in the HTML string.textContent, which the browser decodes — so escaping round-trips losslessly. Without it, a </div> or List<String> in the outline closes the div early and silently truncates the map.Don't rebuild the map — just rewrite initialExpandLevel in the frontmatter before rendering. Use set_expand_level(src, level) from the helper module:
level 1/2/3 expands that many levels.level = -1 expands everything (the "expand all" button).initialExpandLevel, else add one under the markmap: key (block or inline form), else prepend a minimal frontmatter — but only when none exists. It never rewrites the word "initialExpandLevel" sitting in your body text, and never stacks a second --- block on top of existing frontmatter (markmap reads only the first block, so stacking would silently drop your other settings).When there is a query, keep only nodes that match + their path to the root (ancestors) + their subtree (descendants), so a hit appears in context instead of floating alone. Algorithm in filter_markmap():
#. List-item level = (last heading level) + 1 + indentation, where indentation counts 2-space or tab units (expandtabs), and the marker may be -/*/+/numbered.#-rank numerically exceeds a bullet's level. (Heading rank and bullet indentation share one number line, so comparing raw depths mis-nests an H4-after-a-bullet; the parent tree is what keeps ancestry correct.)_norm).initialExpandLevel: -1._norm strips accents via Unicode NFD so "compliance", "COMPLIANCE", and accented variants all match. Zero matches yields a frontmatter-only (blank) map by design — callers branch on the returned count.
The helpers live in this skill's scripts/ directory — and the working directory is
normally the user's project, not this folder, so a bare "scripts" on sys.path
won't resolve. Build paths from the skill's base directory (the path announced when
this skill loaded):
import sys
from pathlib import Path
SKILL_DIR = Path(r"<this skill's base directory>") # announced when the skill loaded
sys.path.insert(0, str(SKILL_DIR / "scripts"))
from render_markmap import write_mindmap, apply_presets, set_expand_level, filter_markmap
Single self-contained file (recommended) — writes the .md source of truth and a
fully inlined .html that opens offline anywhere, with no sibling vendor/:
src = Path("outline.md").read_text(encoding="utf-8") # the user's outline (sample: SKILL_DIR / "assets/example.md")
src = apply_presets(src) # fill default markmap options (no override)
# optional: src, n = filter_markmap(src, "branch b") # search + keep context
# optional: src = set_expand_level(src, -1) # expand all
write_mindmap(src, "out/mapa.html") # -> out/mapa.md + a self-contained out/mapa.html
# smaller HTML + a sibling vendor/ folder instead: write_mindmap(src, "out/mapa.html", inline=False)
Just the HTML string (e.g. to embed) — build_html(src) returns one self-contained
document with the libraries inlined:
from render_markmap import build_html
open("mindmap.html", "w", encoding="utf-8").write(build_html(src, height=850))
Inside Streamlit:
sys.path.insert(0, str(SKILL_DIR / "scripts")) # SKILL_DIR as in the setup above
from render_markmap import render_markmap, set_expand_level
render_markmap(set_expand_level(src, level), height=850)
The shape of a mind map carries meaning, so structure the content deliberately:
apply_presets set the frontmatter (color, wrap width, expand level) so you don't hand-tune each map (§1).apply_presets applied, or the frontmatter set deliberately..html with the network off (and from a different folder) — it still renders (self-contained).build_html paints its own dark backdrop by default; only go transparent over a host you know is dark. And style both text and foreignObject * with !important, or half the labels stay dark.build_html HTML-escapes the source, so </>/& in node text are safe and round-trip to markmap unchanged — don't pre-escape them yourself.--- block.initialExpandLevel: -1 = expand all.npx claudepluginhub jaderson-bit/mindmap-markmap-viewer --plugin mindmap-markmap-viewerCreate branching, draggable HTML mind maps and concept maps for capturing brainstorms, mapping knowledge structures, exploring debugging hypotheses, or organizing nested ideas. Always include a Submit button (calls `submitToClaude`) to send the captured structure back to the agent for next steps. Use whenever the user wants to capture, organize, or explore branching ideas, hypotheses, knowledge structures, or any tree/graph-shaped thinking — especially when they say "brainstorm", "map out", "explore", or "what if".
Create shareable HTML visual artifacts from markdown, plans, architecture docs, brainstorms, and other structured content. Prefer browser-viewable HTML first when it will materially improve clarity or sharing; otherwise fall back to terminal rendering. Triggers: visualize, mindmap, mind map, show me the structure, draw a map, make this clear, make this visual.
Creates scientific documents and diagrams using markdown with embedded Mermaid. Default format for version-controlled documentation, enabling clean diffs and no build steps.