The anchor primitive

anchor(kind, payload)
marks the current location with a namespaced, queryable piece of data. kind namespaces the anchor so two unrelated pieces of code picking the same id scheme never collide — prefix it with something specific to what you’re building ("glossary-term", "figure-list-entry"). payload is whatever you need back later — entirely opaque to contexture itself. Deliberately renders nothing on its own beyond the metadata: emitting it and deciding how (or whether) to render content around it are two different jobs, left to the caller, exactly as term() (the quickstart) does both explicitly.
anchors(kind)
every anchor of that kind, in document order, from anywhere in the bundle — including a document other than the one this is called from, which is the entire point. Must be called from within a context.

Re-emitting stored content: strip-labels

A term’s body can be arbitrary content, including a labelled figure — and re-emitting that figure verbatim into the glossary would otherwise plant a second copy of its label, which Typst rejects outright as a duplicate. strip-labels(node) reconstructs node with every label removed, and — specifically for a figure, a labelled block equation, or a heading — pins the real, already-resolved number of the true original onto the copy’s own numbering, read directly via a fresh query() of that label. The copy shows the same number as the original; only the original stays a real, referenceable target.

#import "../../lib.typ": *

// Same idea as bundle-glossary-basics.typ, but one term's body is itself
// a labelled figure --- re-emitting it verbatim in the glossary would
// otherwise plant a second copy of <tab-doses>, which Typst rejects as a
// duplicate label. strip-labels() drops the label from the copy while
// pinning the real, original "Figure 1" number onto it, read directly
// off the true instance via a query --- so both copies show the same
// number, and only one of them is a real, referenceable target.

#let term(id, body) = {
  anchor("demo-term", (id: id, body: body))
  body
}

#let render-glossary() = context {
  for h in anchors("demo-term") [
    *#h.value.id* (p. #h.location().page()):
    #strip-labels(h.value.body)
    #v(0.5em)
  ]
}

#let glossary = satellite("glossary", render: () => render-glossary())

#show: bundle.with(
  template: body => {
    set page(width: 16.6cm, height: auto, margin: 12pt)
    set text(size: 10.5pt)
    body
  },
  documents: (glossary,),
)

= Methods

#term("doses")[
  #figure(
    table(columns: 2, [*Arm*], [*Dose*], [A], [10mg], [B], [Placebo]),
    caption: [Study drug doses.],
  ) <tab-doses>
]

As shown in @tab-doses, two arms were compared.

Both the manuscript and the glossary show “Table 1” — the genuine, resolved number, not a guess:

manuscript.pdf
glossary.pdf

Structural utilities

collect-metadata(body, tag) and collect-labels(body) are the structural building blocks strip-labels and anchor/anchors themselves are built from — a plain walk of an in-memory content tree, no context or layout involved, exposed directly for code that needs to inspect content it’s holding but hasn’t (or may never) placed into any document this compile. is-blank(body) answers “does this contain any real text at all” (handy for flagging an accidentally empty call to something like term()); is-textual(body) answers “is this safe to wrap in literal quotation marks”, i.e. free of any figure, table, or block equation that a stray quote mark would otherwise float above.

← QuickstartList of figures →