Typst Graphics and CeTZ
Render mathematical, technical and domain-specific Typst graphics as responsive SVG.
The theme’s typst renderer compiles ordinary
Typst source to responsive SVG during the site
build and inserts that SVG inline in the page. Typst outlines glyphs in its SVG
output, however: the figure is vector artwork, but text inside the artwork is
not browser-selectable <text>. Captions and the complete source shown beside
each example remain selectable semantic HTML. Do not choose this renderer when
selectable labels inside the finished figure are a requirement; use D2,
Graphviz, Plot or another renderer that emits SVG text instead.
CeTZ is an authoring library
for precise vector drawings inside Typst; it is inspired by TikZ and
Processing, but it is not a TikZ compatibility layer.
This distinction matters. The renderer can use Typst’s math, drawing
primitives and compatible Typst Universe
packages without a package-specific integration. Actual .tex or TikZ source
would require a separate TeX backend and is not accepted by renderer="typst".
Authoring paths#
| Source | Author writes | Build behavior |
|---|---|---|
| Inline | A fenced typst block | The pre-renderer discovers, compiles and inlines it |
| Project file | A .typ asset and diagram shortcode | Hugo inlines its cached SVG |
| URL | A versioned URL in diagram | Hugo fetches the source at build time |
File input is best for a reusable or substantial figure because the full
Typst program remains independently testable. Put shared sources under
assets/; a leaf bundle can keep a source beside its index.md.
CeTZ mathematical construction#
This rendering is Karl’s Picture from the CeTZ 0.5.2 gallery. It combines a unit circle, mathematical labels, projections, fills and line intersections.
Complete Typst source#
The source is the complete, unmodified CeTZ gallery example, licensed with CeTZ under LGPL-3.0.
#import "@preview/cetz:0.5.2"
#set page(width: auto, height: auto, margin: .5cm)
#show math.equation: block.with(fill: white, inset: 1pt)
// Create a new canvas to draw on
#cetz.canvas(length: 3cm, {
import cetz.draw: *
// Change the design for all elements after it
set-style(
// Design of arrow tips at the end of lines
mark: (fill: black, scale: 2),
// Design of lines
stroke: (thickness: 0.4pt, cap: "round"),
// Design of angles
angle: (
radius: 0.3,
label-radius: .22,
fill: green.lighten(80%),
stroke: (paint: green.darken(50%))
),
// Design of all text elements with an anchor
content: (padding: 1pt)
)
// Draws the grid behind the circle
grid((-1.5, -1.5), (1.4, 1.4), step: 0.5, stroke: gray + 0.2pt)
// Draw the unit circle
circle((0,0), radius: 1)
// Draw the axis lines and axis labels
line((-1.5, 0), (1.5, 0), mark: (end: "stealth"))
content((), $ x $, anchor: "west")
line((0, -1.5), (0, 1.5), mark: (end: "stealth"))
content((), $ y $, anchor: "south")
// Draw the number steps on the x-axis
for (x, ct) in ((-1, $ -1 $), (-0.5, $ -1/2 $), (1, $ 1 $)) {
line((x, 3pt), (x, -3pt))
content((), anchor: "north", ct)
}
// Draw the number steps on the y-axis
for (y, ct) in ((-1, $ -1 $), (-0.5, $ -1/2 $), (0.5, $ 1/2 $), (1, $ 1 $)) {
line((3pt, y), (-3pt, y))
content((), anchor: "east", ct)
}
// Draw the green angle
cetz.angle.angle((0,0), (1,0), (1, calc.tan(30deg)),
label: text(green, [#sym.alpha]))
// Draw the hypothenuse of the triangle
line((0,0), (1, calc.tan(30deg)))
// Change the stroke for all upcoming elements
set-style(stroke: (thickness: 1.2pt))
// Draw the inner opposite leg of the triangle:
// "The intersection of a vertical line (|-) through (30deg, 1) and a horizontal line through (0, 0)"
line((30deg, 1), ((), "|-", (0,0)), stroke: (paint: red), name: "sin")
// Place the text halfway through on the opposite leg
content(("sin.start", 50%, "sin.end"), text(red)[$ sin alpha $])
// Draw the adjacent leg of the triangle
line("sin.end", (0,0), stroke: (paint: blue), name: "cos")
// Place the text halfway and position it below the line
content(("cos.start", 50%, "cos.end"), text(blue)[$ cos alpha $], anchor: "north")
// Draw the outer opposite leg of the triangle
line((1, 0), (1, calc.tan(30deg)), name: "tan", stroke: (paint: orange))
// Draw the tangent equasion at the top and to the right of the line
content("tan.end", $ text(#orange, tan alpha) = text(#red, sin alpha) / text(#blue, cos alpha) $, anchor: "west")
})
Markdown inclusion#
{{< diagram renderer="typst" src="graphics/karls-picture.typ"
alt="A unit circle with sine, cosine and tangent constructions"
caption="Karl's Picture from the CeTZ 0.5.2 gallery." >}}CeTZ neural network#
The next figure is an original CeTZ implementation inspired by the TikZ.net neural-network examples. The Typst program computes node positions and connections from the layer sizes; it does not contain or execute TikZ source.
Complete Typst source#
#import "@preview/cetz:0.5.2"
#set page(width: auto, height: auto, margin: .35cm)
#cetz.canvas(length: 1cm, {
import cetz.draw: *
let input = rgb("#d8efdf")
let hidden = rgb("#dce8f7")
let output = rgb("#f8ded8")
let edge = rgb("#8193a8")
let ink = rgb("#263f5b")
let y(count, index) = (count - 1) / 2 - index
// Connections are drawn first so the nodes remain visually dominant.
for i in range(4) {
for j in range(5) {
line((0, y(4, i)), (2.2, y(5, j)),
stroke: (paint: edge, thickness: .35pt))
}
}
for i in range(5) {
for j in range(5) {
line((2.2, y(5, i)), (4.4, y(5, j)),
stroke: (paint: edge, thickness: .35pt))
}
}
for i in range(5) {
for j in range(3) {
line((4.4, y(5, i)), (6.6, y(3, j)),
stroke: (paint: edge, thickness: .35pt))
}
}
for (x, count, fill, symbol) in (
(0, 4, input, $x$),
(2.2, 5, hidden, $h$),
(4.4, 5, hidden, $h$),
(6.6, 3, output, $y$),
) {
for i in range(count) {
circle((x, y(count, i)), radius: .32, fill: fill,
stroke: (paint: ink, thickness: .8pt))
content((x, y(count, i)), $ #symbol _#(i + 1) $)
}
}
content((0, 2.65), text(fill: ink, weight: "bold")[Input])
content((3.3, 3.15), text(fill: ink, weight: "bold")[Hidden layers])
content((6.6, 2.15), text(fill: ink, weight: "bold")[Output])
})
Markdown inclusion#
{{< diagram renderer="typst" src="graphics/neural-network.typ"
alt="A fully connected neural network with input, hidden and output layers"
caption="A CeTZ neural network inspired by the TikZ.net examples." >}}Useful Typst graphics packages#
These libraries all produce Typst content, so they can use the same file, fence and URL rendering paths. They remain independently versioned upstream packages rather than theme-specific chart types.
| Purpose | Package | Useful output |
|---|---|---|
| General vector drawing | CeTZ | Geometry and technical illustration |
| Data visualization | Primaviz | Charts and dashboards |
| Chemistry | Alchemist | Skeletal formulae |
| Project planning | Timeliney | Gantt charts |
| Automata theory | Finite | Finite-state automata |
| Connected diagrams | Fletcher | Nodes and arrows |
| Algorithms | Lovelace | Structured pseudocode |
| Scientific notation | Physica | Physics and tensor notation |
| Chess publishing | Staunton | Chess positions |
| Bioinformatics | Genotypst | Phylogenetic trees |
| Circuit diagrams | Circuiteria | Block circuits |
Primaviz currently provides more than 50 chart types, including grouped and stacked bars, areas, radar charts, gauges, heat maps, box and violin plots, waterfalls, funnels, treemaps, Sankey and chord diagrams, Gantt charts, timelines and dashboards. Refer to its upstream gallery for the current API and complete catalogue rather than treating this page as a second Primaviz manual.
Package examples#
Each example below is a complete standalone .typ asset. Its import pins the
package version used for the shown rendering. The Markdown block is the exact
theme shortcode that includes the source file.
CeTZ vector geometry#
Complete Typst source
#import "@preview/cetz:0.5.2"
#import cetz.draw: *
#set page(width: 80mm, height: auto, margin: 8mm)
#cetz.canvas({
circle((0, 0), radius: 1.4, stroke: 1pt + blue)
line((-1.4, 0), (1.4, 0), stroke: gray)
line((0, -1.4), (0, 1.4), stroke: gray)
line((0, 0), (1.05, 0.92), stroke: 1.2pt + red, mark: (end: ">"))
content((0.5, 0.6), [$r$], anchor: "south-east")
})
Markdown inclusion
{{< diagram renderer="typst" src="graphics/cetz-geometry.typ"
alt="A circle with coordinate axes and an arrow marking its radius" caption="A small geometric construction drawn with CeTZ." >}}Primaviz bar chart#
Complete Typst source
#import "@preview/primaviz:0.9.1": bar-chart
#set page(width: 120mm, height: auto, margin: 6mm)
#bar-chart(
(labels: ("Plan", "Build", "Ship"), values: (18, 31, 46)),
width: 105mm,
height: 55mm,
title: "Projects by phase",
)
Markdown inclusion
{{< diagram renderer="typst" src="graphics/primaviz-bars.typ"
alt="A bar chart comparing project counts for plan, build and ship phases" caption="A compact Primaviz bar chart using inline Typst data." >}}Alchemist skeletal formula#
Complete Typst source
#import "@preview/alchemist:0.2.0": *
#set page(width: 95mm, height: auto, margin: 8mm)
#skeletize({
fragment("C")
single()
fragment("C")
branch({
single(angle: 1)
fragment("O")
})
single()
fragment("C")
})
Markdown inclusion
{{< diagram renderer="typst" src="graphics/alchemist-molecule.typ"
alt="A branched skeletal chemical formula with carbon and oxygen fragments" caption="A small molecular structure composed with Alchemist." >}}Timeliney Gantt chart#
Complete Typst source
#import "@preview/timeliney:0.4.0"
#set page(width: 125mm, height: auto, margin: 6mm)
#timeliney.timeline(show-grid: true, {
import timeliney: *
headerline(group(([Week 1], 1), ([Week 2], 1), ([Week 3], 1)))
taskgroup(title: [Delivery], {
task("Design", (from: 0, to: 1))
task("Build", (from: 1, to: 2))
task("Release", (from: 2, to: 3))
})
})
Markdown inclusion
{{< diagram renderer="typst" src="graphics/timeliney-plan.typ"
alt="A three-week Gantt chart containing design, build and release tasks" caption="A compact delivery plan rendered with Timeliney." >}}Finite-state automaton#
Complete Typst source
#import "@preview/finite:0.5.1": automaton
#set page(width: 95mm, height: auto, margin: 8mm)
#automaton(
(
idle: (active: "start"),
active: (idle: "stop", active: "work"),
),
initial: "idle",
final: ("active",),
)
Markdown inclusion
{{< diagram renderer="typst" src="graphics/finite-automaton.typ"
alt="An automaton transitioning between idle and active states" caption="A two-state automaton generated from a transition dictionary." >}}Fletcher connected diagram#
Complete Typst source
#import "@preview/fletcher:0.5.8" as fletcher: diagram, node, edge
#set page(width: 105mm, height: auto, margin: 8mm)
#diagram(
node((0, 0), [Source], radius: 5mm),
edge("r", "-|>", [validate]),
node((1, 0), [Transform], radius: 7mm),
edge("r", "-|>", [publish]),
node((2, 0), [Output], radius: 5mm),
)
Markdown inclusion
{{< diagram renderer="typst" src="graphics/fletcher-flow.typ"
alt="Source, transform and output nodes connected by labeled arrows" caption="A small directed processing flow drawn with Fletcher." >}}Lovelace pseudocode#
Complete Typst source
#import "@preview/lovelace:0.3.1": pseudocode-list
#set page(width: 105mm, height: auto, margin: 8mm)
#pseudocode-list[
+ *for each* item in data
+ *if* item matches query *then*
+ append item to results
+ *end*
+ *return* results
]
Markdown inclusion
{{< diagram renderer="typst" src="graphics/lovelace-search.typ"
alt="Nested pseudocode for filtering matching items from a dataset" caption="Structured pseudocode with nesting and algorithm keywords." >}}Physica scientific notation#
Complete Typst source
#import "@preview/physica:0.9.8": curl, grad, tensor, pdv
#set page(width: 110mm, height: auto, margin: 10mm)
#set text(size: 18pt)
$ curl (grad f) = 0 $
$ tensor(T, -mu, +nu), quad pdv(f, x, y, [1, 2]) $
Markdown inclusion
{{< diagram renderer="typst" src="graphics/physica-notation.typ"
alt="Equations showing curl, gradient, tensor and partial derivative notation" caption="Scientific mathematical notation supplied by Physica." >}}Staunton chess position#
Complete Typst source
#import "@preview/staunton:2.0.0": diagram
#set page(width: 90mm, height: auto, margin: 5mm)
#diagram(
"rnbqkbnr/pp1ppppp/8/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2",
)
Markdown inclusion
{{< diagram renderer="typst" src="graphics/staunton-position.typ"
alt="A chess board showing the position after e4, c5 and knight f3" caption="A publication-ready chess position parsed from FEN." >}}Genotypst phylogenetic tree#
Complete Typst source
#import "@preview/genotypst:0.11.0": parse-newick, render-rectangular-tree
#set page(width: 120mm, height: auto, margin: 7mm)
#let tree = parse-newick(
"(('API':0.2,'Worker':0.1)'Services':0.3,'Database':0.6)Platform;"
)
#render-rectangular-tree(
tree,
width: 100mm,
height: 35mm,
align-tip-labels: true,
)
Markdown inclusion
{{< diagram renderer="typst" src="graphics/genotypst-tree.typ"
alt="A phylogenetic tree grouping API and worker services beside a database" caption="A small rectangular tree parsed from Newick data." >}}Circuiteria block circuit#
Complete Typst source
#import "@preview/circuiteria:0.2.1"
#set page(width: 110mm, height: auto, margin: 8mm)
#circuiteria.circuit({
import circuiteria: *
element.block(x: 0, y: 0, w: 2.4, h: 1.2, name: [Sensor], id: "sensor")
element.block(x: 4, y: 0, w: 2.4, h: 1.2, name: [Controller], id: "control")
wire.wire("signal", ("sensor.east", "control.west"), directed: true)
})
Markdown inclusion
{{< diagram renderer="typst" src="graphics/circuiteria-block.typ"
alt="A sensor block connected by a directed signal to a controller block" caption="A two-component block circuit with a directed wire." >}}Physica provides scientific mathematical notation; its published feature list does not include digital clock, bus or voltage timing diagrams. Circuiteria is the documented Typst package for block circuits. The theme does not claim a timing-diagram feature until an appropriate renderer or package is integrated and tested.
Package imports and reproducible builds#
Pin the package version in every source file, for example
#import "@preview/cetz:0.5.2".
The Typst CLI downloads a missing Universe package on first use and caches it. A clean online build therefore needs access to the package repository. For a fully offline build, populate the Typst package cache or vendor the dependency and import it by file path. The generated SVG cache allows an unchanged figure to build without invoking Typst, but CI should still reproduce every figure from source.
Inline and remote source#
For a small figure, use a typst fence in the same way as the D2 and DOT
fences on Diagrams and Charts. Put alt and caption in the
fence attributes. For substantial figures, prefer a source file so the full
program can be viewed, copied and tested independently as in both examples
above.
A remote source uses the same figure component:
{{< diagram renderer="typst"
url="https://example.org/figures/network.typ"
alt="A neural-network diagram"
caption="Typst source fetched during the Hugo build." >}}Remote sources are explicit build dependencies. Prefer a versioned, project-controlled URL. The source is fetched during the build; visitors only receive the rendered SVG and do not contact the source server.
Accessibility and output constraints#
- Supply an
altdescription that communicates the structure or conclusion. - Use a caption for provenance, units or interpretation.
- Avoid encoding meaning through colour alone.
- Prefer a transparent or neutral background for colour-mode compatibility.
- Keep each source to a single figure-sized page. Document-oriented or multi-page Typst output is outside this renderer’s scope.
- Check that fonts used by the source are installed in the build environment.
For automatic graph layout and browser-rendered charts, return to Diagrams and Charts. For finished SVG and bitmap assets, see Images.