How Quarkdown Handles Cross-References Between Sections, Figures, and Tables
Quarkdown treats cross-references as a first-class language feature by parsing .ref {id} calls into CrossReference AST nodes, resolving them against CrossReferenceableNode targets via the CrossReferenceResolverHook, and rendering them as localized hyperlinks during the final output phase.
Quarkdown, maintained in the iamgio/quarkdown repository, implements cross-referencing as a core compiler feature rather than a post-processing step. The system connects reference calls to target elements—such as headings, figures, and tables—through a type-safe AST resolution pipeline that handles parsing, ID matching, and localized rendering.
The Cross-Reference Architecture
Cross-references in Quarkdown operate through a three-phase pipeline: parsing, resolution, and rendering. Each phase is implemented in dedicated modules within quarkdown-core and quarkdown-html.
Parsing Cross-Reference Nodes
When the lexer encounters a reference call like .ref {my-id}, it creates a CrossReference node defined in quarkdown-core/src/main/kotlin/com/quarkdown/core/ast/quarkdown/reference/CrossReference.kt. Simultaneously, target elements that implement the CrossReferenceableNode interface—such as Heading, Figure, and Table—are parsed with their respective identification metadata.
The CrossReferenceableNode interface, defined in quarkdown-core/src/main/kotlin/com/quarkdown/core/ast/quarkdown/reference/CrossReferenceableNode.kt, establishes the contract for all referable elements:
interface CrossReferenceableNode : Node {
/** The ID used to reference this node (null ⇒ not referable). */
val referenceId: String?
}
Resolving References to Definitions
The CrossReferenceResolverHook, located in quarkdown-core/src/main/kotlin/com/quarkdown/core/context/hooks/reference/CrossReferenceResolverHook.kt, performs the matching between references and definitions. This hook walks the entire AST, collects all CrossReference nodes and all CrossReferenceableNode implementations, and matches them using the findDefinitionPair method.
If a CrossReference has a referenceId that matches a definition's referenceId, the compiler links them. If no match is found, the reference remains unresolved.
Rendering Localized Links
During the rendering phase, quarkdown-html/src/main/kotlin/com/quarkdown/rendering/html/node/QuarkdownHtmlNodeRenderer.kt receives the linked CrossReference. It queries the target definition for its linkableReferenceId and generates an HTML anchor element:
<a href="#logo"><span class="cross-reference">Figure 1</span></a>
The inner text is localized based on the target type (e.g., "Figure", "Section", "Table") and its tracked location number.
Cross-Referencing Sections, Figures, and Tables
Different element types implement CrossReferenceableNode with specific ID storage mechanisms.
Sections and Headings
In quarkdown-core/src/main/kotlin/com/quarkdown/core/ast/base/block/Heading.kt, the Heading class stores its optional ID in the customId property. When customId is non-null, the heading becomes referable:
## Installation Guide {#install}
The value install becomes the referenceId used for resolution purposes.
Figures
Figures, including ImageFigure defined in quarkdown-core/src/main/kotlin/com/quarkdown/core/ast/quarkdown/block/Figure.kt, expose referenceId directly. Authors can specify IDs using the {#id} syntax after image blocks:
 {#logo}
If a node's referenceId equals the placeholder value "_" (CrossReferenceableNode.PLACEHOLDER_REFERENCE_ID), it can be numbered without becoming a cross-reference target.
Tables
Tables implement CrossReferenceableNode in quarkdown-core/src/main/kotlin/com/quarkdown/core/ast/base/block/Table.kt. The optional {#id} after the table definition is stored in the referenceId property:
| Name | Value |
|------|-------|
| A | 10 |
{#data-table}
Numbering and Localization
When a node is marked as trackable (canTrackLocation = true), the location tracking system assigns hierarchical numbers (section 1.2, figure 2, table 3) and stores them in the node's location property. The QuarkdownHtmlNodeRenderer.kt uses this data to generate localized labels like "Section 1" or "Figure 2".
The renderer emits markup containing data-location and data-localized-kind attributes, enabling CSS-based styling of reference numbers.
Usage Examples
Referencing a Section
## Getting Started {#getting-started}
Read the section .ref {getting-started} for details.
Generated HTML:
<h2 id="getting-started">Getting Started</h2>
<p>Read the section <a href="#getting-started"><span class="cross-reference">Section 1</span></a> for details.</p>
Referencing a Figure
The logo appears in .ref {logo}.
 {#logo}
Generated HTML:
<p>The logo appears in <a href="#logo"><span class="cross-reference">Figure 1</span></a>.</p>
<figure id="logo">
<img src="icon.svg" alt="Quarkdown logo" title="Quarkdown icon">
<figcaption>Figure 1 – Quarkdown icon</figcaption>
</figure>
Referencing a Table
Data are listed in .ref {data-table}.
| Name | Value |
|------|-------|
| A | 10 |
{#data-table}
Generated HTML:
<p>Data are listed in <a href="#data-table"><span class="cross-reference">Table 1</span></a>.</p>
<table id="data-table">
<thead><tr><th>Name</th><th>Value</th></tr></thead>
<tbody>
<tr><td>A</td><td>10</td></tr>
<tr><td>B</td><td>20</td></tr>
</tbody>
<caption>Table 1 – Data overview</caption>
</table>
Standard Library Convenience
The standard library provides a concise wrapper in quarkdown-stdlib/src/main/kotlin/com/quarkdown/stdlib/Reference.kt:
fun reference(id: String) = CrossReference(id).wrappedAsValue()
This allows users to write .reference {my-id} instead of .ref {my-id}, though both invoke the same underlying resolution mechanism.
Summary
- Cross-reference pipeline: Quarkdown parses
.ref {id}intoCrossReferencenodes, matches them againstCrossReferenceableNodeimplementations viaCrossReferenceResolverHook, and renders them as localized HTML links. - Referable elements: Headings (via
customId), Figures (viareferenceId), and Tables (via{#id}syntax) all implement theCrossReferenceableNodeinterface. - ID matching: The
findDefinitionPairmethod inCrossReferenceResolverHook.ktmatches reference IDs to target definitions during compilation. - Numbering system: Trackable nodes (
LocationTrackableNode) receive automatic hierarchical numbering that feeds into localized reference labels like "Figure 1" or "Section 2.1". - Syntax options: Use
.ref {id}or the stdlib.reference {id}to create references, and{#id}after headings, images, or tables to define targets.
Frequently Asked Questions
How do I create a cross-reference target in Quarkdown?
Attach a {#id} suffix to any referable element. For headings, place it after the heading text: ## Title {#my-id}. For figures and tables, place it after the content block. This ID becomes the referenceId used by the CrossReferenceResolverHook to link references to targets.
What types of elements can be cross-referenced?
According to the source code, any element implementing CrossReferenceableNode can be targeted. This includes Heading (sections), Figure (including ImageFigure), and Table. The interface requires a referenceId property that uniquely identifies the element within the document.
How does Quarkdown handle automatic numbering for cross-references?
When a node has canTrackLocation = true (implementing LocationTrackableNode), the location tracking hook assigns hierarchical numbers during compilation. The QuarkdownHtmlNodeRenderer.kt queries this location data to generate localized labels like "Figure 1" or "Table 2", inserting them into the rendered HTML alongside the hyperlink.
What is the difference between .ref and .reference in Quarkdown?
There is no functional difference. .ref is the core syntax that creates a CrossReference node. .reference is a convenience wrapper provided by the standard library in Reference.kt that calls the same underlying constructor. Both are resolved by CrossReferenceResolverHook and rendered identically.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →