Rheo

rheo-context

When Rheo compiles a project, it prepends a small binding to the top of every vertebra:

#let rheo-context() = (
  handle: "chapters:intro",
  metadata-of: rheo-metadata,
  ..sys.inputs.rheo-context,
)

The rheo-context dictionary exposes Rheo’s view of the project to your Typst code: which file this is, every file in the spine alongside it, and a way of reading any of their resolved document metadata live. Most of this is global context that can also be read in the format-global sys.inputs.rheo-context, but handle and metadata-of are file-specific. (Rheo injects a distinct binding into each file, so every file sees its own handle.)

rheo-context() is a zero-arg function that returns a dictionary. It is not a Typst context value, and you can read most of it with no #context keyword. The one exception is metadata-of (see below).

This page's handle is #rheo-context().handle.

Fields

The shape of rheo-context is designed to be extensible.

FieldDescription
handleThis file’s handle — its :-separated identifier, the same handle used for cross-file links.
spine

A tree (an array of top-level nodes) mirroring the project’s directory and section structure. Each node is a dictionary with four fields:

  • title — the node’s title, always path-derived (the file or directory name, prettified), regardless of any #set document(title: ...) the vertebra itself authors.
  • handle — the vertebra’s handle, or none for a group node (a directory or section with no landing file).
  • path — the vertebra’s path relative to the project root, or none for a group node.
  • children — an array of child nodes, recursing to arbitrary depth (empty for a node with no descendants).

You can use a title or handle to derive the link to a vertebra. Walk children to build nested navigation.

spine-flatA flat list of every clickable vertebra in spine order (group nodes are omitted). Each entry is a dictionary with three fields: handle, path, and a title that is always path-derived.
metadata-ofA function value, (handle) => dict, reading another vertebra’s #set document(...) fields live off the compiled bundle. See below for more information.
targetThe output format Rheo is compiling for, such as "html" or "epub". It is absent for PDF, where Typst’s native target() returns "paged". In authored files, prefer Typst’s own target() (which Rheo polyfills to return this value) over reading this field directly, as target() works everywhere, e.g. #if target() == "epub" [ ... ].
extThe output file extension for the format being compiled, such as "html" for HTML or "xhtml" for EPUB. 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 uses to build cross-vertebra links.
rheo-versionThe compiling Rheo binary’s own semver string, e.g. "0.6.0". Unlike target/ext, it is always present, on every format, including PDF. A package’s own Typst code can read it to enforce a minimum Rheo and fail with a clear message of its own choosing, rather than breaking obscurely against a build-time surface an older Rheo simply doesn’t have. Its absence is the signal to treat as “older than the release that added this field. (A package can also declare a floor in its own manifest: see Packages.)
reset-footnotesThe resolved per-format footnote-reset toggle, as a plain bool. Unlike target/ext, it is always present, on every format including PDF, because it’s a resolved default rather than something format-gated. This only ever actually takes effect on HTML/EPUB regardless of its value, as a combined PDF has no per-page boundary to reset at. Set reset_footnotes = false under [html]/[epub] in rheo.toml (default true) if you’d rather footnotes accumulate across the whole bundle instead of restarting on every page.

Reading vertebra metadata

Every vertebra compiled under a per-page layout such as HTML or EPUB publishes a small, hidden beacon after its own body as Typst metadata. You can use the metadata-of function in rheo-context to query this metadata, meaning that you can query attributes such as the document title and date of other vertebrae from any point of the spine, which is useful for building blog feeds or other site listings. Because this is a live query using Typst introspection, it needs the #context keyword:

// This vertebra's own metadata, looked up by its own handle.
#context {
  let me = (rheo-context().metadata-of)(rheo-context().handle)
  if "date" in me [
    Published #me.date.display("[year]-[month]-[day]").
  ]
}

The are a few important nuances to the metadata-of function that you should keep in mind if you intend to use it:

Example: building a blog feed

You can build an index of all pages in your project by mapping spine-flat through metadata-of. Note that we need the #context keyword because we are using the metadata-of function:

#context {
  let meta-of = rheo-context().metadata-of
  for e in rheo-context().spine-flat {
    let m = meta-of(e.handle)
    let when = m.at("date", default: none)
    if when != none [
      - #link(label(e.handle))[#m.at("title", default: e.title)] --- #when.display()
    ]
  }
}

You can also use rheo-context().spine to keep structure when building, for example, a table of contents:

#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)