CtrlK
BlogDocsLog inGet started
Tessl Logo

excalidraw

Create Excalidraw diagrams programmatically. Use when the user wants architecture diagrams, system designs, flowcharts, or any visual diagram with a hand-drawn aesthetic. Outputs editable .excalidraw JSON files. Best for architecture overviews and presentations where the hand-drawn style and drag-and-drop editing add value over Mermaid's text-only approach.

90

Quality

88%

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Advisory

Suggest reviewing before use

SKILL.md
Quality
Evals
Security

Excalidraw Diagram Generator

Generate Excalidraw diagrams programmatically using Python. Produces professional-looking, editable .excalidraw files instead of ASCII art.

Output format: .excalidraw JSON files that can be:

  • Opened at https://excalidraw.com (drag and drop the file)
  • Edited in VS Code with the Excalidraw extension
  • Exported to SVG/PNG for embedding in documentation

Token cost note: Excalidraw JSON is verbose (4,000-22,000+ tokens per file). Delegate diagram generation to subagents when context is tight.

Quick Start

Method 1: Direct Python Script (Recommended)

Write a Python script using the generator library and run it:

#!/usr/bin/env python3
import sys, os
sys.path.insert(0, os.path.expanduser("~/.claude/plugins/duet/skills/excalidraw/scripts"))
from excalidraw_generator import Diagram, Flowchart, ArchitectureDiagram

# Create your diagram
d = Diagram()
box1 = d.box(100, 100, "Step 1", color="blue")
box2 = d.box(300, 100, "Step 2", color="green")
d.arrow_between(box1, box2, "next")
d.save("my_diagram.excalidraw")

Run with:

python3 /path/to/your_script.py

Method 2: Inline Python Execution

python3 -c "
import sys, os
sys.path.insert(0, os.path.expanduser('~/.claude/plugins/duet/skills/excalidraw/scripts'))
from excalidraw_generator import Diagram

d = Diagram()
a = d.box(100, 100, 'Hello', color='blue')
b = d.box(300, 100, 'World', color='green')
d.arrow_between(a, b)
d.save('hello.excalidraw')
print('Created: hello.excalidraw')
"

API Reference

Diagram Class

The base class for all diagrams.

from excalidraw_generator import Diagram

d = Diagram(background="#ffffff")  # white background

Methods

box(x, y, label, width=150, height=60, color="blue", shape="rectangle", font_size=18)

Create a labeled shape. Returns an Element for connecting.

  • x, y: Position (top-left corner)
  • label: Text to display
  • color: "blue", "green", "red", "yellow", "orange", "violet", "cyan", "teal", "gray", "black"
  • shape: "rectangle", "ellipse", "diamond"
box1 = d.box(100, 100, "Process A", color="blue")
box2 = d.box(100, 200, "Decision?", color="yellow", shape="diamond")

text_box(x, y, content, font_size=20, color="black")

Create standalone text.

d.text_box(100, 50, "System Architecture", font_size=28, color="black")

arrow_between(source, target, label=None, color="black", from_side="auto", to_side="auto")

Draw an arrow between two elements.

  • from_side, to_side: "auto", "left", "right", "top", "bottom"
d.arrow_between(box1, box2, "sends data")
d.arrow_between(box1, box3, from_side="bottom", to_side="top")

line_between(source, target, color="black")

Draw a line (no arrowhead) between elements.

save(path)

Save the diagram. Extension .excalidraw added if not present.

d.save("output/my_diagram")  # Creates output/my_diagram.excalidraw

Flowchart Class

Specialized for flowcharts with automatic positioning.

from excalidraw_generator import Flowchart

fc = Flowchart(direction="vertical", spacing=80)

fc.start("Begin")
fc.process("p1", "Process Data")
fc.decision("d1", "Valid?")
fc.process("p2", "Save")
fc.end("Done")

fc.connect("__start__", "p1")
fc.connect("p1", "d1")
fc.connect("d1", "p2", label="Yes")
fc.connect("d1", "__end__", label="No")

fc.save("flowchart.excalidraw")

Methods

  • start(label="Start") - Green ellipse
  • end(label="End") - Red ellipse
  • process(node_id, label, color="blue") - Blue rectangle
  • decision(node_id, label, color="yellow") - Yellow diamond
  • node(node_id, label, shape, color, width, height) - Generic node
  • connect(from_id, to_id, label=None) - Arrow between nodes
  • position_at(x, y) - Set position for next node

AutoLayoutFlowchart Class

For complex flowcharts with automatic hierarchical layout. Requires grandalf package (pip install grandalf).

from excalidraw_generator import AutoLayoutFlowchart, DiagramStyle, FlowchartStyle, LayoutConfig

fc = AutoLayoutFlowchart(
    diagram_style=DiagramStyle(roughness=0),
    flowchart_style=FlowchartStyle(decision_color="orange"),
    layout_config=LayoutConfig(vertical_spacing=100, horizontal_spacing=80)
)

fc.add_node("start", "Start", shape="ellipse", color="green", node_type="terminal")
fc.add_node("process1", "Validate Input", node_type="process")
fc.add_node("check", "Is Valid?", shape="diamond", color="yellow", node_type="decision")
fc.add_node("success", "Process Data", node_type="process")
fc.add_node("error", "Show Error", color="red", node_type="process")
fc.add_node("end", "End", shape="ellipse", color="red", node_type="terminal")

fc.add_edge("start", "process1")
fc.add_edge("process1", "check")
fc.add_edge("check", "success", label="Yes")
fc.add_edge("check", "error", label="No")
fc.add_edge("success", "end")
fc.add_edge("error", "process1", label="Retry")

result = fc.compute_layout(two_column=True, target_aspect_ratio=0.8)
fc.save("auto_flowchart.excalidraw")

ArchitectureDiagram Class

For system architecture diagrams.

from excalidraw_generator import ArchitectureDiagram

arch = ArchitectureDiagram()

arch.user("user", "User", x=100, y=200)
arch.component("frontend", "React App", x=250, y=200, color="blue")
arch.service("api", "API Gateway", x=450, y=200, color="violet")
arch.database("db", "PostgreSQL", x=650, y=200, color="green")

arch.connect("user", "frontend", "HTTPS")
arch.connect("frontend", "api", "REST")
arch.connect("api", "db", "SQL")

arch.save("architecture.excalidraw")

Methods

  • component(id, label, x, y, width=150, height=80, color="blue")
  • database(id, label, x, y, color="green") - Ellipse shape
  • service(id, label, x, y, color="violet")
  • user(id, label="User", x=100, y=100) - Gray ellipse
  • connect(from_id, to_id, label=None, bidirectional=False)

Configuration & Styling

DiagramStyle - Global Appearance

from excalidraw_generator import Diagram, DiagramStyle

d = Diagram(diagram_style=DiagramStyle(
    roughness=0,              # 0=clean, 1=hand-drawn, 2=rough sketch
    stroke_style="solid",     # "solid", "dashed", "dotted"
    stroke_width=2,           # Line thickness (1-4)
    color_scheme="corporate"  # Named color scheme
))

Roughness levels:

  • 0 - Architect: Clean, precise lines
  • 1 - Artist: Normal hand-drawn look (default)
  • 2 - Cartoonist: Rough, sketchy appearance

Color Schemes

d = Diagram(diagram_style=DiagramStyle(color_scheme="vibrant"))

primary = d.scheme_color("primary")
secondary = d.scheme_color("secondary")
accent = d.scheme_color("accent")
warning = d.scheme_color("warning")
danger = d.scheme_color("danger")
neutral = d.scheme_color("neutral")
SchemePrimarySecondaryAccentWarningDanger
defaultbluegreenvioletyellowred
monochromeblackgraygraygrayblack
corporatebluetealvioletorangered
vibrantvioletcyanorangeyellowred
earthtealgreenorangeyellowred

Color Reference

ColorStroke HexUse For
blue#1971c2Primary components
green#2f9e44Success, databases
red#e03131Errors, end states
yellow#f08c00Warnings, decisions
orange#e8590cHighlights
violet#6741d9Services
cyan#0c8599Network
teal#099268Secondary
gray#868e96Users, actors
black#1e1e1eText, arrows

Complete Examples

Example 1: Simple Flow

import sys, os
sys.path.insert(0, os.path.expanduser("~/.claude/plugins/duet/skills/excalidraw/scripts"))
from excalidraw_generator import Diagram

d = Diagram()
d.text_box(200, 30, "Data Processing Pipeline", font_size=24)

input_box = d.box(100, 100, "Input", color="gray")
process = d.box(300, 100, "Process", color="blue")
output = d.box(500, 100, "Output", color="green")

d.arrow_between(input_box, process, "raw data")
d.arrow_between(process, output, "results")

d.save("pipeline.excalidraw")

Example 2: Decision Flowchart

import sys, os
sys.path.insert(0, os.path.expanduser("~/.claude/plugins/duet/skills/excalidraw/scripts"))
from excalidraw_generator import Flowchart

fc = Flowchart(direction="vertical", spacing=100)

fc.start("User Request")
fc.process("auth", "Authenticate")
fc.decision("valid", "Valid Token?")

fc.position_at(300, 340)
fc.process("proc", "Process Request")
fc.process("resp", "Return Response")

fc.position_at(100, 340)
fc.process("err", "Return 401")

fc.connect("__start__", "auth")
fc.connect("auth", "valid")
fc.connect("valid", "proc", "Yes")
fc.connect("valid", "err", "No")
fc.connect("proc", "resp")

fc.save("auth_flow.excalidraw")

Example 3: Microservices Architecture

import sys, os
sys.path.insert(0, os.path.expanduser("~/.claude/plugins/duet/skills/excalidraw/scripts"))
from excalidraw_generator import ArchitectureDiagram

arch = ArchitectureDiagram()

arch.user("client", "Client", x=400, y=50)
arch.service("gateway", "API Gateway", x=350, y=180, color="violet")

arch.service("auth", "Auth Service", x=100, y=350, color="blue")
arch.service("users", "User Service", x=300, y=350, color="blue")
arch.service("orders", "Order Service", x=500, y=350, color="blue")
arch.service("notify", "Notification", x=700, y=350, color="cyan")

arch.database("authdb", "Auth DB", x=100, y=500, color="green")
arch.database("userdb", "User DB", x=300, y=500, color="green")
arch.database("orderdb", "Order DB", x=500, y=500, color="green")

arch.component("queue", "Message Queue", x=600, y=450, color="orange")

arch.connect("client", "gateway", "HTTPS")
arch.connect("gateway", "auth", "gRPC")
arch.connect("gateway", "users", "gRPC")
arch.connect("gateway", "orders", "gRPC")
arch.connect("auth", "authdb", "SQL")
arch.connect("users", "userdb", "SQL")
arch.connect("orders", "orderdb", "SQL")
arch.connect("orders", "queue", "publish")
arch.connect("queue", "notify", "subscribe")

arch.save("microservices.excalidraw")

Viewing the Output

After generating a .excalidraw file:

  1. Excalidraw.com: Go to https://excalidraw.com and drag the file onto the canvas
  2. VS Code: Install the "Excalidraw" extension, then open the file
  3. CLI: Use open filename.excalidraw on macOS

Exporting to PNG

# First time setup
cd ~/.claude/plugins/duet/skills/excalidraw/scripts
npm install
npx playwright install chromium

# Export
node ~/.claude/plugins/duet/skills/excalidraw/scripts/export_playwright.js diagram.excalidraw output.png

Tips

  1. Positioning: Use a grid system. Start shapes at multiples of 50 or 100 for alignment
  2. Spacing: Leave 50-100px between elements for clean arrows
  3. Labels: Keep labels short (2-3 words). Use text boxes for longer descriptions
  4. Colors: Use consistent colors for similar components (all databases green, all services blue)
  5. Layout patterns:
    • Horizontal flow: x increases, y constant
    • Vertical flow: y increases, x constant
    • Grid: Combine both for complex diagrams
  6. After generation: Open in Excalidraw to fine-tune positions and add hand-drawn elements

See Also

  • /mermaid - For inline documentation diagrams that render natively on GitHub
  • /design - Design thinking applies to diagram composition and layout
  • /prose - Diagram labels benefit from the same concision rules as prose
Repository
tslateman/duet
Last updated
Created

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.