If you can write a bulleted list, you can make a mind map. Paste one
<script> tag into your page, wrap your list in a
<pre class="mindmap">, and it is drawn as a mind map when the
page loads. Labels take a little Markdown, and readers can fold branches away,
drag nodes around and reshape the map.
This guide is written for people who make teaching material — course pages, lecture notes, handbooks. Nothing in it asks you to write JavaScript or CSS, and there is nothing to install or build. The one exception is the Advanced part at the end, which is marked off so you can skip it.
Add it to your page
Add this line once per page. Anywhere works; just before the closing
</body> tag is the usual spot:
<script src="https://se-education.org/mind-maps-helper/mindmap.js"></script>
Then write a mind map wherever you want one:
<pre class="mindmap">
Software Engineering
Requirements
Elicitation
Specification
Design
Architecture
Design Patterns
Implementation
Code Quality
Refactoring
Testing
Unit Testing
Integration Testing
</pre>
That produces this:
Software Engineering
Requirements
Elicitation
Specification
Design
Architecture
Design Patterns
Implementation
Code Quality
Refactoring
Testing
Unit Testing
Integration Testing
Indentation is the whole syntax. A line indented further than the one above it becomes its child. Use two spaces per level and you will never think about it again.
Putting the script on every page at once
Rather than pasting that line into every page, use whatever your site generator offers for site-wide scripts.
MarkBind. Add the address to externalScripts in
site.json:
{
"externalScripts": [
"https://se-education.org/mind-maps-helper/mindmap.js"
]
}
MarkBind can also carry it in a layout, if you would rather have the script
only on the pages using that layout: put the <script> line
inside a <head-bottom> block in your layout file under
_markbind/layouts/.
Jekyll. Add the <script> line to the
layout your pages use.
Writing maps in Markdown
Most teaching material is written in Markdown rather than HTML, and there
the <pre class="mindmap"> form always works.
If you remember one thing from this page, remember that one: paste it into a
Markdown file exactly as it is, and it accepts every option below.
<pre class="mindmap">
Topics
Week 1
Week 2
</pre>
Fenced code blocks work too, but how you write them depends on your site generator, because each one records the language differently.
| Generator | Write this |
|---|---|
| Jekyll / GitHub Pages, MkDocs, Docusaurus, and most others | ```mindmap |
| MarkBind | ```mindmap {.mindmap} |
Why MarkBind needs the extra bit. MarkBind passes fenced
blocks to its syntax highlighter, which drops the language name, leaving
nothing for the script to recognise. The {.mindmap} suffix adds a
label that survives. Checked against MarkBind 7.1.
Do not use a plain <div> in Markdown.
Markdown reinterprets the indented lines inside a
<div>, which mangles the map. A fenced block or a
<pre> is safe.
Writing the text
Bullets are optional
These two produce the same map. Use whichever looks better in your source.
Assessment
Exams
Midterm
Final
Coursework
Project
Quizzes
- Assessment
- Exams
- Midterm
- Final
- Coursework
- Project
- Quizzes
The rules, in full
- The first line is the centre of the map. There can be only one such line.
- Deeper indentation means a deeper level. Any consistent step works — 2 spaces, 4 spaces, or tabs.
- A leading
-,*or+followed by a space is treated as a bullet and dropped. - Blank lines are ignored, so you can use them to group things while writing.
- A line starting with
//is a comment and is not drawn. [+]before the text makes that branch start folded;[-]makes it start open — see Folding one branch.[blue]before the text paints that branch — or the centre node — in a colour you name — see Colouring the map.[dim]fades a branch into the background,[hot]picks one out — see Dimming and highlighting.- Node text takes a little Markdown — see Formatting inside a node. No HTML.
If something is wrong with your list, the page shows a short message saying what and on which line, instead of silently drawing nothing.
Longer labels wrap
A long label breaks onto another line rather than stretching the map. Set the
width it breaks at with data-max-node-width. Every line of a wrapped
label starts at the same edge, so the text stays easy to read down the left and
frays only on the right.
<pre class="mindmap" data-max-node-width="110">
Course Admin
Weekly deadlines are strictly enforced
Late submissions lose 10% per day
Extensions need prior approval
</pre>
Course Admin Weekly deadlines are strictly enforced Late submissions lose 10% per day Extensions need prior approval
Formatting inside a node
Node text takes a small slice of Markdown — the parts that work within a line, nothing bigger. Everything else is shown exactly as typed.
Software Engineering
**Requirements** first
Run `git rebase` with care
*Optional:* ~~dropped~~
[More examples](#example)
Week 1\nWeek 2
Software Engineering **Requirements** first Run `git rebase` with care *Optional:* ~~dropped~~ [More examples](#example) Week 1\nWeek 2
| Write | Get |
|---|---|
**text** or __text__ | Bold |
*text* or _text_ | Italic |
`text` | Monospace, on a tinted chip |
~~text~~ | Struck through |
[text](url) | A link |
 | An image |
\n | A line break where you want it |
\* | A literal asterisk (same for any marker) |
It tries hard not to surprise you
Teaching material is full of characters that look like Markdown but are not, so the rules are deliberately cautious:
snake_case_namestays as typed — underscores only count between words.2 * 3 * 4stays as typed — a marker needs a non-space right after it.unclosed **boldstays as typed — an unmatched marker is just a character.[not a link]stays as typed — a link needs its(url)part.
If a map still fights you, switch the inline formatting off with
data-markup="false": bold, italics, code, links and images stop
being read, and those characters are drawn as typed. The list itself is
untouched — indenting, bullets, comments and the [+]-style markers
are how the map is built rather than formatting, so they keep working.
Links
Clicking the link text follows it; clicking anywhere else on the node folds that branch as usual, and dragging does neither. Links are also read out to screen readers.
For safety, only ordinary web addresses are accepted:
http, https, mailto and paths within your
own site. Anything else — a link that runs code, in particular — is refused
however it is spelled, and the text is left as typed.
Images
An image sits inline with the text, so Icon  here
puts it mid-sentence, while an image alone on a line becomes the node's whole
content. An image with no size given is scaled to fit within 120×90. To
set the size yourself, add it inside the brackets, after the address:
 both dimensions
 width, height follows the aspect ratio
The map is drawn straight away and tidies itself once the images report their real size, so a slow image never holds up the rest of the page.
Both forms in one map — a small icon sitting beside its label, and a diagram that is the whole of its node:
Week 5
 Lecture notes
Architecture

Reading
Chapter 4
Week 5
 Lecture notes
Architecture

Reading
Chapter 4
The icon above is given an explicit =18x18, because a small glyph
should not be scaled to whatever size the file happens to be. The diagram is
given none, so it is scaled to fit the 120×90 box on its own.
Collapsing and expanding
Any node with children gets a small − button on its outer edge. Clicking the node (or the button) folds that branch away and leaves a + behind; clicking again brings it back. Try it on the map above, or this one:
Testing
Unit Testing
Test Drivers
Stubs
JUnit
Integration Testing
Top-down
Bottom-up
Big Bang
System Testing
Functional
Non-Functional
Acceptance Testing
Alpha
Beta
It works with the keyboard too: Tab to a node, then Enter or Space. The map slides into its new shape as it folds; for a reader whose device is set to reduce motion it arrives there in one step instead, without the animation.
Folding the whole map at once
A reader working through a big map one branch at a time can open or shut all of it in a single click. In the map's top corner, beside the shape switch, is a button that reads Expand all while any branch is folded; press it and every branch opens. With nothing left folded it becomes Collapse all, which shuts the map back down to its top-level topics. Fold one branch by hand and it goes back to offering Expand all.
Software Engineering
Requirements
Gathering
Specification
Validation
Design
Architecture
Design Patterns
Implementation
Coding Standards
Refactoring
Testing
Unit Testing
Integration Testing
There is nothing to set up: the button appears wherever there is something to
fold, and a map whose branches are all single nodes does not get one. It hides
with the shape switch under data-controls="false", and goes away
entirely with data-interactive="false".
Starting a map partly folded
Big maps are friendlier if they open as an overview.
data-collapse-level sets how many levels show at first — the rest
stay one click away.
<pre class="mindmap"
data-collapse-level="1">
Testing
Unit Testing
Stubs
JUnit
Integration Testing
Top-down
</pre>
Testing
Unit Testing
Stubs
JUnit
Integration Testing
Top-down
Folding one branch
To fold one branch rather than a whole level, put [+] in front of
its text. It is the same sign the fold button shows, so what you write is the
state the reader opens on.
Testing
Unit Testing
Stubs
JUnit
[+] Integration Testing
Top-down
Bottom-up
System Testing
Functional
Testing
Unit Testing
Stubs
JUnit
[+] Integration Testing
Top-down
Bottom-up
System Testing
Functional
[-] does the opposite: it holds a branch open where
data-collapse-level would have folded it. That is how a map can open
as an overview and still show the one branch the page is about.
<pre class="mindmap"
data-collapse-level="1">
Testing
[-] Unit Testing
Stubs
JUnit
Integration Testing
Top-down
</pre>
Testing
[-] Unit Testing
Stubs
JUnit
Integration Testing
Top-down
A marker goes after any bullet — - [+] Design — and never shows up
in the label. It sets only the state the map opens in; the reader folds
and unfolds as usual afterwards. A node with nothing under it has nothing to
fold, so there the marker is simply dropped.
Like a bullet, a marker needs a space after it, which leaves
[+](notes.html) a link. Write \[+] for a label that
really does begin with one. Markers work whatever data-markup
says, because they are part of how the list is built rather than formatting
inside a label.
To switch all of this off and get a plain static diagram, set
data-interactive="false".
Colouring the map
Branches take a colour from a built-in set, in the order they appear. To choose one yourself, name it in front of the text, the way you would a fold marker.
Software Engineering
[blue] Requirements
Elicitation
[green] Design
Architecture
[pink] Testing
Unit Testing
Software Engineering
[blue] Requirements
Elicitation
[green] Design
Architecture
[pink] Testing
Unit Testing
The eight names are blue, orange,
green, purple, gold, teal,
pink and slate. Each has a light and a dark version, so
a branch you name still follows the page's light or dark theme like any other.
red, grey and gray are accepted as well,
for orange and slate. Every branch below is named, so none of them is the colour
its position would otherwise have given it.
Accents [pink] pink [blue] blue [gold] gold [green] green [slate] slate [purple] purple [teal] teal [orange] orange
A name holds from that node down. On a top-level branch it colours the whole branch; further in it recolours the rest of that branch, which is how one sub-topic gets picked out from the rest.
Testing
Unit Testing
Stubs
[pink] Flaky tests
Timing
Integration Testing
Top-down
Testing
Unit Testing
Stubs
[pink] Flaky tests
Timing
Integration Testing
Top-down
Naming one branch leaves the others as they were: the unnamed ones still follow branch order, so adding a colour recolours what you named and nothing else.
Colouring the centre node
The centre node takes a name too, and wears it differently. It is the one box filled solidly rather than tinted, so it takes the bright version of the colour, with dark text on top of it.
[gold] Software Engineering
Requirements
Design
Testing
[gold] Software Engineering Requirements Design Testing
A centre node you have coloured stays that colour in both light and dark themes, unlike the default one, which flips from dark to light with the page. The bright colours are legible on either.
Naming the centre node changes that one box and nothing else. It does not take a colour away from the branches, so the first branch is still the first colour.
Colour markers follow the same rules as fold markers, and the two stack in
either order — [+] [green] Design and [green] [+] Design
are the same node. Both go after any bullet, both need a space after them, both
work whatever data-markup says, and \[green] gives you a
label that really does begin with one. A bracketed word that names no colour is
left alone, so [TODO] Revise this stays as typed.
Dimming and highlighting
Two more markers change how loudly a node is drawn. [dim] fades a
node and everything under it into the background. [hot] picks one
out: a deeper fill in the branch's own colour, a thicker outline, a bolder label,
and a heavier curve arriving at it.
Syllabus
[dim] Requirements
Elicitation
[hot] Design
Architecture
Testing
Unit Testing
Syllabus
[dim] Requirements
Elicitation
[hot] Design
Architecture
Testing
Unit Testing
The ordinary look sits between the two, so a dimmed topic reads as one the reader is past and a highlighted one as the one to look at, with everything else left alone. A map of a syllabus can dim what the course has covered, highlight what today is about, and leave what is still ahead exactly as it was.
Emphasis holds from that node down, the way a colour does.
[normal] takes a node and everything under it back out of it, which
is how one sub-topic stays at full strength inside a chapter you have
dimmed.
Requirements
[dim] Elicitation
Interviews
[normal] Prototyping
Throwaway prototypes
Specification
Requirements
[dim] Elicitation
Interviews
[normal] Prototyping
Throwaway prototypes
Specification
Highlighting deepens the branch's own colour rather than bringing a colour of its own, so a picked-out node still says which branch it belongs to. On the centre node an emphasis marker is dropped, unlike a colour name: emphasis is a node standing out from the ones around it, and the centre node has nothing to stand out from.
Dimming fades the box and the curve into it, but keeps the label readable rather than fading that too — a dimmed topic is one the reader is past, not one they cannot read. Links and code inside a dimmed node keep their own colours for the same reason.
Both carry into print, which matters, because "what we have covered" and "what today is about" is often why a map is on paper at all. Screen readers are told about them as well, once wherever the emphasis changes.
Emphasis markers follow the other markers' rules, and all three stack in any
order — [+] [green] [dim] Design and [dim] [green] [+]
Design are the same node. They go after any bullet, need a space after
them, work whatever data-markup says, and \[dim] gives
you a label that really does begin with one. A bracketed word that names no
emphasis is left alone, so [Draft] Notes stays as typed.
Choosing the shape
The same content reads differently depending on how it is arranged. Balanced is compact and works well as a figure; one-sided reads top-to-bottom like an indented outline, which some readers find easier to follow. Rather than making the choice for everyone, each map carries a small switch in its top corner — it fades in when you hover the map.
Requirements
Gathering
Interviews
Surveys
Observation
Specification
User Stories
Use Cases
Glossary
Validation
Reviews
Prototyping
Which way the branches run is one choice; how each branch is arranged underneath is another, and indented branches below covers that one.
You still set the starting shape with data-direction; the switch
just lets a reader try the other one. Folded branches stay folded across a
switch. If you would rather pin one shape, set
data-controls="false" — which takes the
fold button beside it with it.
By keyboard, the switch is a single stop: Tab to it and the arrow keys move between the two shapes, the way a set of radio buttons works.
The switch does not print. A printed map keeps whichever shape the reader left it in, which is the one they chose to print.
One-sided maps
Good for short lists, or when the map sits in a narrow column.
<pre class="mindmap"
data-direction="right">
Git
Local
commit
branch
Remote
push
pull
</pre>
Git
Local
commit
branch
Remote
push
pull
Indented branches
A map that goes three or four levels deep gets wide. Each level claims a column as wide as its longest label, so by the fourth one the map is running off the side of the page and shrinking to fit.
data-layout="indent" arranges it the other way. Instead of
opening a new column, each level steps a little further out and stacks
underneath its parent, with the line dropping from the underside of the box —
the way folders and sub-folders are shown in a file browser. A level then costs
the same small step whatever its labels say. The map below is
599 pixels wide; fanned out, the same map is 1075 — so it
fits a column this narrow, where the fanned one would be shrunk as far as it
goes and still run off the side.
<pre class="mindmap"
data-layout="indent">
Software Engineering
Requirements
Elicitation
Interviews
Observation
Specification
Use cases
Design
...
</pre>
Software Engineering
Requirements
Elicitation
Interviews
Observation
Specification
Use cases
Design
Architecture
Layered
Client-server
Modelling
UML diagrams
Testing
Unit tests
Integration tests
It is a trade rather than a straight win: what the map stops spending sideways it spends downwards, so an indented map is taller than the same map fanned out. That is usually the better way round on a web page, which scrolls down and not across — but a short, wide map has no width problem to solve, and looks better fanned.
Indenting reads best one-sided
In a balanced map the branches on the left step leftwards, away from the centre, so that half of the map is a mirror image of a file browser and the deepest labels are the ones furthest out. It is perfectly readable, but the right-hand half is the one that reads the way an outline normally does. Sending every branch the same way gives you the whole map in that form — the same map as just above, indented:
<pre class="mindmap"
data-layout="indent"
data-direction="right">
Git
Local
commit
branch
Remote
push
pull
</pre>
Git
Local
commit
branch
Remote
push
pull
That is a recommendation, not a rule, and there is a real reason to go the other way: splitting the branches across the centre roughly halves the map's height, which is the one dimension an indented map spends freely. A deep map that would run off the bottom of the screen one-sided may well be better balanced. Either way the switch in the corner still offers the reader the other shape, so what you set here is where the map opens rather than where it has to stay.
Everything else works the same. Branches still split left and right of the
centre, the shape switch still moves them all to one side, and folding, colours
and dragging are unchanged. Only the reader cannot switch between the two
layouts: data-layout is the author's call, because it is the map
you wrote rather than a view of it.
Moving nodes
Readers can drag any node to somewhere clearer. The branch below it comes along, and the connecting curves follow. Drag the map above and see.
- Dragging a node moves its whole branch, so branches keep their shape.
- The centre node is the exception: it moves on its own. Taking its branches along would just slide the entire map, which changes nothing.
- The rest of the map stays exactly where it was — nothing reflows around you.
- If you drag past the edge, the canvas grows to fit once you let go.
- A click is still a click: only a real drag moves a node, so folding still works.
- With a keyboard, focus any node with children (or the centre) and use the arrow keys. Hold Shift for fine steps.
- On a touch screen, a straight-up-or-down swipe scrolls the page as usual even if it starts on a node, so a full-screen map never traps a reader. Anything else drags.
Moves last for that reader and that visit only — reloading the page brings
the tidy layout back. To turn dragging off but keep folding, set
data-draggable="false".
For developersTo offer a "reset layout"
button of your own, call MindMap.resetPositions().
Blocks of your own HTML
Some things do not fit in a line of text — a marks table, a worked example,
a styled callout. Write that block anywhere on the page, give it an
id, and point at it the same way you would point at a picture. A
# means "the element with this id" rather than a file:

The block is copied into the node, so the original stays where you put it and one block can appear in several maps. It keeps your own classes and your own CSS, so it looks in the node exactly as it looks on the page.
Where the block lives
Usually you do not want the block showing up twice on the page. Either put it
in a <template>, which the browser never displays:
<template id="tip-box">
<div class="tip"><b>Watch out</b> — coupling is not dependency.</div>
</template>
or mark an ordinary element with class="mm-source", which this
script hides for you:
<div id="marks-box" class="mm-source">
<table> ... </table>
</div>
A block that is meant to be visible on the page needs neither: point at it and it appears in both places.
Week 5
Assessment

Design
Coupling

Cohesion
| Component | Weight |
|---|---|
| Project | 40% |
| Exam | 35% |
| Participation | 25% |
Coupling is not the same as dependency.
Week 5
Assessment

Design
Coupling

Cohesion
How big the block gets
A block lays itself out within 260px wide unless you say otherwise.
data-embed-max-width changes that width for a whole map;
 fixes one block's size exactly.
Height is capped as well: a block you have not sized gets at most 400px of it.
Nothing is lost when the content is taller — the block keeps all of it and
scrolls inside the node. Giving the block a size of your own replaces that cap
with the height you asked for, so  lays out 600px
tall, and scrolls only if the content outgrows even that.
What changes inside such a node
- Clicking the block does not fold the branch, since you are meant to read and use what is in it. The − button still folds it.
- Links inside the block work normally, and the node can still be dragged by any other part of it.
- The words in front of the
#are what a screen reader reads out for the block:reads as "Grade breakdown". Leave them out and the block's own opening words stand in, shortened — so write them whenever those first words would not say what the node is. - The copy is tidied on the way in:
ids are removed, so nothing on your page ends up with a duplicate, and anything that could run is left out.
The block has to be on the page before the map is drawn. If the map names an id that is not there, it shows a message instead of drawing — usually a typo in the id. Blocks written straight into the page are always in time.
For developersExactly what is stripped from the copy, what a copy loses that the original had, and how to handle a block some other script builds after the page loads: More about embedded blocks.
Options
Set these as attributes on the container. All are optional.
| Attribute | Default | What it does |
|---|---|---|
data-layout |
fan |
How branches are arranged. fan spreads each level into its
own column. indent steps each level a little further out
and stacks it underneath, the way folders and sub-folders are shown —
much narrower for a map that goes deep. |
data-direction |
balanced |
Which shape the map opens in — readers can switch it themselves.
balanced splits branches left and right of the centre.
right puts them all on the right, left all on
the left. |
data-max-node-width |
190 |
How wide (in pixels) a label may get before it wraps to another line. |
data-column-gap |
46 |
Horizontal space between levels. Lower it to make a wide map fit. In an indented map it sets only the gap between the centre and its branches; the levels below step by a fixed amount that does not need setting. |
data-embed-max-width |
260 |
How wide (in pixels) an embedded block of your own HTML may lay itself out before it wraps. |
data-collapse-level |
off | Show only this many levels at first. 1 shows the centre and
its branches; deeper nodes start folded. A node's own [+] or
[-] wins over it. |
data-markup |
true |
Set to false to take the label text literally, with no
bold, links or images. Bullets, comments and the markers are
structure, and stay. |
data-controls |
true |
Set to false to hide the buttons in the map's corner —
the shape switch and the fold button — fixing the map in the shape you
chose. |
data-draggable |
true |
Set to false to keep folding but stop readers moving nodes. |
data-interactive |
true |
Set to false for a plain static diagram — no folding, no dragging. |
data-theme |
follows the page | light or dark, to pin this one map regardless
of the site's theme. |
data-credit |
true |
Set to false to drop the small caption linking back to
this project from under the map. |
Write the map as <pre class="mindmap" ...> whenever
you use an option. That form carries options in every site generator,
and it is worth the few extra characters:
<pre class="mindmap" data-direction="right" data-collapse-level="1">
Software Engineering
Requirements
Design
</pre>
Fenced blocks can carry options too, but whether they reach the map depends on
your generator. In MarkBind, write
```mindmap {.mindmap data-direction="right"}. In Jekyll, put
{: data-direction="right"} on the line after the fence. If an option
you set that way seems to be ignored, write the map as a
<pre class="mindmap"> instead — that always works.
Wrapping the map in your own <div> will not carry
options. Put them on the map itself. A
<div data-direction="right"> around a fenced block does
nothing.
Credit link
A small caption sits under the bottom right of every map, reading « Made with Mind Maps Helper » and linking back to this page. A reader who runs into a mind map in somebody's lecture notes has no other way of finding out what drew it.
<pre class="mindmap">
Testing
Unit
Integration
System
</pre>
Testing Unit Integration System
The caption is not part of the drawing, so a wide map scrolls underneath it
rather than carrying it off the screen, and the link opens in a new tab rather
than taking your reader off your page. Where there is a mouse it rests faint and
comes up to full strength while the pointer is inside the map; on a touch screen
it stays legible. It does not print: on paper the link is dead ink, so the map
goes to the printer on its own. To drop it from the screen as well, set
data-credit="false" — which is what every other demo on this page
does, since a link from this site back to this site helps nobody.
A bigger example
CS2103 Software Engineering
Requirements
Stakeholders
User Stories
Use Cases
Non-Functional
Design
Architecture
OO Design
Design Patterns
UML Diagrams
Implementation
Code Quality
Refactoring
Error Handling
Documentation
Quality Assurance
Unit Testing
Integration Testing
Test Coverage
Code Review
Project Management
Revision Control
Workflows
Scheduling
Teamwork
If something looks wrong
The map is still showing as indented text. The script did not
run, or it did not recognise your block. Check that the
<script> line is on the page and the address is spelled
correctly, then check the block itself — a <pre class="mindmap">
always works, while a fenced block has to be written the way your generator
needs.
The map says something is wrong with a line. Read the message — it names the line and the problem. The usual cause is a second line that is not indented under anything: a map has exactly one starting line, and everything else sits somewhere under it.
"This node asks for the page element with id …". The map
points at a block that was not on the page when it was drawn — usually a typo in
the id. See
Blocks of your own HTML.
The map draws, but with the wrong colours and odd stray text. Your site is blocking the stylesheet the script adds. See Content Security Policy.
Anything else. A map that fails for a reason you can act on names the problem and the line on the page itself. Anything else is reported in the browser console.
Good to know
- Maps are drawn as SVG, so they stay sharp at any zoom and print cleanly. The drawing is all that prints: the buttons in the corner and the credit caption are for a reader who can click, so they stay off the page.
- Maps shrink to fit a narrow screen, down to 70% of full size, and scroll sideways if they still do not fit. A scrolled map starts centred on its centre node.
- Screen readers get the map as a nested list. An embedded block is
announced by the words you put in front of its
#, or by a shortened version of its own text where you left those out. - No tracking and nothing else loaded: once the script is on your page it fetches nothing of its own. Images you point at by address, and whatever is inside an embedded block, are fetched by the browser as usual.
- Nesting goes 100 levels deep. Past that a map tells you which line went too far rather than failing part-way through drawing.
- A drawn map is not a standalone picture file. Its colours and fonts come
from a stylesheet the script adds to your page, so an
<svg>saved out on its own arrives unstyled. An image you pointed at by address stays a pointer rather than becoming part of the drawing, so a saved copy still fetches it from where it lives, and shows nothing anywhere that address does not reach. Write the address as adata:URI and the picture travels with the copy instead. An embedded block does come along, but it leaves your own styling behind.
Versioning
The address of the script carries no version number, and always serves the current release. That cuts both ways, and it is worth being plain about: your pages pick up new releases automatically, as they ship, without you changing anything and without a chance to try them first.
What you get in exchange is that your existing maps keep working. Any future
change that would break them ships at a new address
(/v2/mindmap.js) rather than landing on this one, so a page that
never touches its <script> line keeps the behaviour it was
built against.
Advanced — you can stop here
Everything above is all you need to put mind maps on your pages. What follows is for people who want to go further, and it assumes you are comfortable with CSS or JavaScript. Nothing here is required, and skipping it costs you nothing.
Matching the map to your site
Colours come from CSS custom properties, so a few lines of CSS match a map to your site's own palette.
Light and dark are handled already. If your site has its own dark-mode
toggle, the map follows it — MarkBind, Bootstrap 5.3, Docusaurus and Tailwind
are all detected. Failing that, it follows the reader's system setting. To pin
one map either way, add data-theme="light" or
data-theme="dark"; that beats everything else.
.mm-container {
--mm-surface: #ffffff; /* box fill */
--mm-text: #1f2933; /* label text */
--mm-muted: #5b6976; /* deepest-level text */
--mm-root-bg: #2c3e50; /* centre node fill */
--mm-root-text: #ffffff; /* centre node text */
--mm-code-bg: #eceff2; /* code chip fill */
--mm-code-text: #8a3033; /* code text */
--mm-link: #2563a8; /* link text */
}
A colour named on the centre line wins over the two
--mm-root-* values for that map.
Replacing the branch colours
Branch colours cycle through a built-in palette in the order your top-level
branches appear. To use your own, override
window.MindMap.palette — each entry is a
[light, dark] pair, written as hex, rgb(),
hsl() or a colour word. The names you write in a map
([blue] and the rest) point at positions in that array rather than
at colour values, so your palette recolours the named branches — and a named
centre node — along with the rest.
The centre node's text colour is worked out from the colour underneath it, so
a dark colour in the bright half of a pair gets white text rather than a label
lost in its own box. A colour only the page can resolve, such as
var(--brand), is one that calculation cannot read, and such a centre
node falls back to dark text.
The palette is read as each map is drawn, so it has to be set before the maps it should affect are rendered. Maps already on the page are drawn once the document has been parsed, which any inline script placed after the library will beat:
<script src="https://se-education.org/mind-maps-helper/mindmap.js"></script>
<script>
window.MindMap.palette = [
['#2f6f4f', '#7fc3a1'], /* first branch: light, dark */
['#8a3033', '#e29a9c'], /* second branch */
['#2563a8', '#8fbdf0'] /* third branch */
];
</script>
Setting it later recolours nothing by itself — re-render the maps that should pick it up.
Calling it from JavaScript
Nothing here is needed for an ordinary page: maps already in your HTML are
drawn on load. Use render() or renderAll() for maps
added after that. The rest inspect, control or refresh maps that have
already been drawn.
| Call | What it does | Returns |
|---|---|---|
MindMap.renderAll() | Draws every unrendered map on the page. Runs automatically on load. | Array of the containers drawn |
MindMap.renderAll(el) | Same, but only inside el. | Array of the containers drawn |
MindMap.render(el) | Draws one map. | The container that replaced it |
MindMap.parse(text) | Parses the source, without drawing anything. | The root node |
MindMap.collapseAll(el) | Folds every branch of one drawn map. | true, or false if el holds no map |
MindMap.expandAll(el) | Unfolds every branch of one drawn map. | true, or false if el holds no map |
MindMap.resetPositions(el) | Undoes every drag, returning nodes to the computed layout. | true, or false if el holds no map |
MindMap.setDirection(el, dir) | Switches shape from your own control: balanced, right or left. | true if the shape changed |
MindMap.refresh(el) | Re-measures the embedded blocks of one map, after you have changed what is in them. | true if anything was re-measured |
What el may be. For the two drawing calls it is
the map's source — the <pre class="mindmap">, or any
element inside a fenced block's wrapper; renderAll(el) instead takes
an element to search within. For every other call it is a map that has
already been drawn: the container returned by render(), any element
inside it, or an ancestor holding exactly one map. Hand one of those calls a
source that was never drawn, or an element with no map in it, and it changes
nothing and returns false.
The booleans mean "something changed", not "it worked".
setDirection() returns false for a direction the map is
already in, and equally for a misspelt one — right,
left and balanced are the only values accepted, and
anything else is ignored rather than reported. refresh() returns
false when a map has no embedded blocks to re-measure.
Drawing a map yourself:
<pre id="late" class="mindmap-pending">
Topics
Requirements
Design
</pre>
<script>
var el = document.getElementById('late');
el.className = 'mindmap'; // the script only matches maps it recognises
MindMap.render(el);
</script>
render() replaces the element it is given, so hold on to what it
returns if you mean to control the map afterwards. It does not throw on a bad
map: it returns a container showing an on-page error instead.
parse() is the one call that does throw — carrying the offending
line number — since it has no page to put a message on.
More about embedded blocks
This section is the fine print behind Blocks of your own HTML. Blocks meant to be read — a table, a card, a worked example — copy cleanly and need none of it; a working widget is worth checking in the node.
What is stripped from the copy. ids are removed,
so no id ends up duplicated, and so is anything that would start running when
the copy is inserted: <script>, <iframe>,
<object>, <embed>, <base>,
<meta>, <link>, <style>,
on* handlers and javascript: URLs however they are
spelled. Inside a <template> none of those has ever been live,
so the copy is where they would start.
What the copy is, is a copy. Anything wired up with
addEventListener stays behind on the original, so a button whose
handler was attached that way does nothing in the node. A delegated listener can
still catch it, but only where it is bound to an ancestor of the copy —
a shared page container the map itself sits inside. Bound to an ancestor of the
original block instead, it never hears the copy, which lives over in the map. And
dropping the ids costs whatever depended on them: a
<label for>, an aria-labelledby, a link to
#somewhere inside the block, a rule written as
#id .thing.
A block built by another script. The element has to be in the
page when the map is first drawn, and lateness is not forgiven. A map
naming an id that is not there fails with an on-page message, and that message
takes the place of the map's source — so there is nothing left for a later
MindMap.renderAll() to find, and no way for it to retry. Keep such a
map out of the automatic pass that runs on load, and draw it yourself once the
block is in the page:
<pre id="later" class="mindmap-pending">
Grades

</pre>
<script>
buildMarksBox(); // creates #marks-box
var el = document.getElementById('later');
el.className = 'mindmap'; // now the script will match it
MindMap.render(el);
</script>
Content Security Policy
A Content Security Policy can block the drawing without blocking the script. Two separate directives matter:
script-srchas to allowhttps://se-education.org, or the script never loads at all.style-srchas to allow the stylesheet the script injects. The map's CSS is added as a<style>element at load, which counts as an inline style, so a policy without'unsafe-inline'blocks it. The map still draws — the SVG is built either way — but it arrives with none of its styling: wrong colours, no sideways scrolling, and the outline meant for screen readers showing up as visible text. The browser names the offending directive in the console, along with a hash you could allow instead; the hash changes with every release, and there is no nonce hook today, so'unsafe-inline'is in practice the only stable answer.
Node positions are set through the CSSOM rather than written into markup, and CSP does not police that, so those survive a strict policy — it is the stylesheet that goes. Loosen only the directive you need, and only for the pages carrying maps.