7 Configuration and Scoping
Setting a default once instead of repeating it on every diagram() call has three different mechanisms in typograph, because #set — Typst’s own scoped-default syntax — is reserved for element functions, and diagram isn’t one. #set typ.diagram(..) is a hard compile error, and no package can change that. The three mechanisms cover the same ground #set would have:
7.1 1. diagram.with(..) — the idiomatic default
Typst’s own partial application, free at runtime:
#let diagram = typ.diagram.with(theme: my-theme, scale: 0.8cm, font-size: 9pt, port-spacing: 8pt)Bind this once near the top of a document (or a project’s shared theme file) and call diagram({ .. }) everywhere after. A let-shadowed rebind later in the document gives scoped defaults for free, following normal Typst scoping — this is the right default for anything the author of the document controls directly.
7.2 2. #set text(..) — already works
Node and edge labels are set in 1em and inherit the surrounding text style, so #set text(size: 9pt) (or any other text property) affects diagram labels exactly as it affects running prose, with no package involvement at all.
7.3 3. config() — for call sites you don’t control
typ.config(
body,
scale: .., scale-edges: .., port-spacing: .., font-size: .., grid: .., inset: ..,
anchor: .., math-axis: .., baseline: ..,
node-styles: .., edge-styles: ..,
)config() supplies these non-theme defaults to every diagram() inside body that doesn’t override the same argument itself. It exists for the one case .with() can’t reach: diagrams inside content you don’t directly control the call sites of — an #included chapter, or a template applied #show: template.with(..) over a whole document:
#show: typ.config.with(font-size: 9pt, scale: 0.8cm, port-spacing: 8pt) // whole documentor scoped to one section:
#typ.config(font-size: 7pt)[
... diagrams here are small ...
]Scopes nest and revert: a nested config() overrides its parent for its own body, then reverts cleanly once that body ends, and a diagram’s own explicit argument always wins over any configured default, at any nesting depth.
Omit a key or pass auto when a nested config() should preserve its parent’s value. There are two exceptions: font-size: auto restores the surrounding document’s text size, and baseline: auto restores the calculated math-axis baseline. To reset another key to its package default, supply that concrete value, for example scale: 1, grid: false, or port-spacing: 7pt. node-styles: auto and edge-styles: auto leave inherited styles unchanged.
#typ.config(font-size: 8pt, scale: 0.6cm)[
Small throughout this scope:
#diagram({ typ.edge(typ.node(0, 0, label: $alpha$), typ.node(1, 0)) })
#typ.config(font-size: 13pt, scale: 1cm)[
A nested scope overrides it:
#diagram({ typ.edge(typ.node(0, 0, label: $alpha$), typ.node(1, 0)) })
]
...and reverts afterwards:
#diagram({ typ.edge(typ.node(0, 0, label: $alpha$), typ.node(1, 0)) })
]node-styles/edge-styles are the one place config() does more than plain override: they merge with whatever the package/theme already contributed rather than replacing it wholesale — node-styles merges one level by kind, edge-styles merges shallowly — and a diagram’s own node-styles:/edge-styles: then merges on top of the configured ones, the same “later wins, per key” rule as everywhere else in the style system (see Core Concepts).
The merge does not recurse into individual style values: replacing inset replaces its whole side dictionary, and replacing "shape.parts" replaces the entire part collection. Within each layer min-size expands to both axes before merging, so a later min-size beats an earlier min-width.
theme is deliberately not a config() option, for the same reason it isn’t a plain default anywhere else: appearance is always bound explicitly with diagram.with(theme: ..), never inherited implicitly from an enclosing scope.
Implementation note, for anyone curious why this needed state at all when .with() didn’t: config() is a stack held in document state, so nested scopes revert correctly, read during the diagram’s own contextual layout pass — it doesn’t add a separate pass of its own. See Performance Model for the renderer’s actual passes.
7.3.1 Measuring contextual diagrams
With Typst 0.15.1, measuring the same contextual diagram value that is also rendered elsewhere can observe the rendered occurrence’s configuration scope. For reliable size calculations, pass the relevant options explicitly to the diagram being measured, or measure a fragment that is not also laid out in another scope. This behavior also occurs with numeric-only diagrams; it is not specific to relative positioning.