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.

terminal
npm install sketchmark roughjs

rough.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.

your-diagram.ts
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.

index.html
<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

KeywordExampleDescription
titletitle label="My Diagram"Title shown above the diagram
layoutlayout rowRoot layout: row, column, grid
config gapconfig gap=60Gap between root-level items (default: 80)
config marginconfig margin=40Outer canvas margin (default: 60)
config themeconfig theme=oceanGlobal palette name
config fontconfig font=caveatDiagram-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:

PropertyExampleDescription
labellabel="API Gateway"Display text. Use \n for line breaks
themetheme=primaryNamed theme defined with theme keyword
widthwidth=140Override auto-width in px
heightheight=55Override auto-height in px
fillfill="#e8f4ff"Background fill color
strokestroke="#0044cc"Border stroke color
colorcolor="#003399"Text color
fontfont=caveatFont family or built-in name
font-sizefont-size=14Label font size in px
text-aligntext-align=leftleft, center, right
vertical-alignvertical-align=toptop, middle, bottom
line-heightline-height=1.5Line height multiplier for multiline labels
letter-spacingletter-spacing=2Letter spacing in px
The text shape has no border or background — it auto word-wraps long content. Set width= to control the wrap width.

Edges

fromId  connector  toId  [label="..."] [stroke="#color"] [stroke-width=N]
ConnectorArrowLine style
->endsolid
<-startsolid
<->bothsolid
-->enddashed
<-->bothdashed
--nonesolid
---nonedashed

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 { ... }
}
PropertyDefaultDescription
labelLabel at top-left. Omit for no label and no reserved space
layoutcolumnrow, column, or grid
gap10Space between children in px
padding26Inner padding in px
justifystartMain-axis distribution (needs explicit width to be visible)
alignstartCross-axis alignment
columns1Column 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
  }
}
Any style property explicitly set on a 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.
"""
SyntaxResult
# textH1 — large heading (40px)
## textH2 — medium heading (28px)
### textH3 — small heading (20px)
**text**Bold
*text*Italic
blank lineVertical 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=danger

Typography

Typography properties work on all text-bearing elements.

PropertyApplies toDescription
fontallFont family or built-in name
font-sizeallSize in px
font-weightall400, 500, 600, 700
letter-spacingallSpacing in px
text-alignnodes, notes, table cells, markdownleft, center, right
vertical-alignnodes, notestop, middle, bottom
line-heightnodes, notes, markdownMultiplier 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]
ActionSyntaxDescription
highlightstep highlight idPulsing glow — draws attention
drawstep draw idStroke-draw reveal animation
drawstep draw a-->bAnimate an edge appearing
fadestep fade idFade to 22% opacity
unfadestep unfade idRestore full opacity
erasestep erase idFade to invisible
showstep show idMake hidden element visible
hidestep hide idHide element instantly
pulsestep pulse idSingle brightness flash
colorstep color id fill="#f00"Change fill color
movestep move id dx=50 dy=0Translate by dx/dy px (cumulative)
scalestep scale id factor=1.5Scale — absolute, 1.0 = normal
rotatestep rotate id deg=45Rotate — cumulative degrees

All actions accept an optional duration=N in milliseconds.

move is cumulative — 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.

animation-api.ts
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 position

Wobble 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   node

Traffic 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 green

Layout 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=N

Group 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 valueEffect
startPack children to start (default)
centerCenter children in container
endPack children to end
space-betweenFirst at start, last at end, equal gaps between
space-aroundEqual space around each child
justify requires an explicit width larger than the total child width to have a visible effect.
align valueEffect
startCross-axis start (default)
centerCross-axis center
endCross-axis end

Themes & Fonts

Palettes

Set a global palette with config theme=NAME.

NameDescription
lightWarm parchment (default)
darkDark warm background
oceanCool blues
forestGreens
sunsetWarm oranges and reds
slateCool grays
rosePinks and magentas
midnightGitHub-dark style blues
sketchGraphite 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.

NameStyle
caveatHand-drawn, casual
handleeHand-drawn, friendly
indie-flowerHand-drawn, playful
patrick-handHand-drawn, clean
dm-monoMonospace, refined
jetbrainsMonospace, code-like
instrumentSerif, editorial
playfairSerif, elegant
systemSystem UI sans-serif
monoCourier New
serifGeorgia
config font=caveat               # diagram-wide

box a label="Hand-drawn" font=caveat
box b label="Code style"  font=dm-mono font-size=11

Custom Themes

Register any font already loaded in the page:

register-font.ts
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=brand

Pass 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.

render-api.ts
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 download
theme: '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.

pipeline.ts
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

export.ts
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 ↗