Reladraw skill
Write, render and edit diagrams in reladraw, a text diagram language where you say where things go, so the arrangement can be read back out of the source without looking at the picture.
by reladraw·Apache-2.0 license·★ 997 Stars on the repo·GitHub ↗
- Download the ZIP below (don't unzip it).
- In Claude.ai, open Settings › Capabilities.
- Under Skills, click Upload skill and pick the ZIP.
- Turn the skill on, then ask Claude to use it.
Checked ·commit bfb8cf7
Files of Reladraw
Show the full text237 lines
reladraw
A text language for diagrams. Source in, standalone SVG out, no runtime dependencies.
Every other diagram language hands the arrangement to a layout engine, so where things land is an output you cannot predict from what you wrote. reladraw inverts that: the file states the arrangement in the terms a person would say out loud — right of api, between web and worker, level with queue — and the tool works out only the distances. Nothing is ever chosen for you.
That matters here specifically. You cannot see the SVG you produced. With auto-layout that leaves you guessing; with reladraw the arrangement is in the sentences you just wrote, so re-reading your own source tells you where everything is.
Check it is installed
reladraw --help
If that fails, npx reladraw works without installing, and npm install -g reladraw installs the command.
The loop
- Write a
.reladrawfile. reladraw diagram.reladraw -o diagram.svg(-o -writes to stdout; the default output path is the input with.svgin place of its extension).- If it fails, the error names the statement and what is wrong with it. Fix and re-run.
- Re-read your own source to confirm the picture says what you meant.
Errors are the feedback you have. A file that renders is a file whose arrangement you fully stated, because anything left ambiguous is refused rather than guessed:
diagram.reladraw:3: "c" does not say where it sits vertically: "right of a" and "left of b" would put it in different places
Be honest about what this does not give you. Re-reading the source confirms intent — that the worker landed under the API, that all four machines hang off the hub. It does not confirm outcome: whether a long text overflowed, whether a line crosses four others, whether two independently-anchored clusters collided. A machine-readable diagnostics report is planned and is not built yet, so for a large or dense diagram, say plainly that you have stated the arrangement but not verified the render.
The one idea
A gap is a minimum distance, never an exact one. Everything ends up as close together as your statements allow.
So putting something between two things is what pushes them apart, by exactly what it needs:
node hub "Hub"
node side "Side" left of hub
node wedge "Wedged" right of side left of hub level with hub
Nothing says how far apart hub and side are. Delete wedge and they close back up. You never pick a number and no number goes stale when a text grows. There are no coordinates in this language, in any form.
Writing a file
A statement starts at the beginning of a line, and a long one continues onto following lines that are indented; a blank or unindented line ends it. How much indentation makes no difference, and there are no blocks — indentation never puts one thing inside another. // starts a comment and may trail a statement.
A statement is a positional head — the keyword, a name, then a text — followed by attributes and placements in any order. A token ending in a colon opens an attribute and nothing else does, so node n "text" gap: wide below worker and node n "text" below worker gap: wide are the same statement.
Nodes
node <name> ["<text>" [(<text properties>)]] [<placement> | <attribute>] ...
Leave the text out and the node takes its own name as its text (node parser draws a node reading "parser"). Write "" for a deliberately blank node. Inside text, / — a slash with a space on each side — is a line break; a slash without spaces is an ordinary character, so TCP/IP and URLs survive.
Containment is a dotted name and the parent must be declared first. Children stack vertically in written order unless a child carries its own placement.
node server "Server"
node server.api "API"
node server.worker "Worker"
A container with "" and fill: none border: none draws nothing and takes no room of its own, which is how you make a group that can be placed against as one shape.
Node attributes: style, fill and border (each a color), shape, icon, badge, gap, overlap: allow, url (a quoted destination), deck (a stack of copies behind the box, one quoted text each: deck: "Drive 2" "Drive 3"), and contents: on a container.
contents: (widths: match, align: center) says how a container's children sit when its title is wider than they are. widths: takes natural, match (all as wide as the widest) or fill (all as wide as the band); align: takes left, center or right.
The text and its brackets
Everything a text says about itself goes in brackets after it, never among the node's attributes: color, size (small | normal | large), wrap (fold every n characters), align (left | center | right, how the lines range against each other) and at (where the block sits, named from the nine positions — top-left, bottom-center, center and the rest).
node docker "Docker" (at: bottom-center, align: center) below deploy
node aside "a longer remark that folds" (size: small, wrap: 30) shape: none
edge a -> b "rclone" (color: theme-muted)
An edge's text takes the same keys less at. A style has no text of its own, so it hangs the bracket off a key: style aside text: (size: small, color: theme-muted).
A stretch of a text can borrow a style's text color, which is how a node carries a quieter qualifier:
style dim text: (color: theme-muted)
node grinder "Grinder / [dim]medium-fine[/dim]"
The mark names a style and never a color; the closer repeats the name; \[ is a literal bracket. There is no subtext attribute — it was removed, and an older file carrying it gets an error naming the mark to write instead.
The body
Every node has one body and two keys can name it. shape: rectangle | document | circle | none is the outline it is drawn with — rectangle is the default, circle is sized to its text and always round, and none is text with no box at all, which is what an annotation is. icon: <name> draws the node as a picture, with no box: disk, desktop, laptop, package, cubes, cube, database. Writing both is an error.
badge: <name> is different again: it puts one of those pictures beside a node's text, and the node keeps its own body and grows to hold both.
A picture outside the set is declared with icon <name>, followed by SVG pasted between """ marks or a quoted .svg file path, and then used by name like a built-in. Only the command-line tool reads files. Prefer the built-ins; declare one only when the user supplies the SVG or asks for a picture the set lacks.
node dump "nightly dump" shape: document
node aside "a remark" (wrap: 30) shape: none
node unit icon: cube
node drive "External HD" badge: disk
A node drawn as a picture and given no text shows none — everywhere else a node with no text takes its name, but a picture usually is the statement.
A color attribute names the part it colors: fill is the area, border the outline, text the text, and line the drawn line of an edge. A word is refused on a kind that has no such part, so border: on a shape: none node is an error — with no body there is no outline, and text: is its only color.
A style contributes a part only to the kinds that have it, so a style shared between nodes and edges writes one key for each — style backup border: #d2904e line: #d2904e colors the nodes' borders and the edges' lines from one name.
There is no stroke attribute. It was removed because it named no part; if you have seen it in an older file, it is border on a node, text on one with no body or a picture body, and line on an edge.
Every attribute is checked by name, so do not invent one. A word the tool does not know is an error, and so is a real word on a kind that has no use for it — fill: on a node with no body, gap: or overlap: on an edge, contents: on a node that can have no children. The error says either what the kind takes or where the word does belong. A key handed over by a style is exempt, which is what lets one style dress both nodes and edges.
Placement
above X below X left of X right of X
above-left of X above-right of X below-left of X below-right of X
level with X top level with X bottom level with X
left level with X right level with X
of is optional after a direction. A node carries as many placements as it needs, and any of them may name several targets joined by and — right of borg and bare places against the box that just bounds them.
Three rules that decide most of what you write:
- A lone directional placement binds both axes.
right of dockeralso centers the node vertically on Docker, because walking right from something keeps you on its center line. That half drops away as soon as another placement claims that axis. - Two placements on one axis with nothing on the other is an error, because the tool would have to choose which row the node shares. Add
level with Xor abelow. - Exactly one node in the file may be left unplaced. Everything else is positioned, directly or transitively, against it.
Gaps are named — none, tight, normal (default), wide — and belong to the placement, in brackets:
node stack "Stack" below wedge right of wall (gap: tight) gap: wide
gap: as a plain attribute is the node's default and reaches every relationship the node is in, including placements written against it. (gap: none) puts two sides flat against each other.
Edges
edge <from> -> <to> ["<text>"] [between <a> and <b> [vertically|horizontally]] [above|below|left of|right of <node> ...] [attributes]
<- and <-> also work, and -- is a plain line; a <- b draws the same as b -> a and lets you write the subject first. Endpoints may be nested (server.api).
Each end may carry a mark, written in the arrow: arrow (glyph >/<), oarrow (|>/<|), dot (*), odot (o), diamond, odiamond, bar (|), none. So a <|-- b, a *--o b, a diamond--> b. A mark touches the dashes or is bracketed ([dot] -- b). from-mark: and to-mark: say the same as attributes and can live in a style; they fill only ends the arrow leaves blank, and contradicting a mark written in the arrow is an error.
from: and to: name a side of the first and second node written, whichever way the arrow points — top, bottom, left, right — and turn the line into a curve that actually leaves and arrives that way. Name them when it matters which side a line meets a box on.
A line never passes through a box: every edge takes the shortest way between its ends that goes through none, going round whatever is in its way. Of two ways round that are equally short, it goes over the top (round the right for a column). An end with no side named leaves by whichever side makes the way shortest.
between a and b says the line travels down the gap between two named nodes. below c (or above, left of, right of) says which side of a node the line passes, only where it goes by that node; write one per node, and below c and d covers the stretch between them too. Use these when you want a line to go a particular way rather than the shortest.
An edge with text widens the corridor between its own two ends by what the text needs, so texts are safe to add.
How the line is drawn goes in its bracket: line: (path: square, corners: rounded, pattern: dashed, thickness: thick, crossing: arc). path: is curved (the default), square (right angles) or straight; it changes how the line joins its ends, never which side of anything it passes. line: red alone is the color. To draw a whole diagram with right angles, write default edge line: (path: square, corners: rounded).
Annotations
node <name> "<text>" (wrap: 30) shape: none <placement> ...
There is no note statement — an annotation is a node with no body, anchored to a node so it travels with it. Always give one a (wrap: n) — without one a sentence is drawn as one very long line across whatever is beside it.
Against a part of a box
inside <node> <part>
outside <node> <part>
on <node> <part>
A placement may target a part of a node: its text, a side (top, bottom, left, right), or one of nine points — top-left, top-center, top-right, left-center, center, right-center, bottom-left, bottom-center, bottom-right. A bare side is the whole edge; the -center point is its midpoint.
inside tucks the node within the box against that part, inset tight unless (gap: …) says otherwise; (gap: none) puts it hard against the edge. outside puts it wholly beyond that part. on centers it on the part, so a node on a corner straddles it.
node bob "Bob the builder"
node bob_link "bob.example.com" inside bob bottom-center
node count "3" on bob top-right
This is not containment: a dotted name puts something in a box and widens it, while a node placed against a part is stamped on it and changes nothing. Use it for a mark, a count, or a link line at the bottom of a box.
Styles
style store fill: theme-primary-subtle border: theme-primary badge: database
node records "Records" style: store
Prefer theme colors, which follow the theme so the diagram reads in light and dark alike: theme-primary and theme-secondary are the theme's two accents, -subtle is an accent softened toward the page (the one to fill a box with), and theme-muted is quieter text. theme-page, theme-text, theme-fill, theme-border and theme-line are the theme's own colors for each part. A hex or CSS color also works, or none, but stays fixed in every theme — a dark fill chosen on a dark page is unreadable on a light one.
A complete small file
// A request path, left to right.
style store fill: theme-primary-subtle border: theme-primary badge: database
node browser "Browser"
node api "API server" right of browser
node db "Postgres" right of api style: store
node worker "Worker" below api
edge browser -> api "HTTP" from: right to: left
edge api -> db "SQL" from: right to: left
edge worker -> db "writes" from: right to: bottom
node aside "The worker shares the database / but takes no HTTP traffic." (wrap: 30) shape: none below worker (gap: tight)
What will bite you
- Reaching for
note,boxorlink. They are not statements. A note isnode … shape: none; the keywords arenodeandedge. Each gets an error naming the replacement. - Nodes that nothing orders. Every pair of nodes must clear the other, and where the file says nothing about which side of what, it is an error naming the pair:
"b" and "c" overlap, and nothing says which side of the other either one sits on. Hanging two children off the same side of the same target is the usual cause. Place one against the other. - An annotation with no wrap.
- Reaching for a coordinate, an offset, or a waypoint. None exist. If a line goes somewhere wrong, say more about it with
between,below <node>andfrom:/to:; if a node is in the wrong place, add a placement. #is not a comment. It opens a hex color. Comments are//.- Guessing at syntax from another language. There are no braces, no semicolons, no
-->, no subgraphs. If you want something not written here, checkreference/syntax.mdbefore inventing it.
Reference
reference/syntax.md is the full syntax reference — every construct, the shape, icon and position sets, how edges sharing a side or a channel are ordered, what the language deliberately refuses and why, and the known defects. Read it when you need a construct this page does not cover, or when an error message points at behavior you did not expect.
| 1 | |
| 2 | name reladraw |
| 3 | description Write, render and edit diagrams in reladraw, a text diagram language where you say where things go, so the arrangement can be read back out of the source without looking at the picture. Use when asked to draw, diagram, sketch or visualize an architecture, a system, a data flow, a pipeline, a deployment, a directory layout, or the shape of a change or pull request; when reading or editing a .reladraw file; or when the user says "reladraw", "diagram this", "draw the architecture", "show me how these pieces fit". Use it in place of Mermaid, Graphviz, D2 or hand-drawn ASCII boxes whenever a node-and-line diagram is wanted and reladraw is installed. NOT for charts of data — bar, line, pie, scatter — and NOT for pictures that are not nodes and lines. |
| 4 | |
| 5 | |
| 6 | # reladraw |
| 7 | |
| 8 | A text language for diagrams. Source in, standalone SVG out, no runtime dependencies. |
| 9 | |
| 10 | Every other diagram language hands the arrangement to a layout engine, so where things land is an output you cannot predict from what you wrote. reladraw inverts that: the file states the arrangement in the terms a person would say out loud — `right of api`, `between web and worker`, `level with queue` — and the tool works out only the distances. Nothing is ever chosen for you. |
| 11 | |
| 12 | That matters here specifically. You cannot see the SVG you produced. With auto-layout that leaves you guessing; with reladraw the arrangement is in the sentences you just wrote, so re-reading your own source tells you where everything is. |
| 13 | |
| 14 | ## Check it is installed |
| 15 | |
| 16 | |
| 17 | reladraw --help |
| 18 | |
| 19 | |
| 20 | If that fails, `npx reladraw` works without installing, and `npm install -g reladraw` installs the command. |
| 21 | |
| 22 | ## The loop |
| 23 | |
| 24 | Write a `.reladraw` file. |
| 25 | `reladraw diagram.reladraw -o diagram.svg` (`-o -` writes to stdout; the default output path is the input with `.svg` in place of its extension). |
| 26 | If it fails, the error names the statement and what is wrong with it. Fix and re-run. |
| 27 | Re-read your own source to confirm the picture says what you meant. |
| 28 | |
| 29 | Errors are the feedback you have. A file that renders is a file whose arrangement you fully stated, because anything left ambiguous is refused rather than guessed: |
| 30 | |
| 31 | |
| 32 | diagram.reladraw:3: "c" does not say where it sits vertically: "right of a" and "left of b" would put it in different places |
| 33 | |
| 34 | |
| 35 | **Be honest about what this does not give you.** Re-reading the source confirms *intent* — that the worker landed under the API, that all four machines hang off the hub. It does not confirm *outcome*: whether a long text overflowed, whether a line crosses four others, whether two independently-anchored clusters collided. A machine-readable diagnostics report is planned and is not built yet, so for a large or dense diagram, say plainly that you have stated the arrangement but not verified the render. |
| 36 | |
| 37 | ## The one idea |
| 38 | |
| 39 | **A gap is a minimum distance, never an exact one.** Everything ends up as close together as your statements allow. |
| 40 | |
| 41 | So putting something between two things is what pushes them apart, by exactly what it needs: |
| 42 | |
| 43 | |
| 44 | node hub "Hub" |
| 45 | node side "Side" left of hub |
| 46 | node wedge "Wedged" right of side left of hub level with hub |
| 47 | |
| 48 | |
| 49 | Nothing says how far apart `hub` and `side` are. Delete `wedge` and they close back up. You never pick a number and no number goes stale when a text grows. There are no coordinates in this language, in any form. |
| 50 | |
| 51 | ## Writing a file |
| 52 | |
| 53 | A statement starts at the beginning of a line, and a long one continues onto following lines that are indented; a blank or unindented line ends it. How much indentation makes no difference, and there are no blocks — indentation never puts one thing inside another. `//` starts a comment and may trail a statement. |
| 54 | |
| 55 | A statement is a positional head — the keyword, a name, then a text — followed by attributes and placements **in any order**. A token ending in a colon opens an attribute and nothing else does, so `node n "text" gap: wide below worker` and `node n "text" below worker gap: wide` are the same statement. |
| 56 | |
| 57 | ### Nodes |
| 58 | |
| 59 | |
| 60 | node <name> ["<text>" [(<text properties>)]] [<placement> | <attribute>] ... |
| 61 | |
| 62 | |
| 63 | Leave the text out and the node takes its own name as its text (`node parser` draws a node reading "parser"). Write `""` for a deliberately blank node. Inside text, ` / ` — a slash with a space on each side — is a line break; a slash without spaces is an ordinary character, so `TCP/IP` and URLs survive. |
| 64 | |
| 65 | Containment is a dotted name and the parent must be declared first. Children stack vertically in written order unless a child carries its own placement. |
| 66 | |
| 67 | |
| 68 | node server "Server" |
| 69 | node server.api "API" |
| 70 | node server.worker "Worker" |
| 71 | |
| 72 | |
| 73 | A container with `""` and `fill: none border: none` draws nothing and takes no room of its own, which is how you make a group that can be placed against as one shape. |
| 74 | |
| 75 | Node attributes: `style`, `fill` and `border` (each a color), `shape`, `icon`, `badge`, `gap`, `overlap: allow`, `url` (a quoted destination), `deck` (a stack of copies behind the box, one quoted text each: `deck: "Drive 2" "Drive 3"`), and `contents:` on a container. |
| 76 | |
| 77 | `contents: (widths: match, align: center)` says how a container's children sit when its title is wider than they are. `widths:` takes `natural`, `match` (all as wide as the widest) or `fill` (all as wide as the band); `align:` takes `left`, `center` or `right`. |
| 78 | |
| 79 | ### The text and its brackets |
| 80 | |
| 81 | Everything a text says about *itself* goes in brackets after it, never among the node's attributes: `color`, `size` (`small | normal | large`), `wrap` (fold every n characters), `align` (`left | center | right`, how the lines range against each other) and `at` (where the block sits, named from the nine positions — `top-left`, `bottom-center`, `center` and the rest). |
| 82 | |
| 83 | |
| 84 | node docker "Docker" (at: bottom-center, align: center) below deploy |
| 85 | node aside "a longer remark that folds" (size: small, wrap: 30) shape: none |
| 86 | edge a -> b "rclone" (color: theme-muted) |
| 87 | |
| 88 | |
| 89 | An edge's text takes the same keys less `at`. A **style** has no text of its own, so it hangs the bracket off a key: `style aside text: (size: small, color: theme-muted)`. |
| 90 | |
| 91 | A stretch of a text can borrow a style's text color, which is how a node carries a quieter qualifier: |
| 92 | |
| 93 | |
| 94 | style dim text: (color: theme-muted) |
| 95 | node grinder "Grinder / [dim]medium-fine[/dim]" |
| 96 | |
| 97 | |
| 98 | The mark names a style and never a color; the closer repeats the name; `\[` is a literal bracket. There is no `subtext` attribute — it was removed, and an older file carrying it gets an error naming the mark to write instead. |
| 99 | |
| 100 | ### The body |
| 101 | |
| 102 | Every node has one body and two keys can name it. `shape: rectangle | document | circle | none` is the outline it is drawn with — `rectangle` is the default, `circle` is sized to its text and always round, and `none` is text with no box at all, which is what an annotation is. `icon: <name>` draws the node **as** a picture, with no box: `disk`, `desktop`, `laptop`, `package`, `cubes`, `cube`, `database`. Writing both is an error. |
| 103 | |
| 104 | `badge: <name>` is different again: it puts one of those pictures *beside* a node's text, and the node keeps its own body and grows to hold both. |
| 105 | |
| 106 | A picture outside the set is declared with `icon <name>`, followed by SVG pasted between `"""` marks or a quoted `.svg` file path, and then used by name like a built-in. Only the command-line tool reads files. Prefer the built-ins; declare one only when the user supplies the SVG or asks for a picture the set lacks. |
| 107 | |
| 108 | |
| 109 | node dump "nightly dump" shape: document |
| 110 | node aside "a remark" (wrap: 30) shape: none |
| 111 | node unit icon: cube |
| 112 | node drive "External HD" badge: disk |
| 113 | |
| 114 | |
| 115 | A node drawn as a picture and given no text shows none — everywhere else a node with no text takes its name, but a picture usually is the statement. |
| 116 | |
| 117 | A color attribute names the part it colors: `fill` is the area, `border` the outline, `text` the text, and `line` the drawn line of an edge. A word is refused on a kind that has no such part, so `border:` on a `shape: none` node is an error — with no body there is no outline, and `text:` is its only color. |
| 118 | |
| 119 | A style contributes a part only to the kinds that have it, so a style shared between nodes and edges writes one key for each — `style backup border: #d2904e line: #d2904e` colors the nodes' borders and the edges' lines from one name. |
| 120 | |
| 121 | There is no `stroke` attribute. It was removed because it named no part; if you have seen it in an older file, it is `border` on a node, `text` on one with no body or a picture body, and `line` on an edge. |
| 122 | |
| 123 | **Every attribute is checked by name, so do not invent one.** A word the tool does not know is an error, and so is a real word on a kind that has no use for it — `fill:` on a node with no body, `gap:` or `overlap:` on an edge, `contents:` on a node that can have no children. The error says either what the kind takes or where the word does belong. A key handed over by a style is exempt, which is what lets one style dress both nodes and edges. |
| 124 | |
| 125 | ### Placement |
| 126 | |
| 127 | |
| 128 | above X below X left of X right of X |
| 129 | above-left of X above-right of X below-left of X below-right of X |
| 130 | level with X top level with X bottom level with X |
| 131 | left level with X right level with X |
| 132 | |
| 133 | |
| 134 | `of` is optional after a direction. A node carries as many placements as it needs, and any of them may name several targets joined by `and` — `right of borg and bare` places against the box that just bounds them. |
| 135 | |
| 136 | Three rules that decide most of what you write: |
| 137 | |
| 138 | **A lone directional placement binds both axes.** `right of docker` also centers the node vertically on Docker, because walking right from something keeps you on its center line. That half drops away as soon as another placement claims that axis. |
| 139 | **Two placements on one axis with nothing on the other is an error**, because the tool would have to choose which row the node shares. Add `level with X` or a `below`. |
| 140 | **Exactly one node in the file may be left unplaced.** Everything else is positioned, directly or transitively, against it. |
| 141 | |
| 142 | Gaps are named — `none`, `tight`, `normal` (default), `wide` — and belong to the placement, in brackets: |
| 143 | |
| 144 | |
| 145 | node stack "Stack" below wedge right of wall (gap: tight) gap: wide |
| 146 | |
| 147 | |
| 148 | `gap:` as a plain attribute is the node's default and reaches every relationship the node is in, including placements written *against* it. `(gap: none)` puts two sides flat against each other. |
| 149 | |
| 150 | ### Edges |
| 151 | |
| 152 | |
| 153 | edge <from> -> <to> ["<text>"] [between <a> and <b> [vertically|horizontally]] [above|below|left of|right of <node> ...] [attributes] |
| 154 | |
| 155 | |
| 156 | `<-` and `<->` also work, and `--` is a plain line; `a <- b` draws the same as `b -> a` and lets you write the subject first. Endpoints may be nested (`server.api`). |
| 157 | |
| 158 | Each end may carry a mark, written in the arrow: `arrow` (glyph `>`/`<`), `oarrow` (`|>`/`<|`), `dot` (`*`), `odot` (`o`), `diamond`, `odiamond`, `bar` (`|`), `none`. So `a <|-- b`, `a *--o b`, `a diamond--> b`. A mark touches the dashes or is bracketed (`[dot] -- b`). `from-mark:` and `to-mark:` say the same as attributes and can live in a style; they fill only ends the arrow leaves blank, and contradicting a mark written in the arrow is an error. |
| 159 | |
| 160 | `from:` and `to:` name a side of the first and second node written, whichever way the arrow points — `top`, `bottom`, `left`, `right` — and turn the line into a curve that actually leaves and arrives that way. Name them when it matters which side a line meets a box on. |
| 161 | |
| 162 | A line never passes through a box: every edge takes the shortest way between its ends that goes through none, going round whatever is in its way. Of two ways round that are equally short, it goes over the top (round the right for a column). An end with no side named leaves by whichever side makes the way shortest. |
| 163 | |
| 164 | `between a and b` says the line travels down the gap between two named nodes. `below c` (or `above`, `left of`, `right of`) says which side of a node the line passes, only where it goes by that node; write one per node, and `below c and d` covers the stretch between them too. Use these when you want a line to go a particular way rather than the shortest. |
| 165 | |
| 166 | An edge with text widens the corridor between its own two ends by what the text needs, so texts are safe to add. |
| 167 | |
| 168 | How the line is drawn goes in its bracket: `line: (path: square, corners: rounded, pattern: dashed, thickness: thick, crossing: arc)`. `path:` is `curved` (the default), `square` (right angles) or `straight`; it changes how the line joins its ends, never which side of anything it passes. `line: red` alone is the color. To draw a whole diagram with right angles, write `default edge line: (path: square, corners: rounded)`. |
| 169 | |
| 170 | ### Annotations |
| 171 | |
| 172 | |
| 173 | node <name> "<text>" (wrap: 30) shape: none <placement> ... |
| 174 | |
| 175 | |
| 176 | There is no `note` statement — an annotation is a node with no body, anchored to a node so it travels with it. **Always give one a `(wrap: n)`** — without one a sentence is drawn as one very long line across whatever is beside it. |
| 177 | |
| 178 | ### Against a part of a box |
| 179 | |
| 180 | |
| 181 | inside <node> <part> |
| 182 | outside <node> <part> |
| 183 | on <node> <part> |
| 184 | |
| 185 | |
| 186 | A placement may target a *part* of a node: its `text`, a side (`top`, `bottom`, `left`, `right`), or one of nine points — `top-left`, `top-center`, `top-right`, `left-center`, `center`, `right-center`, `bottom-left`, `bottom-center`, `bottom-right`. A bare side is the whole edge; the `-center` point is its midpoint. |
| 187 | |
| 188 | `inside` tucks the node within the box against that part, inset `tight` unless `(gap: …)` says otherwise; `(gap: none)` puts it hard against the edge. `outside` puts it wholly beyond that part. `on` centers it on the part, so a node on a corner straddles it. |
| 189 | |
| 190 | |
| 191 | node bob "Bob the builder" |
| 192 | node bob_link "bob.example.com" inside bob bottom-center |
| 193 | node count "3" on bob top-right |
| 194 | |
| 195 | |
| 196 | This is not containment: a dotted name puts something *in* a box and widens it, while a node placed against a part is stamped on it and changes nothing. Use it for a mark, a count, or a link line at the bottom of a box. |
| 197 | |
| 198 | ### Styles |
| 199 | |
| 200 | |
| 201 | style store fill: theme-primary-subtle border: theme-primary badge: database |
| 202 | node records "Records" style: store |
| 203 | |
| 204 | |
| 205 | Prefer theme colors, which follow the theme so the diagram reads in light and dark alike: `theme-primary` and `theme-secondary` are the theme's two accents, `-subtle` is an accent softened toward the page (the one to fill a box with), and `theme-muted` is quieter text. `theme-page`, `theme-text`, `theme-fill`, `theme-border` and `theme-line` are the theme's own colors for each part. A hex or CSS color also works, or `none`, but stays fixed in every theme — a dark fill chosen on a dark page is unreadable on a light one. |
| 206 | |
| 207 | ## A complete small file |
| 208 | |
| 209 | |
| 210 | // A request path, left to right. |
| 211 | style store fill: theme-primary-subtle border: theme-primary badge: database |
| 212 | |
| 213 | node browser "Browser" |
| 214 | node api "API server" right of browser |
| 215 | node db "Postgres" right of api style: store |
| 216 | node worker "Worker" below api |
| 217 | |
| 218 | edge browser -> api "HTTP" from: right to: left |
| 219 | edge api -> db "SQL" from: right to: left |
| 220 | edge worker -> db "writes" from: right to: bottom |
| 221 | |
| 222 | node aside "The worker shares the database / but takes no HTTP traffic." (wrap: 30) shape: none below worker (gap: tight) |
| 223 | |
| 224 | |
| 225 | ## What will bite you |
| 226 | |
| 227 | **Reaching for `note`, `box` or `link`.** They are not statements. A note is `node … shape: none`; the keywords are `node` and `edge`. Each gets an error naming the replacement. |
| 228 | **Nodes that nothing orders.** Every pair of nodes must clear the other, and where the file says nothing about which side of what, it is an error naming the pair: `"b" and "c" overlap, and nothing says which side of the other either one sits on`. Hanging two children off the same side of the same target is the usual cause. Place one against the other. |
| 229 | **An annotation with no wrap.** |
| 230 | **Reaching for a coordinate, an offset, or a waypoint.** None exist. If a line goes somewhere wrong, say more about it with `between`, `below <node>` and `from:`/`to:`; if a node is in the wrong place, add a placement. |
| 231 | **`#` is not a comment.** It opens a hex color. Comments are `//`. |
| 232 | **Guessing at syntax from another language.** There are no braces, no semicolons, no `-->`, no subgraphs. If you want something not written here, check `reference/syntax.md` before inventing it. |
| 233 | |
| 234 | ## Reference |
| 235 | |
| 236 | [reference/syntax.md] is the full syntax reference — every construct, the shape, icon and position sets, how edges sharing a side or a channel are ordered, what the language deliberately refuses and why, and the known defects. Read it when you need a construct this page does not cover, or when an error message points at behavior you did not expect. |
| 237 |
Discussion
Alternatives
Browse more free Claude skills or everything in Design.