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 ZIPUse now

Files of Reladraw

reladraw/bfb8cf72 files
SKILL.md
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

  1. Write a .reladraw file.
  2. reladraw diagram.reladraw -o diagram.svg (-o - writes to stdout; the default output path is the input with .svg in place of its extension).
  3. If it fails, the error names the statement and what is wrong with it. Fix and re-run.
  4. 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 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.
  • 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.
  • 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, 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.
  • 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> and from:/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, check reference/syntax.md before 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---
2name: reladraw
3description: 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 
8A text language for diagrams. Source in, standalone SVG out, no runtime dependencies.
9 
10Every 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 
12That 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```
17reladraw --help
18```
19 
20If that fails, `npx reladraw` works without installing, and `npm install -g reladraw` installs the command.
21 
22## The loop
23 
241. Write a `.reladraw` file.
252. `reladraw diagram.reladraw -o diagram.svg` (`-o -` writes to stdout; the default output path is the input with `.svg` in place of its extension).
263. If it fails, the error names the statement and what is wrong with it. Fix and re-run.
274. Re-read your own source to confirm the picture says what you meant.
28 
29Errors 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```
32diagram.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 
41So putting something between two things is what pushes them apart, by exactly what it needs:
42 
43```
44node hub "Hub"
45node side "Side" left of hub
46node wedge "Wedged" right of side left of hub level with hub
47```
48 
49Nothing 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 
53A 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 
55A 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```
60node <name> ["<text>" [(<text properties>)]] [<placement> | <attribute>] ...
61```
62 
63Leave 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 
65Containment 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```
68node server "Server"
69node server.api "API"
70node server.worker "Worker"
71```
72 
73A 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 
75Node 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 
81Everything 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```
84node docker "Docker" (at: bottom-center, align: center) below deploy
85node aside "a longer remark that folds" (size: small, wrap: 30) shape: none
86edge a -> b "rclone" (color: theme-muted)
87```
88 
89An 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 
91A stretch of a text can borrow a style's text color, which is how a node carries a quieter qualifier:
92 
93```
94style dim text: (color: theme-muted)
95node grinder "Grinder / [dim]medium-fine[/dim]"
96```
97 
98The 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 
102Every 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 
106A 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```
109node dump "nightly dump" shape: document
110node aside "a remark" (wrap: 30) shape: none
111node unit icon: cube
112node drive "External HD" badge: disk
113```
114 
115A 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 
117A 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 
119A 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 
121There 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```
128above X below X left of X right of X
129above-left of X above-right of X below-left of X below-right of X
130level 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 
136Three 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 
142Gaps are named — `none`, `tight`, `normal` (default), `wide` — and belong to the placement, in brackets:
143 
144```
145node 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```
153edge <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 
158Each 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 
162A 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 
166An edge with text widens the corridor between its own two ends by what the text needs, so texts are safe to add.
167 
168How 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```
173node <name> "<text>" (wrap: 30) shape: none <placement> ...
174```
175 
176There 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```
181inside <node> <part>
182outside <node> <part>
183on <node> <part>
184```
185 
186A 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```
191node bob "Bob the builder"
192node bob_link "bob.example.com" inside bob bottom-center
193node count "3" on bob top-right
194```
195 
196This 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```
201style store fill: theme-primary-subtle border: theme-primary badge: database
202node records "Records" style: store
203```
204 
205Prefer 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.
211style store fill: theme-primary-subtle border: theme-primary badge: database
212 
213node browser "Browser"
214node api "API server" right of browser
215node db "Postgres" right of api style: store
216node worker "Worker" below api
217 
218edge browser -> api "HTTP" from: right to: left
219edge api -> db "SQL" from: right to: left
220edge worker -> db "writes" from: right to: bottom
221 
222node 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](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