rheo-context
When Rheo compiles a project, it prepends a small binding to the top of every vertebra:
#let rheo-context() = (
handle: "chapters:intro",
..sys.inputs.rheo-context,
)Only the handle is per-file; it is composed with the format-global values (spine, spine-flat, target, ext) spread from sys.inputs.rheo-context. 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 binding into each file, so every file sees its own values.
rheo-context() is a zero-arg function returning a dictionary; call it to read Rheo’s view. It is not a Typst context value, and it needs no #context keyword (sys.inputs is non-contextual). 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 five 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 four fields:
Reach for it when a flat sequence beats walking the tree — prev/next navigation, page counts, and the like. A file reads its own metadata by finding its |
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" [ ... ]. |
ext | The output file extension for the format being compiled — "html" for HTML, "xhtml" for EPUB — sourced from the format plugin. Like target, it is format-global (the same for every vertebra) and absent for PDF, where there are no per-page files. This is the value Rheo itself uses to build depth-relative cross-vertebra link hrefs. |
Vertebra metadata
Every vertebra in spine and spine-flat carries a metadata dictionary harvested from its #set document(...) rule. It is format-global — the same values are present across PDF, HTML, and EPUB. Each key appears only when the corresponding argument is set; a file with no #set document(...) gets an empty dictionary.
title— the document title, as plain text.author— theauthorargument (a string, or an array of strings).keywords— thekeywordsargument (a string, or an array of strings).date— the document date, as a real Typstdatetime(not a string), so you can call.display(),.year(), and the like.
The date key is present only for a static date — #set document(date: datetime(year: ..., ...)). It is absent when the date is none, auto, datetime.today(), or a partial datetime (the same resolution rules the Atom feed uses for entry timestamps).
// This file's own metadata, looked up by handle.
#let me = rheo-context().spine-flat.find(v => v.handle == rheo-context().handle)
#if "date" in me.metadata [
Published #me.metadata.date.display("[year]-[month]-[day]").
]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)