Display
Mind map
One idea in the middle and everything that grows from it. Drag to look around, pinch or use the buttons to zoom, and close branches you don’t need right now.
Installation
npx sinaui add mind-mapCopy src/lib/ui/MindMap.svelte and mindmap.ts, with Button.svelte, Icon.svelte, Tooltip.svelte, toast/, announce.ts, motion.ts and tokens.css. No other dependencies. Put a <Toaster /> in your layout
so removing a topic can be undone.
Examples
The House of Wisdom
Read only. Astronomy starts closed: its number says how much is inside. Tap it to open.
Arrow keys move between topics, Space opens or closes a branch, Home goes to the center.
<script lang="ts">
import MindMap from '#lib/ui/MindMap.svelte';
import type { MindNode } from '#lib/ui/mindmap.js';
import {
BookOpen01Icon,
MedicineBottle01Icon,
SunCloud01Icon,
CalculatorIcon
} from '@hugeicons/core-free-icons';
let root: MindNode = $state({
id: 'wisdom',
label: 'The House of Wisdom',
note: 'Baghdad, 9th century',
children: [
{
id: 'translation',
label: 'Translation',
icon: BookOpen01Icon,
children: [
{
id: 'greek',
label: 'Greek',
children: [
{ id: 'aristotle', label: 'Aristotle' },
{ id: 'galen', label: 'Galen' }
]
},
{ id: 'persian', label: 'Persian' },
{ id: 'sanskrit', label: 'Sanskrit', note: 'Siddhanta tables' }
]
},
{
id: 'astronomy',
label: 'Astronomy',
icon: SunCloud01Icon,
collapsed: true,
children: [
{ id: 'observatory', label: 'Shammasiyya observatory' },
{ id: 'zij', label: 'Star tables (zij)' },
{ id: 'earth', label: 'Measuring the Earth' }
]
},
{
id: 'medicine',
label: 'Medicine',
icon: MedicineBottle01Icon,
children: [
{ id: 'hunayn', label: 'Hunayn ibn Ishaq', note: 'Ten treatises on the eye' },
{ id: 'hospitals', label: 'Hospitals (bimaristan)' }
]
},
{
id: 'mathematics',
label: 'Mathematics',
icon: CalculatorIcon,
children: [
{
id: 'algebra',
label: 'Al-Khwarizmi’s algebra',
children: [
{ id: 'jabr', label: 'Completion (al-jabr)' },
{ id: 'muqabala', label: 'Balancing' }
]
},
{ id: 'numerals', label: 'Hindu numerals' }
]
}
]
});
</script>
<div class="frame">
<MindMap bind:root label="The House of Wisdom" />
</div>
<style>
.frame {
inline-size: 100%;
block-size: 30rem;
}
</style>Plan the library’s new reading room
Editable. Pick a topic and press Tab to add inside it, Enter to add beside it, F2 or double-click to rename, Delete to remove (with undo). Drag a topic onto another to move it there.
Arrow keys move between topics, Space opens or closes a branch, Home goes to the center. Enter adds a topic beside this one, Tab adds one inside it, F2 renames, Delete removes.
<script lang="ts">
import MindMap from '#lib/ui/MindMap.svelte';
import { toOutline, type MindNode } from '#lib/ui/mindmap.js';
import CopyButton from '#lib/ui/CopyButton.svelte';
let root: MindNode = $state({
id: 'room',
label: 'Plan the library’s new reading room',
children: [
{
id: 'space',
label: 'Space',
children: [
{ id: 'light', label: 'North light for copyists' },
{ id: 'quiet', label: 'Quiet corner' }
]
},
{
id: 'collection',
label: 'Collection',
children: [
{ id: 'optics', label: 'Book of Optics' },
{ id: 'canon', label: 'Canon of Medicine' }
]
},
{ id: 'people', label: 'People', children: [{ id: 'keeper', label: 'A keeper of books' }] },
{ id: 'budget', label: 'Budget' }
]
});
</script>
<div class="stack">
<div class="frame">
<MindMap bind:root label="Plan for the reading room" editable />
</div>
<CopyButton value={() => toOutline(root)} size="sm">Copy as outline</CopyButton>
</div>
<style>
.stack {
inline-size: 100%;
display: grid;
justify-items: start;
gap: 0.75rem;
}
.frame {
justify-self: stretch;
block-size: 30rem;
}
</style>A book’s outline
All branches to the right, reading like a table of contents.
Arrow keys move between topics, Space opens or closes a branch, Home goes to the center.
<script lang="ts">
import MindMap from '#lib/ui/MindMap.svelte';
import type { MindNode } from '#lib/ui/mindmap.js';
let root: MindNode = $state({
id: 'optics',
label: 'Book of Optics',
note: 'Ibn al-Haytham',
children: [
{
id: 'b1',
label: 'Book I: How we see',
children: [
{ id: 'b1-rays', label: 'Light enters the eye' },
{ id: 'b1-eye', label: 'Parts of the eye' }
]
},
{
id: 'b2',
label: 'Book II: What we see',
children: [{ id: 'b2-form', label: 'Recognizing form' }]
},
{ id: 'b3', label: 'Book III: Errors of sight' },
{
id: 'b4',
label: 'Books IV–VII: Reflection and refraction',
children: [
{ id: 'mirrors', label: 'Mirrors' },
{ id: 'alhazen', label: 'Alhazen’s problem' },
{ id: 'refraction', label: 'Refraction' }
]
}
]
});
</script>
<div class="frame">
<MindMap bind:root label="Outline of the Book of Optics" sides="right" />
</div>
<style>
.frame {
inline-size: 100%;
block-size: 28rem;
}
</style>Props
| Prop | Type | Default |
|---|---|---|
root | MindNode, bindable: { id, label, children?, collapsed?, color?, icon?, note? } | |
label | string (required) | |
editable | boolean | false |
sides | 'both' | 'right' | 'both' |
onchange | (root) => void |
A first-level topic’s color colors its whole branch. The map fills its container:
give the container a height. mindmap.ts also exports toOutline(root) (indented text) and toJSON(root).
Keyboard
| Key | Action |
|---|---|
| ← → ↑ ↓ | The nearest topic in that direction. |
| Home | The center. |
| Space | Open or close a branch. |
| Enter | Editable: add a topic beside this one. Otherwise: open or close. |
| Tab | Editable: add a topic inside this one. Shift Tab leaves the map. |
| F2 | Rename. Enter keeps it, Esc cancels. |
| Delete Backspace | Remove the topic and what’s inside it. |
| ⌘/Ctrl + scroll | Zoom. |
Accessibility
- A tree with one Tab stop. Each topic is read with its level and place, branches say whether they’re open, and the chosen topic is marked selected.
- The keys are described to screen readers; closing, moving and removing are announced.
- The map follows the keyboard: moving to a topic off screen brings it into view.
- Topics are real text, so they can be found, read and translated.
- With reduced motion, topics appear and move without gliding.