Lucianski95 downloadsRenders LaTeX forest-style syntactic trees for linguistics inline.
Render LaTeX forest-style syntactic trees inline in Obsidian notes. Node labels go through Obsidian's MathJax pipeline, so primes (X$'$), superscripts (X$^0$), subscripts and arbitrary inline math just work.
The plugin focuses on the subset of forest that linguists actually use for syntactic trees: bracket syntax, per-node options, the for tree propagator, edge labels, triangle (roof) phrases, and \draw-style movement arrows between named nodes — with TikZ-faithful to[…] semantics.
Trees live in fenced forest code blocks:
```forest
[CP
[DP, name=who [who$_i$]]
[C$'$
[C [did]]
[TP
[DP [Mary]]
[T$'$
[T]
[vP
[DP [$t$]]
[v$'$
[v]
[VP
[V [see]]
[DP, name=obj [$t_i$]]]]]]]]]
\draw[->] (obj) to[out=south, in=south] (who);
```
The plugin processes the source as forest-package syntax and emits an inline SVG. Labels are MathJax-rendered, so anything between $…$ typesets properly.
Every node sits in [ … ]. Children are nested directly:
[Parent [Child1] [Child2 [Grandchild]]]
To put a literal [, ], ,, or = into a label, brace-protect that chunk: [{a, b}].
A comma after the node content introduces a comma-separated option list:
[VP, draw, name=verb, tier=word]
| Option | Effect |
|---|---|
name=<id> |
Names the node so movement arrows can target it |
tier=<tier> |
Aligns all nodes sharing a tier name to the same horizontal line |
roof / triangle |
Draws a triangle from the parent down to this node (phrasal shorthand) |
draw |
Outlines the node with a rectangle |
circle |
Outlines the node with a circle |
phantom |
Reserves layout space but draws nothing — useful for alignment |
parent anchor=south|center |
Where the edge leaving this node downward attaches |
child anchor=north|center |
Where the edge entering this node from above attaches |
edge label={...} |
Italic label on the edge between this node and its parent |
for tree and for descendantsApply options to a node and all its descendants:
[CP, for tree={parent anchor=south, child anchor=north}
[DP] [C$'$ [C] [TP]]]
for descendants={...} does the same but excludes the node itself.
Anything between $…$ in a node label is rendered with MathJax:
| You type | You get |
|---|---|
X$'$ |
X′ |
X$^0$ |
X⁰ |
t$_i$ |
tᵢ |
$\overline{X}$ |
X̄ |
Forest's TeX-dimension size options can sit on any node — root or interior — and apply locally:
| Option | Meaning |
|---|---|
s sep=X |
Sibling separation among this node's children |
l sep=X |
Level separation between this node and its children |
s sep+=X |
Add to the global sibling separation |
l sep+=X |
Add to the global level separation |
inner sep=X |
Padding inside each node label |
inner xsep=X |
Horizontal-only padding |
inner ysep=X |
Vertical-only padding |
font size=X |
Label font size |
X is a TeX dimension: pt, pc, bp, cm, mm, em, ex, in, px, or a bare number (= pixels). 1 pt ≈ 1.333 px, 1 cm ≈ 37.8 px at 96 dpi.
Local overrides on inner nodes only affect that node's children:
```forest
[vP ← root spacing default
[DP [Mary]]
[v$'$, s sep=50pt, l sep=40pt ← v's subtree breathes out, rest stays tight
[v]
[VP
[V [saw]]
[DP [John]]]]]
```
To apply to a whole subtree, use the propagator:
```forest
[CP
[DP]
[C$'$
[C]
[TP, for tree={l sep=40pt, s sep=50pt}
[DP] [T$'$ [T] [vP [DP] [v$'$ [v] [VP]]]]]]]
```
After the tree, you can add \draw commands between nodes:
[CP
[DP, name=who [who$_i$]]
[C$'$
[C [did, name = c]]
[TP
[DP, name=subj [Mary$_j$]]
[T$'$
[T [did, name = t]]
[vP
[DP, name=vsubj [$t_j$]]
[v$'$
[V + v [see, name = v]]
[VP
[V [see, name = V]]
[DP, name=obj [$t_i$]]]]]]]]]
\draw[->] (obj) to[out=west, in=west] (who);
\draw[->] (vsubj) to[out=west, in=east] (subj);
\draw[->] (t) to[out=west, in=w] (c);
\draw[->] (V) to[out=west, in=w] (v);
Other forms the parser recognises:
\draw[->] (a) to (b);
\draw[->, dashed] (a) to[bend left=45] (b);
\draw[->, dotted] (a) to node{move} (b);
\draw[->, solid] (a) -- (b);
Recognised within each \draw:
| Token | Meaning |
|---|---|
\draw[-> …] |
Style block. Recognises dashed, dotted, solid (default is dashed) |
(name) |
Reference a node by its name= option or by its content text |
to[…] |
Curved arrow with option block (see below) |
to |
Default: drape below the tree |
-- |
Straight line (TikZ syntax) |
node{label} |
Italic label placed near the arrow's apex |
to[out=A, in=B]Per TikZ semantics, out=A and in=B set both the attachment point on the node boundary AND the tangent direction there. The attachment is the intersection of the ray from the node's centre at angle A with the node's bounding box. So out=south attaches at the south boundary and the curve leaves heading south.
You can use compass names or polar angles (0°=east, 90°=north, 180°=west, 270°=south):
\draw[->] (a) to[out=south, in=south] (b);
\draw[->] (a) to[out=south west, in=south] (b);
\draw[->] (a) to[out=east, in=west] (b);
\draw[->] (a) to[out=270, in=180] (b);
\draw[->] (a) to[out=south east, in=north west, looseness=1.4] (b);
Compass-name → angle: east=0°, north east=45°, north=90°, north west=135°, west=180°, south west=225°, south=270°, south east=315°.
Whitespace inside compound names is tolerated. An optional looseness=N (default 1) scales how strongly the tangent is enforced — higher values exaggerate the curve.
bend left=N and bend right=N rotate the tangent angles. Combined with out/in, they add N° CCW to out and subtract N° from in (for bend left), bowing the curve further to that side of the path direction:
\draw[->] (a) to[out=south, in=east] (b); ← baseline
\draw[->] (a) to[out=south, in=east, bend left=20] (b); ← bows more left
\draw[->] (a) to[out=south, in=east, bend right=20] (b); ← bows more right
Used alone (without out/in), bend left=N / bend right=N produce a quadratic curve bowing to that side of the direct line.
(name.south)For simple cases you can also pin an endpoint to a named anchor directly: (name.south), (name.east), (name.north west), etc. This is shorthand that pins the attachment point and uses the corresponding outward tangent direction. The full TikZ list is supported:
north, south, east, west, north east, north west, south east, south west, center.
When you give neither an anchor nor out=/in=, the renderer auto-picks the side of each node facing the other endpoint.
Drawn geometrically as filled triangles oriented along the curve's tangent at the endpoint — no dependence on SVG <marker> definitions, which makes them robust across themes and embedded-SVG quirks. Style mimics TikZ's default > (slim, slightly concave back).
A settings tab exposes the global visual defaults: level separation, sibling separation, subtree separation, label font size, edge stroke width, and a toggle to outline every node by default. Per-tree options (above) override these locally.
The default look is aligned with TikzJax-rendered TikZ output (Computer- Modern-style serif text, thin crisp lines, refined arrow tips). Customize further with a CSS snippet targeting any of:
| Selector | What it controls |
|---|---|
.forest-svg |
The root SVG. Set font-family, color, font-size |
.forest-label-wrap |
Wrapper around each node label |
.forest-edges |
Group containing all tree edges |
.forest-edge |
Each individual tree edge (line or roof) |
.forest-roof |
The triangle of a roof/triangle node |
.forest-arrows |
Group containing all movement arrows |
.forest-movement |
Each arrow's curve (the line, not the head) |
.forest-arrowhead |
The filled triangle at the arrow tip |
.forest-edge-label |
Italic label next to a tree edge |
.forest-movement-label |
Italic label near an arrow's apex |
.forest-node-shape |
The bounding box drawn around draw/circle nodes |
All strokes and fills resolve through currentColor, set from color: var(--text-normal) on the SVG. So one rule recolors everything:
.forest-svg { color: #003366; font-family: "EB Garamond", serif; }
Dark mode is automatic because --text-normal flips with the theme.
main.js, manifest.json, styles.css from the release.<your-vault>/.obsidian/plugins/obsidian-forest/.Alternatively, to build from source:
npm install
npm run build
It is not a complete port of the forest LaTeX package. forest is a TikZ/PGF DSL with conditionals, dynamic trees, aggregate functions, custom handlers, externalization, etc. — none of which is sensible to reimplement in JavaScript. This plugin covers the slice syntacticians reach for daily: bracket trees, primes and bars in labels, triangles, named nodes, movement arrows, and TikZ-style direction control via out=/in= and bend.