agentleFS
Plexito-dePlexito-de/honed-skills/skills/excalidraw/SKILL.md

excalidraw

Skill3 starsChanged 21 days agoApache-2.0View on GitHub

Use when producing a diagram as a .excalidraw file, or when asked for an architecture, flow, sequence, tree, mind map, org chart, timeline, network, ERD, swimlane, kanban or wireframe drawing that should look hand-drawn. Writes a short spec and expands it deterministically with measured font metrics, real layout and correct arrow bindings, then lints and renders it. Triggers on excalidraw, diagram, sketch, whiteboard, architecture diagram, flowchart, sequence diagram, mind map, hand-drawn, and on a diagram that opens with overlapping boxes or detached arrows.

69 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use heartmula, webhook-subscriptions and 10 more.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • math-incSkill

    heartmula

    Set up and run HeartMuLa, the open-source music generation model family (Suno-like). Generates full songs from lyrics + tags with multilingual support.

    Also in 9 projects that use this

  • Tommy-ywSkill

    webhook-subscriptions

    Create and manage webhook subscriptions for event-driven agent activation, or for direct push notifications (zero LLM cost). Use when the user wants external services to trigger agent runs OR push notifications to chats.

    Also in 5 projects that use this

  • john-data-chenSkill

    touchdesigner-mcp

    Control a running TouchDesigner instance via twozero MCP — create operators, set parameters, wire connections, execute Python, build real-time visuals. 36 native tools.

    Also in 7 projects that use this

  • math-incSkill

    minecraft-modpack-server

    Set up a modded Minecraft server from a CurseForge/Modrinth server pack zip. Covers NeoForge/Forge install, Java version, JVM tuning, firewall, LAN config, backups, and launch scripts.

    Also in 5 projects that use this

  • AlekseiULSkill

    TubeScribe

    YouTube video summarizer with speaker detection, formatted documents, and audio output. Works out of the box with macOS built-in TTS. Optional recommended tools (pandoc, ffmpeg, mlx-audio) enhance quality. Requires internet for YouTube access. No paid APIs or subscriptions. Use when user sends a YouTube URL or asks to summarize/transcribe a YouTube video.

    Also in 3 projects that use this

  • mae616Skill

    p5js

    p5.js(クリエイティブコーディング)のProcessing由来「コードでスケッチ」思想を軸に、setup/drawループ・座標系・インタラクション・パフォーマンス最適化を整理する。p5.js、Processing、クリエイティブコーディング、ジェネラティブアート、インタラクティブ表現、ビジュアルプログラミング、Canvas描画、パーティクル、フラクタル、ノイズ表現に関する相談で必ず使うこと。ユーザーが「コードでアート」「動くビジュアル」「インタラクティブな描画」と言った場合にも適用する。

    Also in 10 projects that use this

  • +5 more from math-inc

The file

We split this skill into the rules it gives an agent

4 rules. 0 are common, 1 is distinctive, and 3 are very specific to this project.

  • 0

    Common

    Most of the 64 edited versions we read keep these.

  • 1

    Distinctive

    This author's own choices. Worth a look.

  • 3

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Every published generator estimates width as len(text) * fontSize * 0.55 or * 0.6.

    line 38
  2. Distinctive:

    Gutters about 1.5x node height, minimum 40 px: Not the 200 to 300 px the popular skills prescribe, which is two to three times what real …

    line 123
  3. Very specific:

    endArrowhead defaults to "arrow" when omitted: Write null for a headless arrow.

    line 141
  4. Very specific:

    No prior implementation manages this.

    line 153

“Common” is measured: kept by at least 6 in 10 of the 64 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Excalidraw diagrams

The contract

Write a spec. Never write .excalidraw JSON by hand. A spec of 20 to 60 lines becomes hundreds of lines of correct JSON, and the three things a model is worst at are done by code instead:

ProblemWhy hand-writing failsWhat does it here
CoordinatesPlacing boxes is arithmetic, and the failure is visible: overlapping nodes, arrows through shapesA layout pass with deterministic tie-breaks
Text widthExcalidraw never re-measures text on open. restoreElements runs without refreshDimensions, so a wrong stored width silently clips glyphsAdvance widths measured in a browser against the real fonts
InvariantsMost violations open fine and look wrong, so nothing tells youA linter that runs before you look
S=~/.claude/skills/excalidraw/scripts
python3 $S/exdraw.py mydiagram.yaml -o mydiagram.excalidraw   # build
python3 $S/exlint.py mydiagram.excalidraw                     # MANDATORY, before looking
python3 $S/exfidelity.py source.mmd mydiagram.excalidraw      # only when redrawn from Mermaid
python3 $S/exsvg.py mydiagram.excalidraw -o preview.svg       # deterministic preview
google-chrome --headless --disable-gpu --screenshot=preview.png \
  --window-size=1600,1200 preview.svg                         # then READ the png

The render step is not optional and it is not a formality. A diagram is a user-facing surface, so the standing rule applies: never report it done without looking at it yourself.

Why the multiplier everyone uses is wrong

Every published generator estimates width as len(text) * fontSize * 0.55 or * 0.6. Measured against the real Excalifont at fontSize: 20:

TextTrue advanceAt x0.6At x0.55
Authentication Service219.76264.0 (+20%)242.0 (+10%)
MMMMM76.6060.0 (-22%)55.0 (-28%)
iiiii24.4060.0 (+146%)55.0 (+125%)

Wrong in both directions, because the per-glyph spread is 3.5x (l is 0.225 em, W is 0.786 em). That is why other tools either clip text or leave dead space.

assets/font-metrics.json holds per-glyph advance widths and kern pairs for all eight bundled families, measured with canvas measureText in Chrome against Excalidraw's own woff2 subsets. The browser is the ground truth, because the browser is what produced the widths Excalidraw stored. Verified against four independently published values, reproduced to two decimals: Hello world 101.90, Hello 43.20, Database 93.92, Start 54.92.

Error bound, stated because it matters for autoResize: false: within about 0.1% on prose, up to 3-7% over on a short kern-heavy word. Over is the safe direction, and container widths round up.

The spec

type: flow            # flow tree grid swimlane mindmap sequence timeline network org c4 kanban journey gantt
title: Request path
style: architecture   # architecture (roughness 1) | formal (roughness 0) | sticker (roughness 2)
fontSize: md          # sm 16 | md 20 | lg 28 | xl 36

nodes:
  - id: api
    label: API
    color: blue       # ink red green blue yellow violet grey
    kind: decision    # decision -> diamond, start/end/state -> ellipse, default rectangle
    emphasis: true
    boundary: true    # dashed outline: a scope, not a thing

edges:
  - from: lb
    to: api
    label: HTTPS
    style: dashed
    arrow: false      # headless line

notes:
  - at: api
    text: rate-limited, 100 rps

type: c4 adds C4 notation (c4model.com) and one more pair of fields is open to every type, provenance:

type: c4
title: Online shop, containers       # required whenever `source` is set
source: {repo: example.org/acme/shop, commit: 3f9a2c1}   # 7 to 64 hex characters

nodes:
  - id: api
    label: Orders API
    c4: container                    # person | system | container | component
    tech: Python, FastAPI            # becomes "[Container: Python, FastAPI]" in the same label
    at: api/orders/routes.py:12-140  # repo-relative path:lines, or `external`
  - id: db
    label: Orders database
    c4: container
    tech: Postgres
    shape: database                  # drawn as an ellipse: Excalidraw has no cylinder
  - id: pay
    label: Payment provider
    c4: system
    external: true                   # grey, typed External Software System

edges:
  - from: api
    to: db
    label: Reads and writes orders   # what flows, never just "uses"
    tech: SQL                        # becomes a "[SQL]" line in the arrow label

The type and the technology go into the ONE bound label, at the same size, so they move with the shape. c4, tech, external and shape outside type: c4 are a spec error, and so are an absolute path or .. in at, and a source with no title. Everything lands in customData (c4, tech, provenance, and role: title on the title), which Excalidraw preserves verbatim and the linter reads. A full example is examples/c4.yaml.

Everything else is derived. style is one decision that sets roughness, fill and font together, which is what keeps a diagram internally consistent.

Design rules, measured rather than asserted

From 20 real published .excalidraw scenes (about 1,700 elements) and all 232 community libraries (about 55,000 elements). Most of this contradicts the popular prose skills, and the measurements are in references/design-rules.md.

  1. One roughness value per diagram. 20 of 20 real scenes use exactly one. Every drawing that mixes them is in the ugly pile. 1 for architecture, 0 when the shapes carry formal meaning, 2 only for sticker cards.
  2. One fill style, one stroke width, one stroke colour. 17 of 20, 16 of 20, and 10 of 20 use exactly one respectively; median distinct stroke colours is 1.5. The two ugliest scenes had 14 to 15.
  3. Colour budget: 1 stroke + 3 to 5 fills, each with a stated job. Pair a shade-200 fill with the same hue's shade-800 stroke, or with #1e1e1e. Across 55,000 elements, palette-to-palette pairs are the same hue family in 1,048 of 1,069 cases.
  4. 6 to 12 primary nodes. Split above about 15.
  5. Node aspect 2.5 to 3.5 : 1, height about 3x font size, never below 20x50 px - below that adjustRoughness() silently halves the roughness and the node renders smoother than its neighbours.
  6. Gutters about 1.5x node height, minimum 40 px. Not the 200 to 300 px the popular skills prescribe, which is two to three times what real diagrams use.
  7. Nesting inset 20 px, and stop at three levels. Encode depth by desaturating one hue, never by stacking saturated fills.
  8. Labels of 1 to 3 words, bound to the container. 106 of 218 real labels are one word. The cleanest scenes bind 90 to 100% of labels; the messy ones bind none. Binding is what centres the label and grows the box.
  9. Arrows: 2 points, one head, bound at both ends. 82% of real arrows are straight 2-point; (null, "arrow") in 13 of 13 scenes. Double-headed arrows are 2% of the corpus, so draw bidirectionality as two parallel arrows. The builder gives every arrow between the same two nodes its own lane 20 px apart (a reverse pair sits 10 px each way), so no two share one line.
  10. Bind an arrow label to the arrow. That is how it gets an opaque backing: the renderer punches a hole of label + 10 px through the arrow behind a bound label. Free text laid over an arrow does not get it.
  11. Two text sizes, three at most, on the 16/20/28/36 ladder. A fractional font size is the most reliable machine-detectable tell of an unmade diagram, because it comes from dragging a text box.
  12. Two typefaces, two jobs. Hand-drawn for prose, the code font for anything literal: IPs, paths, commands, table and topic names.
  13. Dashes are scarce. 0.9% of strokes in the corpus. Solid = fixed, owned, synchronous. Dashed = elastic, logical, async, a boundary. Dotted = a note or a lane separator.
  14. Rotate nothing, opacity 100, canvas white. Zero rotated elements in 14 of 20 scenes; white canvas in 20 of 20. The hand-drawn feel comes from roughness, not tilt.

Format facts that decide correctness

Full reference in references/element-schema.md. The ones that bite:

  • Bindings are reciprocal and restore repairs neither. An arrow follows a moved shape via the SHAPE's boundElements; its endpoint comes from the ARROW's binding. Different mechanisms, both required. One alone renders correctly and detaches on first drag. This is the most-reported defect in agent-written Excalidraw files.
  • The binding schema changed. Current is {elementId, fixedPoint: [rx, ry], mode: "orbit"}. focus and gap are gone from the type, and a legacy binding is migrated by discarding your numbers and recomputing from where the points already are - so endpoint geometry is the real contract.
  • No fixedPoint coordinate may be exactly 0.5. It is snapped off centre to stop the arrow heading flipping. This builder writes 0.5002, because the upstream 0.5001 minus 0.5 is just under 0.0001 in IEEE 754 double, which is still inside the 1e-4 guard it was chosen to clear.
  • points[0] must be [0, 0] on any linear element, or the element visibly jumps on load.
  • endArrowhead defaults to "arrow" when omitted. Write null for a headless arrow.
  • Always write lineHeight. Omit it while supplying a height and Excalidraw back-derives a bogus value that permanently distorts line spacing.
  • roundness must match the element class or it silently yields radius 0: {type:3} rectangle/image/iframe/embeddable, {type:2} line/arrow/diamond, null ellipse/text.
  • Omit index. A hand-written partially-invalid set throws invalid order key, the throw is swallowed, and the canvas renders blank.
  • Never emit exactly 0 for a coordinate. Three upstream falsy-zero bugs are open on it. This builder starts the canvas at 100.
  • opacity is 0 to 100, not 0 to 1. fillStyle defaults to solid, not hachure. angle is radians.
  • Container geometry is not the naive formula. Max text width is w - 10 for a rectangle but round(w/2 * sqrt2) - 10 for an ellipse and round(w/2) - 10 for a diamond. The x = container.x + (container.width - textWidth)/2 that every published skill uses is wrong for ellipses and diamonds by the inscribed-box inset.

Determinism

The same spec produces a byte-identical file. Ids, seed and versionNonce come from a BLAKE2b of the spec (as canonical JSON, with source and every node and edge at left out, so bumping a commit does not re-roll the seeds) plus each element's path within it; created/updated default to a fixed epoch; layout iteration counts are fixed and every tie-break sorts on a stable key.

No prior implementation manages this. The most-installed skill in the field seeds from Python's hash(), which is salted per process by PYTHONHASHSEED, so it looks reproducible and is not; the closest CLI uses Math.random() and Date.now().

Verify it yourself:

python3 $S/exdraw.py spec.yaml -o a.excalidraw
python3 $S/exdraw.py spec.yaml -o b.excalidraw
cmp a.excalidraw b.excalidraw && echo "byte-identical"

--stamp-now opts out, and gives up reproducibility in exchange for a real timestamp.

Verification

  1. Lint. exlint.py checks referential integrity in both directions, points[0], roundness class, 0x0 elements, NaN, exact-zero coordinates, text overflow, node overlap, lines through boxes, and the style rules above. A blind A/B on seeded defects found geometric checks caught 12 of 12 with no false positives, versus 11 of 12 with one false positive by eyeballing renders. Lint first, always.
    • Strokes, solved exactly. arrow-crossing (warn): two arrows or lines cross. Crossings are the one layout aesthetic with measured weight on reading errors and time, see design-rules.md section 6. Not a crossing: a touch (one stroke ends on the other, a bus spur), a fan-out or fan-in (within 8 px of an end that both strokes bind to the same shape). arrow-overlap (warn): two strokes on one line, which is worse than a crossing. Exempt only when the two share exactly one bound end (a trunk that splits, or spurs merging into one target); two strokes sharing both ends are a duplicate drawn on itself. arrow-through-label (warn): a stroke across another arrow's label. A multi-point arrow, rounded or not, is tested as its polyline. The summary line ends with crossings: N, so two layouts compare by number.
    • C4, as notes tagged "c4model checklist", active when any element carries customData.c4: c4-untyped (a boundary is exempt), c4-no-tech on a container or component, c4-vague-edge (no label, or only uses, calls, connects to, talks to, depends on or interacts with), c4-no-title, c4-too-many at 20 containers. Not checked, because it needs a human: whether the scope is clear, whether a label reads in its arrow's direction, and mixed zoom levels in one diagram.
    • Provenance, active when the title carries a source: provenance-missing (warn) on a shape or arrow with no at, provenance-commit (error) on a commit that is not 7 to 64 hex characters. Whether each line range still says what the edge claims is not checked: that needs the repo and a reader.
  2. Fidelity, when the diagram is a redraw of a Mermaid flowchart. exfidelity.py source.mmd out.excalidraw [--json] lists every node, edge and subgraph label kept, missing or ambiguous, every source edge with or without an arrow between the matching shapes, and every arrow with no source edge. A node must equal one shape's bound label exactly (the [type: tech] line aside), one node to one shape; an edge label is read from its own arrow, so swapped labels fail; a match with two candidates fails as ambiguous. Exit 0 all kept, 1 something lost or ambiguous, 2 the source or the scene cannot be read (any crash is 2 as well, in all three scripts). It reads flowchart and graph only and fails closed: any other diagram type, or one line it does not understand, is exit 2, never a silent pass.
  3. Look at it. Render and read the PNG. The SVG preview is exact on geometry and an approximation of stroke texture.
  4. Open it in Excalidraw once and drag a bound shape. Confirm the arrow follows, the label stays centred, and on a C4 or sourced diagram that customData survives a save. No linter can check that, and it is the failure this skill exists to prevent.

Gotchas

SymptomCause / fix
Arrows detach the first time a box is draggedOnly one side of the binding was written. Both are required, and nothing repairs them.
Text is clipped, or a box has dead spaceStored width is wrong and never re-measured. Use the metrics, not a multiplier.
A label sits off-centre in an ellipse or diamondThe naive centring formula. Use the inscribed-box inset.
The canvas opens blankA hand-written index that fails to parse. Omit index.
One box looks smoother than the restIt is below 20x50 px, so adjustRoughness() halved it.
An arrow renders right, then snaps on first interactionpoints disagree with what the binding implies.
A shape is invisiblewidth and height both 0, or backgroundColor left transparent while a fill was intended.
An element vanished with no errorUnknown type string. restoreElement has no default case and drops it silently.
The diagram looks synthetic despite roughness 1Every element got seed: 1. Seeds must differ.
A PNG export clips the right edge of textexportToSvg computes the viewBox from stored dimensions while text renders at its natural width. Correct metrics prevent it.
An arrow disappearedIt spanned more than 75,000 px, so Excalidraw deleted it and substituted a stub.

Prior art (checked 2026-09)

The field is crowded at the prose layer and empty at the engineering layer. skills.sh lists 100 excalidraw skills; GitHub code search returns 9,552 skill files mentioning it; there is no official Anthropic one. Nothing surveyed combines measured font metrics, a real layout pass, current-schema bindings, genuine hand-drawn output and a pre-render linter. The best have three, and nothing has the first two together.

  • github/awesome-copilot excalidraw-diagram-generator, 29,210 installs and the most-installed in existence, correctly mandates fontFamily: 5. Its arrow script writes no bindings at all, hardcodes "index": "a0" on every element, and seeds from hash().
  • coleam00/excalidraw-diagram-skill, 4,737 stars, has the best design doctrine in the field, including the Isomorphism Test: "If you removed all text, would the structure alone communicate the concept?" It also refuses to generate ("Don't write a Python generator script") and pays for it with a documented ~32,000-token output ceiling. Its two most-copied numbers are contradicted by measurement: the under-30%-containers rule is backwards, and its 200 to 300 px gutters are two to three times reality.
  • excalidraw/mermaid-to-excalidraw is the strongest prior art and its limits are structural: it has no layout engine of its own, it scrapes geometry from a rendered DOM so a real browser is mandatory, and every unsupported diagram type becomes a raster image.
  • zsviczian/obsidian-excalidraw-plugin's ExcalidrawAutomate is the richest programmatic surface that exists and the source of the 0.5001 fixed-point trick.

What this adds: the metrics, the determinism guarantee, the linter, and native handling for the types the whole field leaves raster-only.

Status

Built 2026-09-09. Font metrics verified against four independently published values and reproduced to two decimals by a different method than the one that published them. Every lint rule is proven by falsification: the defect is seeded, the check must fire, the defect is removed, the check must stop. Determinism is asserted by building twice and comparing bytes.

Related

  • references/design-rules.md - the measurements, the palette pairing table, per-diagram-type recipes.
  • references/element-schema.md - the verified format reference, for the raw escape hatch.
  • This skill owns diagrams only. Visual doctrine for product surfaces (typography, colour, motion, growth mechanics) is a separate concern and is not covered here.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.