Declarative, print-first machine-learning & neural-network diagrams for Typst.
Warning
Work in progress — local install only. mlatlas is not yet published to
Typst Universe, so #import "@preview/mlatlas:..." does not
work yet. Install it locally — see Installation. The API is still
stabilizing and may change (e.g. the node primitive was recently renamed block → block2d)
before the first published release.
Describe the model — transformer(blocks: 6), mlp((4, 8, 8, 3)), a two-stream fusion —
and get a clean, publication-quality figure. Batteries-included defaults so a simple diagram
is one line; full control (themes, per-element overrides, custom topologies, raw IR) when you
need it.
Publication-grade figures from dedicated per-family renderers — VGG-16 / LeNet-5 above; LSTM cells (Olah), GNN message passing, DDPM chains, and LDA plates below.
The same diagram in four themes — one setting flips the whole look.
The default mono theme is built for paper: light fills, dark text, sharp orthogonal
edges, stealth arrows, with garnet used only as a sparse accent. Never a dark block with
white text. Switch the entire look with one setting:
#render(ir) // mono (default) — restrained, print-safe
#render(ir, theme: colorful) // Okabe-Ito light tints (colourblind-safe)
#render(ir, theme: grayscale) // pure B&W, value + dash differentiation
#render(ir, theme: slides) // opt-in dark (auto white text)
#render(ir, theme: palette-theme((op: rgb("#1F414D"), norm: rgb("#65780B")))) // your own schemeA luminance check picks readable text for any fill, so contrast is never wrong by accident.
mlatlas is not on the Typst package registry yet, so install it locally — either as a local package or by relative path.
As a local package (recommended). Clone into Typst's local-packages directory, then import
with the @local namespace:
# Linux
git clone https://github.com/j-vaught/mlatlas.git \
~/.local/share/typst/packages/local/mlatlas/0.3.0
# macOS: ~/Library/Application Support/typst/packages/local/mlatlas/0.3.0
# Windows: %APPDATA%\typst\packages\local\mlatlas\0.3.0#import "@local/mlatlas:0.3.0": *Or by relative path. Clone anywhere and point at lib.typ directly:
#import "path/to/mlatlas/lib.typ": *Once published,
#import "@preview/mlatlas:0.3.0": *will work too — until then it won't resolve.
#import "@local/mlatlas:0.3.0": * // local install — see Installation above (not on @preview yet)
// Simple is one line — auto-wired, auto-themed.
#render(seq(
block2d(label: [Input], role: "data"),
block2d(label: [Hidden Layer]),
block2d(label: [Output], role: "output"),
))
#render(transformer(blocks: 6, heads: 8, rope: true)) // residual skips auto-routed
#render(mlp((4, 8, 8, 3))) // node-edge MLP
#render(lenet(), dir: "ltr") // CNN as 3-D feature-map prismsA small hand-rolled 3-D engine projects each block's eight corners itself — so back-face culling and a single clean silhouette are correct at any camera angle (cetz's native 3-D can't manage this cleanly, because it hides the projected coordinates: you get corner spikes and bow-tied outlines). Flat by default — the crisp edges carry the depth — with opt-in directional shading.
#import "@preview/cetz:0.5.2"
#cetz.canvas(length: 1cm, {
import cetz.draw
block3d(draw, w: 1.4, h: 2.0, dep: 1.2, base: rgb("#FFF2E3")) // one block, one call
feature-map(draw, (4, 0), spatial: 112, channels: 128, relu: true) // spatial->height, channels->depth
})Architecture renderers compose it: cnn / feature-stack (feature-map rows), resnet3d,
unet3d, fpn3d, attention-3d (the multi-head QKᵀ score cube), transformer-3d,
rnn-unroll3d, lstm-cell3d, voxel-grid, kernel-slide, vae3d / gan3d. Or drop a 3-D
tensor straight into the IR for auto-layout + edges:
render(seq(tensor(title: [x], axes: ([56], [56], [3])), tensor(title: [z], axes: ([1], [1], [128])))).
The camera is three angles (pitch, yaw, roll) — exactly like cetz's ortho — set once per
scene and overridable per block:
| preset | family | look |
|---|---|---|
cam-iso (default) |
rotation | isometric, corner-front — sculptural |
cam-dimetric |
rotation | gentle dimetric |
cam-top-down |
rotation | high-angle |
cam-cabinet |
oblique | upright front face, depth shears up-right — best for CNN rows |
cam-cavalier |
oblique | upright front, full-depth |
cam-face |
oblique | shallow oblique |
block3d(draw, w: 2, h: 2, dep: 2, cam: cam-cabinet, shade: true) // a preset
block3d(draw, w: 2, h: 2, dep: 2, cam: (30deg, -40deg, 8deg)) // or raw anglesDual-backbone, two-stream / multi-modal fusion, dual-head — a few readable calls, no manual coordinates:
// two-stream / multi-modal fusion
#render(two-stream(image-stream, text-stream, fusion: [Fusion], head: head-stack))
// shared backbone, two task heads
#render(branch(backbone, cls-head, box-head))
// arbitrary fan-in / fan-out
#render(merge(arm-a, arm-b, arm-c, into: block2d(label: [Concat])))block2d(label: [Conv], style: (fill: rgb("#eee"), stroke: 2pt + rgb("#65780B"))) // per-node
block2d(label: [Focal], emphasis: true) // sparse garnet accent
graph(edges: (("a", "b", (style: (stroke: 2pt + red), label: [grad])),)) // per-edge
render(ir, role-map: (attention: "param")) // restyle a category
render(ir, theme: theme(spacing: (18mm, 12mm))) // tweak any theme field
graph(nodes: (..ir-nodes..), edges: (..)) // full IR escape hatchrender warns (by default) when an edge crosses an unrelated block — a bbox-aware,
scale-independent check:
#render(ir) // check: "warn" -> banner if a line crosses a block
#render(ir, check: "error") // hard error
#render(ir, check: "off")| Layer | Pieces |
|---|---|
| Themes | mono (default), colorful, colorblind, grayscale/bw, slides; theme(..), palette-theme(..), theme-swatch |
| IR | ir-node, ir-edge, frag, namespace, shift (escape hatch) |
| Primitives | block2d, op-node, slab/conv (3-D prisms), neuron-graph, tensor (3-D IR node) |
| 3-D engine | block3d, feature-map, scene, project, tensor3d, arrow3d/dock, voxel-grid + cam-iso/cam-cabinet/… presets |
| Composition | seq, parallel, branch, merge, concat, residual, plate, graph |
| Presets | perceptron, mlp, feedforward, transformer(-block), attention-head, resnet-stage, unet, two-stream, gan, vae, rnn-unroll, gcn |
| Dedicated renderers | cnn/vgg16/alexnet/lenet5 (PlotNeuralNet-grade), lstm-cell (Olah), message-passing (GNN), diffusion-chain (DDPM), lda-plate/hmm-chain (PGM) |
| 3-D renderers | resnet3d, unet3d, fpn3d, attention-3d (QKᵀ cube), transformer-3d, rnn-unroll3d, lstm-cell3d, kernel-slide, vae3d/gan3d |
Architecture: a plain-dict semantic IR is the contract; primitives/presets emit IR; the
renderer draws it via fletcher/cetz behind an adapter firewall. See
docs/design-spec.md for the full design and the roadmap toward the
broader ML-diagram atlas (sequence cells, attention internals, generative/graph/probabilistic
models, plots, audio/3-D, and the long tail).
typst compile --root . examples/transformer.typ # or any example
./tools/render-all.sh # compile + rasterize allMIT © 2026 J.C. Vaught.








