Reference
Documentation
Everything you need to build hand-drawn animated diagrams with Sketchmark.
Getting Started
Installation
Sketchmark requires rough.js as a peer dependency. Both must be installed together.
npm install sketchmark roughjsrough.js must be available on window.rough at render time. In a bundler environment, load it via a script tag or import.
Quick Start
The render() function does everything in one call — parses the DSL, lays out elements, renders to SVG, and returns an animation controller.
import { render } from 'sketchmark';
const instance = render({
container: document.getElementById('diagram'),
dsl: `diagram
title label="Hello World"
theme primary fill="#e8f4ff" stroke="#0044cc" color="#003399"
box client label="Client" theme=primary width=130 height=52
box server label="Server" theme=primary width=130 height=52
cylinder db label="Database" width=130 height=64
client --> server label="HTTP"
server --> db label="SQL"
step highlight client
step draw client-->server
step highlight server
step draw server-->db
end`,
renderer: 'svg',
svgOptions: { showTitle: true, interactive: true, theme: 'light' },
});
// step through animation
instance.anim.next();
instance.anim.prev();
await instance.anim.play(800);CDN / No Bundler
Use an import map for clean imports without a build step.
<script src="https://unpkg.com/roughjs@4.6.6/bundled/rough.js"></script>
<script type="importmap">
{ "imports": { "sketchmark": "https://unpkg.com/sketchmark/dist/index.js" } }
</script>
<script type="module">
import { render } from 'sketchmark';
render({
container: document.getElementById('diagram'),
dsl: `diagram
box a label="Hello"
box b label="World"
a --> b
end`,
});
</script>DSL Reference
Every diagram starts with diagram and ends with end. Lines starting with # are comments.
Diagram Header
| Keyword | Example | Description |
|---|---|---|
| title | title label="My Diagram" | Title shown above the diagram |
| layout | layout row | Root layout: row, column, grid |
| config gap | config gap=60 | Gap between root-level items (default: 80) |
| config margin | config margin=40 | Outer canvas margin (default: 60) |
| config theme | config theme=ocean | Global palette name |
| config font | config font=caveat | Diagram-wide font |
Node Shapes
box id label="..." [theme=X] [width=N] [height=N]
circle id label="..."
diamond id label="..."
hexagon id label="..."
triangle id label="..."
cylinder id label="..."
parallelogram id label="..."
text id label="..."
image id label="..." url="https://..."All nodes share these style properties:
| Property | Example | Description |
|---|---|---|
| label | label="API Gateway" | Display text. Use \n for line breaks |
| theme | theme=primary | Named theme defined with theme keyword |
| width | width=140 | Override auto-width in px |
| height | height=55 | Override auto-height in px |
| fill | fill="#e8f4ff" | Background fill color |
| stroke | stroke="#0044cc" | Border stroke color |
| color | color="#003399" | Text color |
| font | font=caveat | Font family or built-in name |
| font-size | font-size=14 | Label font size in px |
| text-align | text-align=left | left, center, right |
| vertical-align | vertical-align=top | top, middle, bottom |
| line-height | line-height=1.5 | Line height multiplier for multiline labels |
| letter-spacing | letter-spacing=2 | Letter spacing in px |
width= to control the wrap width.Edges
fromId connector toId [label="..."] [stroke="#color"] [stroke-width=N]| Connector | Arrow | Line style |
|---|---|---|
| -> | end | solid |
| <- | start | solid |
| <-> | both | solid |
| --> | end | dashed |
| <--> | both | dashed |
| -- | none | solid |
| --- | none | dashed |
Edges also support typography: font, font-size,letter-spacing, color (label text).
Groups
Groups are flex containers. Children inherit the group's layout. Groups can be nested infinitely.
group id [label="..."] [layout=row|column|grid] [gap=N] [padding=N]
[justify=start|center|end|space-between|space-around]
[align=start|center|end]
[columns=N] [width=N] [height=N] [theme=X]
{
box child1 label="..."
group nested layout=row { ... }
}| Property | Default | Description |
|---|---|---|
| label | — | Label at top-left. Omit for no label and no reserved space |
| layout | column | row, column, or grid |
| gap | 10 | Space between children in px |
| padding | 26 | Inner padding in px |
| justify | start | Main-axis distribution (needs explicit width to be visible) |
| align | start | Cross-axis alignment |
| columns | 1 | Column count when layout=grid |
Bare Groups
bare is a layout-only container with no visible chrome — no label, no border, no background, no padding by default. Use it to compose layouts without visual noise.
bare id [layout=row|column|grid] [gap=N] [padding=N] ...
{
markdown intro width=320
"""
# Title
Some prose.
"""
group diagram layout=column gap=20 padding=30 theme=muted
{
box a label="Service A" theme=primary width=130 height=52
}
}bare overrides its invisible defaults.bare wrapper stroke="#ccc" padding=20 gives you a visible border with padding.Tables
table id [label="..."] [theme=X] [font=X] [font-size=N] [text-align=left|center|right]
{
header Col1 Col2 Col3
row val1 val2 val3
row val4 val5 val6
}Wrap cell values containing spaces in quotes: row Free "1k/day" Community. Header rows are always center-aligned; data rows respect text-align.
Notes
Sticky notes with a folded corner. Support multiline labels and full typography control.
note id label="Single line"
note id label="Line one
Line two
Line three"
note id label="..."
[width=N] [height=N]
[font=X] [font-size=N] [letter-spacing=N]
[text-align=left|center|right]
[vertical-align=top|middle|bottom]
[line-height=1.4]Charts
bar-chart id [title="..."] [width=N] [height=N]
line-chart id ...
area-chart id ...
pie-chart id ...
donut-chart id ...
scatter-chart id ...
data
[
["Label", "Series1", "Series2"],
["Jan", 120, 80 ],
["Feb", 150, 95 ]
]For pie and donut charts, each row is ["Label", value]. For scatter charts, use ["headers", "x", "y"] as the first row.
Markdown Blocks
Prose content with Markdown-style headings, bold, and italic — rendered inside the diagram layout alongside nodes and groups.
markdown id [width=N] [padding=N] [font=X]
[text-align=left|center|right] [color=X]
"""
# Heading 1
## Heading 2
### Heading 3
Normal paragraph with **bold** and *italic* text.
Another paragraph after a blank line.
"""| Syntax | Result |
|---|---|
| # text | H1 — large heading (40px) |
| ## text | H2 — medium heading (28px) |
| ### text | H3 — small heading (20px) |
| **text** | Bold |
| *text* | Italic |
| blank line | Vertical spacing |
Themes
Define reusable style presets with theme, then apply them to any element.
theme primary fill="#e8f4ff" stroke="#0044cc" color="#003399"
theme success fill="#e8ffe8" stroke="#007700" color="#004400"
theme warning fill="#fff9e6" stroke="#f0a500" color="#7a5000"
theme danger fill="#ffe8e8" stroke="#cc0000" color="#900000"
theme muted fill="#f5f5f5" stroke="#999999" color="#444444"
# apply to any element
box a theme=primary
group g theme=muted
note n theme=warning
a --> b theme=dangerTypography
Typography properties work on all text-bearing elements.
| Property | Applies to | Description |
|---|---|---|
| font | all | Font family or built-in name |
| font-size | all | Size in px |
| font-weight | all | 400, 500, 600, 700 |
| letter-spacing | all | Spacing in px |
| text-align | nodes, notes, table cells, markdown | left, center, right |
| vertical-align | nodes, notes | top, middle, bottom |
| line-height | nodes, notes, markdown | Multiplier e.g. 1.4 |
Animation
Step Actions
Add step instructions after your diagram elements. All actions work on nodes, groups, tables, notes, charts, and edges.
step action target [options]| Action | Syntax | Description |
|---|---|---|
| highlight | step highlight id | Pulsing glow — draws attention |
| draw | step draw id | Stroke-draw reveal animation |
| draw | step draw a-->b | Animate an edge appearing |
| fade | step fade id | Fade to 22% opacity |
| unfade | step unfade id | Restore full opacity |
| erase | step erase id | Fade to invisible |
| show | step show id | Make hidden element visible |
| hide | step hide id | Hide element instantly |
| pulse | step pulse id | Single brightness flash |
| color | step color id fill="#f00" | Change fill color |
| move | step move id dx=50 dy=0 | Translate by dx/dy px (cumulative) |
| scale | step scale id factor=1.5 | Scale — absolute, 1.0 = normal |
| rotate | step rotate id deg=45 | Rotate — cumulative degrees |
All actions accept an optional duration=N in milliseconds.
dx=50 applied twice = 100px total. scale is absolute — factor=1.0 always resets to normal. rotate is cumulative — use negative degrees to rotate back.AnimationController
The animation controller is returned as instance.anim.
const { anim } = render({ container, dsl });
anim.next(); // advance one step
anim.prev(); // go back one step
anim.reset(); // return to initial state
anim.goTo(3); // jump to step index (0-based)
await anim.play(800); // auto-play all steps, 800ms per step
anim.currentStep // current index (-1 = not started)
anim.total // total step count
anim.canNext // boolean
anim.canPrev // boolean
anim.steps // array of step objects
// listen to events
anim.on((event) => {
// event.type: 'step-change' | 'animation-reset'
// | 'animation-start' | 'animation-end' | 'step-complete'
console.log(event.stepIndex, event.step);
});Patterns
Slide-in entrance:
step move node dx=0 dy=80 # snap below final position
step draw node # reveal at offset
step move node dx=0 dy=-80 # animate up into final positionWobble and fail:
step rotate node deg=8
step rotate node deg=-8
step rotate node deg=8
step rotate node deg=25 # cumulative = 33°, toppling effect
step fade nodeTraffic shift (blue-green deploy):
step highlight lb
step draw lb-->blue
step move green dx=0 dy=60 # push green below
step draw green
step move green dx=0 dy=-60 duration=500
step fade blue
step draw lb-->green
step highlight greenLayout System
Root Layout
layout row # items flow left to right (default)
layout column # items flow top to bottom
layout grid # grid — set columns with: config columns=NGroup Layout
Each group is an independent flex container with its own layout settings.
group g layout=row justify=space-between width=500 gap=16 padding=20
{
box a label="A" width=100 height=50
box b label="B" width=100 height=50
box c label="C" width=100 height=50
}Justify & Align
| justify value | Effect |
|---|---|
| start | Pack children to start (default) |
| center | Center children in container |
| end | Pack children to end |
| space-between | First at start, last at end, equal gaps between |
| space-around | Equal space around each child |
justify requires an explicit width larger than the total child width to have a visible effect.| align value | Effect |
|---|---|
| start | Cross-axis start (default) |
| center | Cross-axis center |
| end | Cross-axis end |
Themes & Fonts
Palettes
Set a global palette with config theme=NAME.
| Name | Description |
|---|---|
| light | Warm parchment (default) |
| dark | Dark warm background |
| ocean | Cool blues |
| forest | Greens |
| sunset | Warm oranges and reds |
| slate | Cool grays |
| rose | Pinks and magentas |
| midnight | GitHub-dark style blues |
| sketch | Graphite pencil-on-paper |
List palette names at runtime:
import { THEME_NAMES } from 'sketchmark';
// ['light', 'dark', 'ocean', 'forest', 'sunset', 'slate', 'rose', 'midnight', 'sketch']Font System
Built-in fonts load automatically from Google Fonts on first use.
| Name | Style |
|---|---|
| caveat | Hand-drawn, casual |
| handlee | Hand-drawn, friendly |
| indie-flower | Hand-drawn, playful |
| patrick-hand | Hand-drawn, clean |
| dm-mono | Monospace, refined |
| jetbrains | Monospace, code-like |
| instrument | Serif, editorial |
| playfair | Serif, elegant |
| system | System UI sans-serif |
| mono | Courier New |
| serif | Georgia |
config font=caveat # diagram-wide
box a label="Hand-drawn" font=caveat
box b label="Code style" font=dm-mono font-size=11Custom Themes
Register any font already loaded in the page:
import { registerFont } from 'sketchmark';
// font already loaded via <link> or @import
registerFont('brand', '"Brand Sans", sans-serif');
// then use in DSL
// config font=brand
// box a font=brandPass a full CSS family directly in DSL (must be quoted):
box a label="Hello" font="'Pacifico', cursive"API Reference
render(options)
One-call API that parses DSL, lays out, renders, and returns a controller.
import { render } from 'sketchmark';
const instance = render({
container: '#my-div', // CSS selector, HTMLElement, or SVGSVGElement
dsl: '...',
renderer: 'svg', // 'svg' (default) | 'canvas'
injectCSS: true, // inject animation CSS into <head>
svgOptions: {
showTitle: true,
interactive: true, // hover + click handlers on nodes
roughness: 1.3, // rough.js roughness (0 = smooth)
bowing: 0.7, // rough.js bowing
theme: 'light', // 'light' | 'dark' | 'auto'
transparent: false, // remove background rect
onNodeClick: (nodeId) => {}, // callback on node click
},
canvasOptions: {
scale: 2, // pixel density (default: devicePixelRatio)
roughness: 1.3,
transparent: false,
},
onNodeClick: (nodeId) => {},
onReady: (anim, svg) => {},
});
// returned instance
instance.scene // SceneGraph — all positioned nodes, edges, groups
instance.anim // AnimationController
instance.svg // SVGSVGElement (renderer='svg')
instance.canvas // HTMLCanvasElement (renderer='canvas')
instance.update(dsl) // re-render with new DSL, preserves pan/zoom
instance.exportSVG() // trigger SVG file download
instance.exportPNG() // trigger PNG file downloadtheme: 'auto' follows the OS prefers-color-scheme setting. transparent: true removes the background rect so the diagram floats over whatever is behind it.Pipeline API
Use the low-level pipeline when you need to control each step manually.
import { parse, buildSceneGraph, layout, renderToSVG } from 'sketchmark';
// each step returns the data structure of the next
const ast = parse(dslString); // DSL string → DiagramAST
const scene = buildSceneGraph(ast); // AST → SceneGraph (unpositioned)
layout(scene); // compute x/y positions (mutates)
const svg = renderToSVG(scene, containerEl, {
theme: 'light',
showTitle: true,
});
// error handling
import { ParseError } from 'sketchmark';
try {
const ast = parse(dsl);
} catch (e) {
if (e instanceof ParseError) {
console.error(`Line ${e.line}, Col ${e.col}: ${e.message}`);
}
}Export
import { exportSVG, exportPNG, exportHTML, getSVGBlob } from 'sketchmark';
// trigger file downloads
exportSVG(svgEl, { filename: 'diagram.svg' });
await exportPNG(svgEl, { filename: 'diagram.png', scale: 2 });
// self-contained HTML with embedded SVG + DSL comment
exportHTML(svgEl, dslSource, { filename: 'diagram.html' });
// get blob without downloading
const blob = getSVGBlob(svgEl);
// via the render() instance
instance.exportSVG('diagram.svg');
await instance.exportPNG('diagram.png');Built something with Sketchmark? Share it on GitHub ↗