-
Notifications
You must be signed in to change notification settings - Fork 2
dictim Syntax
'dictim syntax' is a data format which is a subset of Clojure's edn data format for specifying a diagram.
'dictim format' - a diagram specified in dictim syntax may be specified in either edn or json. They are equivalent.
The syntax of dictim closely resembles that of d2's text language. It is intended to be easy to translate between the two. dictim is a nested format like d2. It also closely resembles that of graphviz' dot language (which is itself similar to d2). In the specification below, it is called out when a particular feature is d2 only. Dictim's primary target is d2, with dot secondary.
If dictim's nested syntax is not what you require, there are two other options:
-
The dictim.graph library allows you to specify a diagram as a graph. The graph definition is then converted to dictim syntax.
-
dictim itself (i.e. this library) has a second syntax, called 'flat dictim' (which is covered at the bottom of this document). Its flat structure makes it easier to manipulate for some use cases, e.g. to break part for storage, than dictim's nested syntax.
- Elements
- Shapes
- Connections
- Containers
- Special Objects
- Attributes
- Directives
- Comments
- Lists
- Empty Lines
- Reserved d2 keywords
- Types
- In Depth
- Gotchas
- Json Format
Dictim is a series of dictim elements - each one closely matching in syntax their d2 equivalent. The d2 tour explains the structure and semantics of the d2 language.
Vectors for objects, maps for styling - this is the fundamental pattern in dictim:
-
Vectors
[...]
represent diagram objects (shapes, connections, containers) that produce visible elements -
Maps
{...}
represent attributes and styling information that control how objects are rendered
A dictim element can be one of six principal types:
- Shapes - Individual diagram elements
- Connections - Links between elements
- Containers - Groups that hold other elements
- Attributes - Styling and configuration
- Comments - Documentation and notes
- Lists - Sequences of elements on one line
Exception: Vars (d2's variables) are modelled as maps but may contain both data and styling information.
Here's a simple dictim diagram showing the core concepts:
(
;; A shape with styling
[:user "User" {:shape "person" :style {:fill "lightblue"}}]
;; A container with nested elements
[:system "System" {:style {:fill "lightgray"}}
[:api "API Server"]
[:database "Database" {:shape "cylinder"}]
[:api "->" :database "queries"]]
;; A connection between top-level elements
[:user "->" :system "uses"]
;; Top-level styling directive
{:direction "right"}
)
This creates a diagram with:
- A user shape (styled as person)
- A system container holding an API server and database
- Connections showing relationships
- Right-to-left layout direction
A collection of dictim elements is modelled as a Clojure list, (..elements..)
.
This is the only time that a Clojure list is used in the dictim syntax.
The syntax for a shape is:
[<shape-key> <label>(optional) <attribute-map>(optional)]
for example:
[:personA "Person A" {:style {:fill "red"}}]
which is represented in d2 as:
personA: Person A {style {fill "red"}}
The shape-key
can be either a string, a number or a keyword.
The label should be a string.
The attribute map should be a map whose keys are either keywords or strings, and whose values are either strings or numbers.
In dictim, the elements of a shape are laid out in a Clojure vector.
Examples with the optional parts missing:
[:personA "Person A" {:style {:fill "red"}}]
[:personA "Person A"]
[:personA {:style {:fill "red"}}]
[:personA]
["person A" "A person called Mike"]
["person A"]
[:architect "System Architect" {:shape "c4-person"}]
[:people.personA]
are all valid shapes. The last one denotes that 'personA' is inside a container called people
. The final example shows a C4 diagram person shape.
d2 offers a shortcut for creating a shape and styling one aspect of it at the same time.
for example:
aShape.style.fill: red
which when parsed into dictim becomes
["aShape" {"style.fill" "red"}]
As a text format, d2 is quite free-form and occasionally offers more than one way to express the same thing. That means that when you round-trip dictim to d2 and back again (by compiling to d2, then parsing to dictim) you might not get the same as the original, even though semantically it's the same.
For example, let's compile a dictim shape to d2 and then parse it back
=> (dictim (d2 [:aShape.style.fill "red"]))
(["aShape" {"style.fill" "red"}])
Note how the attributes are put into their own map on the way back.
Clojure keywords are converted to strings for d2 and not automatically converted back again, although you can control this behaviour by passing a key-fn
to the dictim
function that handles parsing.
For shapes (and connections and containers), if you wish to have a multi-line label, use \\n
at the desired point in the label string. \n
must be escaped, so always \\n
.
There are two types of connections; single connections and multiple connections.
Syntax:
[<key1> <direction> <key2> <label>(optional) <attribute-map>(optional)]
for example:
[:personA "--" :personB "brothers" {:style.stroke-width 2}]
which is represented in d2 as:
personA -- personB: brothers {style.stroke-width: 2}
key1
and key2
must each be either the key of a shape or a container
direction
must be one of "<>" "--" "<-" "->"
Multiple connections are expressed all in one go.
Syntax:
[<key1> <dir1> <key2> <dir2> <key3> ... <keyN> <label>(optional) <attribute-map>(optional)]
for example:
[:a "<-" :b "->" :c "children" {:style.stroke-width 2}]
which is represented in d2 as:
a <- b -> c: children {style.stroke-width: 2}
Multiple connections have to share the same label and attributes if those are specified.
Note
This part of the dictim syntax applies to d2 only.
Connection references are a way of referring to a connection that you've already specified, either to style it or null it out.
for example:
(x -> y)[0].style.stroke: red
in d2 becomes
[:x "->" :y [0] {:style.stroke "red"}]
where the index 0
would refer to the first occurrence of the connection x -> y
in the d2 text.
An index of a connection reference can also be a glob *
when you wish to refer to all connections.
You can null out a connection reference by
[:x "->" :y [0] nil]
will not be displayed.
Connection references can be suspended or unsuspended:
[:x "->" :y [0] :suspend]
[:x "->" :y [1] :unsuspend]
This compiles to:
(x -> y)[0]: suspend
(x -> y)[1]: unsuspend
The syntax for containers is the same as for shapes, except that at the end you put the set of contained elements, which may be of any number and each of any of the six types.
Syntax:
[<ctr-key> <label>(optional) <attribute-map>(optional) <nested elem1> <nested elem2> ... <nested elemN>]]
for example:
[:family1 "The Jones'" {:style {:fill "red"}}
[:personA "Henrick"]
[:personB "Michael"]
[:personA "--" :personB "brothers"]]
which in d2 is:
family1: The Jones' {
style: {
fill: red
}
personA: Henrick
personB: Michael
personA -- personB: brothers
}
Containers may nest other containers which may nest ... and so on.
Note
This part of the dictim syntax is valid in dot, but has no special meaning.
Special objects, e.g.:
- Text & Code
- Icons & Images
- SQL Tables
- UML classes
- Sequence diagrams
- Grid diagrams
are all supported via the dictim equivalent of the relevant d2 syntax.
We've seen attributes already; attribute maps can belong to shapes, connections and containers to specify d2 rendering instructions.
They can also appear at the top level, i.e. an attribute map can exist as a top level element.
An example is the :direction/ direction: reserved keyword, e.g.
{:direction "right"}
which compiles to d2:
direction: right
note the lack of brackets in d2 for top level attributes, i.e. those occurring at diagram level.
Note that because d2 reserves the special character #
to indicate the start of a comment, hex colors must be either single or double-quoted.
This won't work:
{:style.fill "#bfe6a5"} ; # treated as comment start
Instead do:
{:style.fill "'#bfe6a5'"} ; Single quotes around the hex value
Or equivalently as nested attributes:
{:style {
:fill "'#bfe6a5'"
}}
d2's Classes and Vars are also modelled as special maps which resemble an attribute map.
Please note that in dictim, attributes that exist as standalone elements at the top level of a set of elements are sometimes called 'directives'.
Note
This part of the dictim syntax is valid in dot, but has no special meaning.
classes
is a reserved word in d2 and in dictim.
This d2 class definition:
classes: {
uno: {
label: load balancer
width: 100
height: 200
style: {
stroke-width: 0
fill: '#44C7B1'
}
}
dos: {
label: dos
}
}
which can be represented in dictim as
{"classes"
{"uno"
{"label" "load balancer",
"width" 100,
"height" 200,
"style" {"stroke-width" 0, "fill" "'#44C7B1'"}}}}
As per d2, a class definition is later used on a shape, container or edge, for example in dictim.
{"x.class" [:list "uno" "dos"]}
Note
This part of the dictim syntax is valid in dot, but has no special meaning.
vars
is a reserved word in d2 and in dictim.
This d2 var definition:
vars: {
server-name: Cat
}
is equivalent to the dictim
{"vars" {"server-name" "Cat"}}
Substitutions in dictim are the same as in d2. For example, substituting the above var into a shape's label:
["server2" "${server-name}-2"]
This compiles to:
server2: ${server-name}-2
Note
This part of the dictim syntax is valid in dot, but has no special meaning.
The d2-legend
is a special variable introduced in d2 v0.7.0 that allows you to create diagram legends. Legends help
explain the meaning of different shapes, connections, and relationships in your diagrams.
In dictim, d2-legend
is placed inside a vars
block and contains a list of diagram elements that define the legend
entries:
{"vars"
{"d2-legend"
[:list
["microservice" {"label" "Microservice", "shape" "hexagon"}]
["database" "Database" {"shape" "cylinder"}]
["microservice" "<->" "database" "API Connection"]]}}
This compiles to:
vars: {
d2-legend: {
microservice: Microservice {
shape: hexagon
}
database: Database {
shape: cylinder
}
microservice <-> database: API Connection
}
}
The d2-legend can contain any combination of:
- Shapes: Define example shapes that appear in your diagram
- Connections: Show different types of relationships
- Styled elements: Demonstrate visual patterns used in the diagram
{"vars"
{"d2-legend"
[:list
;; Shape examples
["good-service" {"label" "Healthy Service", "style.fill" "green"}]
["bad-service" {"label" "Unhealthy Service", "style.fill" "red"}]
;; Connection examples
["good-service" "<->" "bad-service" "Good relationship"
{"style.stroke" "green", "style.stroke-width" 2}]
["good-service" "->" "bad-service" "Poor relationship"
{"style.stroke" "red", "style.stroke-dash" 3}]]}}
- Location: d2-legend must be placed inside a vars block
-
Format: Always use
[:list ...]
to contain the legend elements - Content: Can include shapes, connections, and any other diagram elements
- Styling: Legend elements can have their own styling to demonstrate visual patterns
- Purpose: Helps viewers understand the meaning of different visual elements in your diagram
Integration with Main Diagram
The d2-legend is typically used alongside your main diagram content, as in this dictim:
(
;; Main diagram elements
["frontend" "Frontend App"]
["api" "API Service" {"shape" "hexagon", "style.fill" "green"}]
["db" "Database" {"shape" "cylinder"}]
["frontend" "->" "api" "requests"]
["api" "<->" "db" "queries"]
;; Legend explaining the visual patterns
{"vars"
{"d2-legend"
[:list
["service" {"label" "Healthy Service", "shape" "hexagon", "style.fill" "green"}]
["service" "->" "data" "Data flow"]
["service" "<->" "data" "Bidirectional sync"]]}}
)
This creates a legend that explains what the green hexagons and different arrow types mean in your diagram.
In d2, certain top-level declarations are syntactically ambiguous - they could be parsed as either containers or attributes. Examples include:
**: suspend
direction: down
*.style.fill: red
While d2 treats these ambiguously (the distinction doesn't matter functionally in d2), dictim's syntax strictly differentiates between containers and attributes:
- Containers:
["key" "label" {...}] or ["key" {...}]
- Attributes:
{"key" "value"}
Design Decision
For ambiguous top-level cases, dictim makes a deliberate choice to parse them as attributes (internally called "directives"). This means:
**: suspend
Parses as:
{"**" "suspend"} ; attribute
Rather than:
["**" "suspend"] ; container with label
Rationale
This design choice ensures:
- Consistency - All top-level global styling and configuration declarations follow the same pattern
- Clarity - The distinction between containers (structural elements) and directives (styling/configuration) is explicit in dictim syntax
- Predictability - Developers can rely on consistent parsing behavior for ambiguous d2 patterns
When these directives are compiled back to d2, they produce the original ambiguous syntax, maintaining full d2 compatibility while providing clearer structure in dictim format.
In d2 comment lines are started with the character #
.
Dictim mirrors this; a comment is a string that starts with the character #
:
"# Hans, accused of cheating"
This compiles to the same in d2:
# Hans, accused of cheating
Lists are a special type of container for sequences of elements that should all be put on one line separated by semicolons:
[:list [:a-shape "A shape"] [:b-shape "B Shape"]]
This compiles to:
a-shape: A shape; b-shape: B shape
Important notes:
- Lists may not nest other lists
- Only shapes or containers may be put into a list in dictim
- In d2, putting elements on one line doesn't have special meaning, except inside sequence diagrams where it denotes the order of the actors
Top-level lists: Occupy their own line in a d2 text file (shown above).
Inner lists: Used within attributes and surrounded by square brackets in d2. For example, a shape that participates in multiple styling classes:
{"x.class" [:list "uno" "dos"]}
Note
Inner lists are not allowed in dot.
The goal of this project is to allow faithful two-way conversion between Clojure data structures (e.g. 'dictim') and d2. In d2, to improve readability, you'll often leave empty lines to separate different parts of the file.
Empty lines are parsed into dictim as:
[:empty-lines 1]
where the 1
indicates the number of consecutive empty lines at this point.
Note: Whether empty-lines
are captured during parsing is optional (defaulted to false). Please see the parsing page for details.
General attributes:
-
shape
label
source-arrowhead
target-arrowhead
-
near
icon
width
height
constraint
direction
-
tooltip
style
class
grid-rows
grid-columns
-
link
level
src
dst
Style sub-keys:
-
opacity
fill
stroke
stroke-width
-
stroke-dash
border-radius
font-color
shadow
-
multiple
3d
animated
link
filled
fill-pattern
-
underline
double-border
bold
italic
text-transform
For details of what these all do, please see the d2 tour.
Dictim-specific keywords:
-
:list
and:empty-lines
These keywords cannot be used in your shapes, connections and containers.
For dot output, Dictim does not currently check against Graphviz' reserved attribute keywords during compilation.
Dictim is quite flexible with types. Element keys can be keywords or strings. In attr maps, the values can be integers, floats, booleans or strings. Non-string types will be converted to strings during compilation. Values in attr maps will be converted back to the primitive type during parsing, and there are the optional key-fn
and label-fn
functions in parsing that can be used to convert the type or format of keys and labels.
Note
Globs/asterisks and ampersands are not valid syntax in shape, container, connection keys in the dot language. In labels, they are fine.
In d2, a wildcard character '*' is called a glob.
globs may be used in two different positions in d2 and dictim.
In attributes, when trying to apply a style to more than one object. For example in the dictim
[:aShape] [:bShape] {:*Shape.style.fill "red"}
would cause both shapes to have a red fill.
The &
character can be used to filter where the style is applied as per the d2 docs.
Secondly, in connection keys to indicate multiple connections. The dictim elements
["zone-A"
["machine A"]
["machine B" ["submachine A"] ["submachine B"]]]
["zone-A.**" "->" "load balancer"]
indicates that there should be connections between all three shapes in zone-A and load balancer.
D2 supports prefixed attributes using &
and !&
for advanced filtering and control. Dictim preserves these prefixes:
The &level
attribute controls diagram level visibility:
{:&level 3}
This compiles to:
&level: 3
Connection source and destination attributes can be prefixed:
;; Apply to connections from external-api
{:&src "external-api"}
;; Exclude connections to internal-db
{:!&dst "internal-db"}
These compile to:
&src: external-api
!&dst: internal-db
Prefix meanings:
-
&
prefix: Apply the attribute/filter -
!&
prefix: Exclude or negate the attribute/filter
Imports in d2 are represented by the @
symbol which is compiled and parsed by dictim. Imports are modelled as shapes, e.g.:
["@x.d2"]
Dictim also supports D2's spread import syntax using ...@
for importing and spreading elements from other files:
["...@shared/common-styles.d2"]
This compiles to:
...@shared/common-styles.d2
Layers, scenarios and steps are modelled in dictim as normal containers whose key is either layers
, scenarios
or steps
.
Examples:
;; Layers for different views
[:layers
[:base
[:user "User"]
[:system "System"]]
[:detailed
[:user "User" {:shape "person"}]
[:system "System" {:style {:fill "lightblue"}}]]]
;; Scenarios for different use cases
[:scenarios
[:happy-path
[:user "->" :system "login"]]
[:error-case
[:user "->" :system "invalid login" {:style {:stroke "red"}}]]]
;; Steps for sequential processes
[:steps
[:step1 "User enters credentials"]
[:step2 "System validates"]
[:step3 "Access granted"]]
d2 is a text format so can be a bit looser in its syntax than a data format. There are a small number of gotchas to be aware of.
d2 allows for a shape and one styling attribute to be specified all in one go:
aShape.style.fill: red
Is this a shape or an attribute? Dictim decides that since this line of d2 would result in a shape being displayed in the resultant diagram, its purpose is to be a shape, therefore the dictim equivalent is:
["aShape" {"style.fill" "red"}]
However the d2
*Shape.style.fill: red
would not produce a shape in the diagram. Its purpose is to style and the dictim equivalent is:
{"*Shape.style.fill" "red"}
i.e. it's an attribute.
Dictim provides a structured, data-driven approach to creating diagrams by representing D2 syntax as Clojure data structures. The key principles to remember:
-
Vectors
[...]
for diagram objects (shapes, connections, containers) -
Maps
{...}
for styling and attributes -
Lists
(...)
for collections of elements
-
Shapes:
[key label? attrs?]
-
Connections:
[key1 direction key2 label? attrs?]
-
Containers:
[key label? attrs? ...nested-elements]
-
Attributes:
{key value}
or{key {nested-key value}}
-
Comments: Strings starting with
#
-
Lists:
[:list element1 element2 ...]
-
Vars:
{"vars" {var-name var-value}}
-
Classes:
{"classes" {class-name class-definition}}
-
Connection references:
[key1 dir key2 [index] attrs?]
- Use meaningful keys for shapes and containers
- Avoid D2 reserved keywords in element names
- Quote hex colors:
"'#ff0000'"
- Use dot notation for nested elements:
"container.element"
- Remember that dictim ↔ D2 conversion preserves semantics, not formatting
dictim is made from plain Clojure data structures; vectors and maps which have their JSON equivalents as arrays and objects. EDN dictim and JSON dictim therefore look very close and have the same syntax; only the format is different.
For example, the dictim to describe a container with shapes and connections:
[:family1 "The Jones'" {:style {:fill "red"}}
[:personA "Henrick"]
[:personB "Michael"]
[:personA "--" :personB "brothers"]]
in JSON becomes:
[
"family1",
"The Jones'",
{
"style": {
"fill": "red"
}
},
[
"personA",
"Henrick"
],
[
"personB",
"Michael"
],
[
"personA",
"--",
"personB",
"brothers"
]
]
The Utilities page details how to convert to/ from JSON.