rheo-context
When Rheo compiles a project, it prepends a small binding to the top of every vertebra:
#let rheo-context = (
handle: "chapters:intro",
spine: (
(title: "Introduction", handle: "intro", path: "content/intro.typ", children: ()),
(title: "Chapters", handle: none, path: none, children: (
(title: "Chapter Introduction", handle: "chapters:intro", path: "content/chapters/intro.typ", children: ()),
// ... one node per vertebra or group, nested to arbitrary depth
)),
),
target: "html", // the output format; absent for PDF
)This exposes Rheo’s view of the project to your Typst code: which file this is, and every file in the spine alongside it. Rheo injects a distinct literal into each file, so every file sees its own values.
rheo-context is an ordinary top-level #let binding — a plain dictionary. It is not a Typst context value, and reading it does not require the #context keyword. Any Typst code in the file can use it directly:
This page's handle is #rheo-context.handle.Fields
The shape of rheo-context is designed to be extensible — more fields may be added in future versions.
| Field | Description |
|---|---|
handle | This file’s handle — its :-separated identifier, the same handle used for cross-file links. |
spine | A tree (a forest — an array of top-level nodes) mirroring the project’s directory and section structure. Each node is a dictionary with four fields:
A group node is not itself clickable — there’s nothing to link to — but its title still labels the section. Walk |
spine-flat | A flat list of every clickable vertebra in spine order (group nodes are omitted). Each entry is a dictionary with three fields:
Use this where a flat sequence is simpler than walking the tree — prev/next navigation, page counts, and the like. |
target | The output format Rheo is compiling for — "html" or "epub". It is absent for PDF, where Typst’s native target() returns "paged". The value is the same for every vertebra. In authored files, prefer Typst’s own target() (which Rheo polyfills to return this value) over reading the field directly — target() works everywhere, e.g. #if target() == "epub" [ ... ]. |
Example: a table of contents
Because spine-flat lists every clickable vertebra in order, a template or package can build a flat table of contents from it directly:
#for entry in rheo-context.spine-flat [
- #link(label(entry.handle))[#entry.title]
]Each file receives the same spine-flat, so this produces a consistent list across the whole project, while rheo-context.handle lets a template highlight the current page.
A nested table of contents — one that reflects the project’s directory and section groups — has to walk spine instead, recursing into children:
#let toc(nodes) = {
for node in nodes [
- #if node.handle != none [
#link(label(node.handle))[#node.title]
] else [
#node.title
]
#if node.children.len() > 0 [
#toc(node.children)
]
]
}
#toc(rheo-context.spine)