v0.4.0 is out — data-driven components and integrated graphics. Read the notes
projectious·theme
On this page

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#

SourceAuthor writesBuild behavior
InlineA fenced typst blockThe pre-renderer discovers, compiles and inlines it
Project fileA .typ asset and diagram shortcodeHugo inlines its cached SVG
URLA versioned URL in diagramHugo 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.

Karl’s Picture from the CeTZ 0.5.2 gallery.TYPST source rendered at build time

Complete Typst source#

The source is the complete, unmodified CeTZ gallery example, licensed with CeTZ under LGPL-3.0.

graphics/karls-picture.typ
#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#

md
{{< 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.

A CeTZ neural network inspired by the TikZ.net examples.TYPST source rendered at build time

Complete Typst source#

graphics/neural-network.typ
#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#

md
{{< 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.

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#

A small geometric construction drawn with CeTZ.

Complete Typst source

graphics/cetz-geometry.typ
#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

Markdown
{{< 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#

A compact Primaviz bar chart using inline Typst data.

Complete Typst source

graphics/primaviz-bars.typ
#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

Markdown
{{< 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#

A small molecular structure composed with Alchemist.

Complete Typst source

graphics/alchemist-molecule.typ
#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

Markdown
{{< 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#

A compact delivery plan rendered with Timeliney.

Complete Typst source

graphics/timeliney-plan.typ
#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

Markdown
{{< 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#

A two-state automaton generated from a transition dictionary.

Complete Typst source

graphics/finite-automaton.typ
#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

Markdown
{{< 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#

A small directed processing flow drawn with Fletcher.

Complete Typst source

graphics/fletcher-flow.typ
#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

Markdown
{{< 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#

Structured pseudocode with nesting and algorithm keywords.

Complete Typst source

graphics/lovelace-search.typ
#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

Markdown
{{< 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#

Scientific mathematical notation supplied by Physica.

Complete Typst source

graphics/physica-notation.typ
#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

Markdown
{{< 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#

A publication-ready chess position parsed from FEN.

Complete Typst source

graphics/staunton-position.typ
#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

Markdown
{{< 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#

A small rectangular tree parsed from Newick data.

Complete Typst source

graphics/genotypst-tree.typ
#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

Markdown
{{< 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#

A two-component block circuit with a directed wire.

Complete Typst source

graphics/circuiteria-block.typ
#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

Markdown
{{< 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:

md
{{< 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 alt description 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.

Edit this page Updated Aug 26, 2026