A React-based mind map component with multiple layout strategies, theming support, and rich node features.
BASHnpm install @clipmind/mindmap # or yarn add @clipmind/mindmap # or pnpm add @clipmind/mindmap
Make sure you have the following peer dependencies installed:
BASHnpm install react react-dom mobx mobx-react styled-components slate slate-react slate-history
TYPESCRIPTimport { useEffect, useState } from 'react'; import { MindMapEditor, generateStore, loadFromMarkdown } from '@clipmind/mindmap'; import styled from 'styled-components'; import type { MindMapStore } from '@clipmind/mindmap'; const Container = styled.div` width: 100vw; height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; `; export function MindmapDemo() { const [store, setStore] = useState<MindMapStore | null>(null); useEffect(() => { // generateStore is async to ensure fonts are loaded before layout calculation generateStore().then(s => { loadFromMarkdown('# Hello World\n\n## Item 1\n### Item 1.1\n## Item 2', s); setStore(s); } ); }, []); if (!store) { return ( <Container>{'Loading...'}</Container> ); } return ( <Container> <MindMapEditor store={store} /> </Container> ); }
Use generateStore to create a properly initialized MindMapStore. This function is async to ensure fonts are fully loaded before layout calculations, preventing text truncation issues.
TYPESCRIPTimport { generateStore } from '@clipmind/mindmap'; // Basic usage const store = await generateStore(); // With custom font const store = await generateStore({ fontFamily: 'Inter' });
| Option | Type | Default | Description |
|---|---|---|---|
fontFamily | string | 'Lexend' | Google Font to use for node text |
The following Google Fonts are supported:
Lexend, Roboto, Open Sans, Lato, Montserrat, Oswald, Source Sans Pro, Raleway, PT Sans, Merriweather, Inter, Roboto Mono, Fira Code, JetBrains Mono, Poppins, Quicksand, Playfair Display, Lora, Pacifico, Dancing Script
You can import mind map data from markdown text using the loadFromMarkdown function.
Use the ClipMind API to generate markdown from any text input.
Get your API Token: Visit API Docs to obtain your API token.
BASHcurl -X POST https://api.clipmind.tech/public/mindmaps/markdown \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -d '{ "text": "My MindMap\n- Idea 1\n- Idea 2", "options": { "mode": "summarize", "language": "english", "length": "medium" } }'
TYPESCRIPTimport { loadFromMarkdown, generateStore } from '@clipmind/mindmap'; // Create store (async to ensure fonts are loaded) const store = await generateStore(); // Fetch markdown from ClipMind API const response = await fetch('https://api.clipmind.tech/public/mindmaps/markdown', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${YOUR_API_TOKEN}`, }, body: JSON.stringify({ text: 'React 入门指南', options: { mode: 'summarize', language: 'chinese', length: 'medium', }, }), }); const { data } = await response.json(); // Load markdown into store loadFromMarkdown(data, store);
You can also load markdown directly without using the API:
TYPESCRIPTconst markdown = `# React 入门指南 ## 核心概念 - JSX 语法 - 组件化开发 - 函数组件 - 类组件 ## Hooks - useState - useEffect - useContext`; loadFromMarkdown(markdown, store);
The parser supports the following markdown syntax:
MARKDOWN# Central Topic ## Branch 1 - Item 1.1 - Item 1.2 -- Sub-item 1.2.1 ## Branch 2 - Item 2.1
Heading levels:
# → Root node (level 1)## → First-level branches (level 2)### → Second-level branches (level 3)List items:
- → Child node-- → Grandchild node--- → Great-grandchild node - Item (2 spaces = deeper level)Include images with optional dimensions:
MARKDOWN# My Mind Map ## {100x80} Brand -  Feature 1
Supported dimension formats:
{100x80} - width x height{width=100 height=80} - explicit attributes{width=100} - width only{100} - width only (shorthand)Export with convertMindMapToMarkdownWithMetadata preserves node styles:
MARKDOWN# Central Topic ## Branch 1 <!-- clipmind-metadata {"version":"cm-md/1","nodes":[{"shape":"rounded"},{"markers":["star"]}]} -->
Save and restore mind map state for persistence:
TYPESCRIPT// Save to localStorage const data = await store.serialize(); localStorage.setItem('mindmap', JSON.stringify(data)); // Restore from localStorage const saved = localStorage.getItem('mindmap'); if (saved) { store.deserialize(JSON.parse(saved)); }
TYPESCRIPTinterface IMindMapData { id: string; rootNode: IMindMapNode; theme: string; // Color theme ID layout: { type: ILayoutType; // 'mindmap' | 'logic' | 'org' | 'timeline' | 'tree' | 'fishbone' theme: string; // Layout theme ID }; resource?: IMindMapResource; // Images and other resources createdAt?: string; updatedAt?: string; }
TYPESCRIPT// Add a child node store.addChildNode({ title: 'New Child', parentId: 'root', // Parent node ID enableScrollToNode: true, // Auto-scroll to new node }); // Add a sibling node (same level as specified node) store.addSiblingNode('node-id'); // Get the ID of the last added child const newNodeId = store.getLastChildNodeId('parent-id');
TYPESCRIPT// Delete single node store.deleteNodes(['node-id']); // Delete multiple nodes store.deleteNodes(['node-1', 'node-2', 'node-3']); // Note: Root node ('root') cannot be deleted
TYPESCRIPT// Set node text store.setNodeText('node-id', 'New Title'); // Set node text without saving to history store.setNodeText('node-id', 'New Title', false); // Update node image store.updateNodeImage('node-id', { src: 'https://example.com/image.png', alt: 'Description', box: { x: 0, y: 0, w: 100, h: 80 } });
TYPESCRIPT// Select a node store.setSelectedNodes(['node-id']); // Get selected nodes const selected = store.selectedNodes; // Clear selection store.setSelectedNodes([]);
Add icon markers to nodes:
TYPESCRIPT// Available markers: 'star', 'heart', 'flag', 'check', 'cross', 'question', etc. store.addMarker('node-id', 'star'); store.removeMarker('node-id', 'star');
Add todo checkboxes to nodes:
TYPESCRIPTstore.setNodeCheckbox('node-id', { checked: false, shape: 'square' // 'square' | 'circle' | 'diamond' }); // Toggle checkbox store.toggleNodeCheckbox('node-id');
Add visual boundaries around node groups:
TYPESCRIPTstore.setNodeBoundary('node-id', { enabled: true, style: 'rounded', // 'rounded' | 'square' | 'wave' color: '#ff6b35' });
Apply custom styles to individual nodes:
TYPESCRIPTstore.setNodeCustomStyle('node-id', { backgroundColor: '#ff6b35', textColor: '#ffffff', fontSize: 16, fontWeight: 'bold' });
TYPESCRIPT// Check if operations are available const canUndo = store.canUndo(); const canRedo = store.canRedo(); // Perform undo/redo store.undo(); store.redo();
TYPESCRIPTstore.saveSvg({ isCopy: false, // Copy to clipboard instead of download exportCheckbox: true, // Include checkbox visuals sealConfig: { // Optional watermark style: 'corner', text: 'My Brand' } });
TYPESCRIPTstore.savePng({ scale: 2, // Export scale (1-4) transparentBackground: false, // Transparent or themed background isCopy: false, // Copy to clipboard exportCheckbox: true, targetWidth: 1920, // Optional fixed width targetHeight: 1080 // Optional fixed height });
TYPESCRIPTstore.saveJpg({ scale: 2, exportCheckbox: true, targetWidth: 1920, targetHeight: 1080 });
TYPESCRIPTstore.savePdf({ scale: 2, transparentBackground: false, exportCheckbox: true });
TYPESCRIPT// Get PNG as data URL (for thumbnails, previews) const dataUrl = await store.savePngPreview({ scale: 1, transparentBackground: false });
TYPESCRIPT// Set layout type and theme store.setLayout('mindmap', 'mindmap-underline'); store.setLayout('logic', 'logic-minimal'); store.setLayout('org', 'org-corporate'); store.setLayout('timeline', 'timeline-default'); store.setLayout('tree', 'tree-trunk'); store.setLayout('fishbone', 'fishbone-classic');
| Layout Type | Available Themes |
|---|---|
mindmap | mindmap-underline, mindmap-modern, mindmap-organic, mindmap-creative |
logic | logic-underline, logic-underline-stack, logic-minimal, logic-minimal-circle, logic-modern |
org | org-corporate, org-modern |
timeline | timeline-default, timeline-axis, timeline-horizontal |
tree | tree-trunk, tree-structure |
fishbone | fishbone-classic, fishbone-modern |
TYPESCRIPTimport { irisTheme, neonTheme, amethystTheme, sunsetTheme, oceanTheme, forestTheme, cherryBlossomTheme, volcanoTheme, hermesOrangeTheme, tiffanyBlueTheme, rainbowTheme, premiumBlackGrayTheme, roseGoldTheme, deepSeaTheme, lavenderTheme, champagneTheme, midnightBlueTheme, emeraldTheme, chinaRedTheme, frenchFlagTheme, germanFlagTheme, americanFlagTheme, japaneseFlagTheme, roseTheme, slateTheme, goldTheme, cyanTheme, indigoTheme, } from '@clipmind/mindmap';
TYPESCRIPTimport { darkIrisTheme, darkNeonTheme, darkAmethystTheme, darkSunsetTheme, darkOceanTheme, darkForestTheme, darkCherryBlossomTheme, darkVolcanoTheme, darkHermesOrangeTheme, darkTiffanyBlueTheme, darkRainbowTheme, darkPremiumBlackGrayTheme, darkRoseGoldTheme, darkDeepSeaTheme, darkLavenderTheme, darkChampagneTheme, darkMidnightBlueTheme, darkEmeraldTheme, darkChinaRedTheme, darkFrenchFlagTheme, darkGermanFlagTheme, darkAmericanFlagTheme, darkJapaneseFlagTheme, darkRoseTheme, darkSlateTheme, darkGoldTheme, darkCyanTheme, darkIndigoTheme, } from '@clipmind/mindmap';
TYPESCRIPTimport { AVAILABLE_THEMES, LIGHT_THEMES, DARK_THEMES } from '@clipmind/mindmap'; // Get all themes console.log(AVAILABLE_THEMES.length); // 56 themes // Apply a theme store.colorTheme = darkNeonTheme; // Reset node colors after theme change store.resetNodeThemes();
TYPESCRIPT// Fit mind map to screen store.canvas.fitToScreen(); // Zoom controls store.canvas.zoomIn(); store.canvas.zoomOut(); store.canvas.setScale(1.5); // Get current view state const view = store.canvas.getView(); // { x: number, y: number, scale: number }
Get your API Token: Visit API Docs to obtain your API token.
TYPESCRIPTimport { useState, useEffect } from 'react'; import { MindMapStore, MindMapEditor, generateStore, loadFromMarkdown, } from '@clipmind/mindmap'; // Get your token from https://clipmind.tech/workspace const API_TOKEN = 'your-api-token'; function MindMapDemo() { const [store, setStore] = useState<MindMapStore | null>(null); useEffect(() => { // generateStore is async to ensure fonts are loaded before layout calculation generateStore().then(setStore); }, []); const handleGenerateFromAPI = async () => { if (!store) return; const response = await fetch('https://api.clipmind.tech/public/mindmaps/markdown', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${API_TOKEN}`, }, body: JSON.stringify({ text: 'React 入门指南', options: { mode: 'brainstorm', language: 'chinese', length: 'medium', }, }), }); const { data } = await response.json(); loadFromMarkdown(data, store); }; const handleExport = () => { store?.savePng({ scale: 2 }); }; const handleSave = async () => { if (!store) return; const data = await store.serialize(); localStorage.setItem('mindmap', JSON.stringify(data)); }; const handleLoad = () => { const saved = localStorage.getItem('mindmap'); if (saved && store) { store.deserialize(JSON.parse(saved)); } }; if (!store) { return <div>Loading...</div>; } return ( <div style={{ width: '100vw', height: '100vh' }}> <MindMapEditor store={store} /> <div style={{ display: 'flex', justifyContent: 'center', position: 'absolute', top: 80, left: '50%', transform: 'translateX(-50%)', gap: 16 }}> <button onClick={handleGenerateFromAPI}>Generate from API</button> <button onClick={handleExport}>Export PNG</button> <button onClick={handleSave}>Save</button> <button onClick={handleLoad}>Load</button> </div> </div> ); }