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.

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:

![Quarkdown logo](icon.svg "The icon") {#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}.

![Quarkdown logo](icon.svg "Quarkdown icon") {#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} into CrossReference nodes, matches them against CrossReferenceableNode implementations via CrossReferenceResolverHook, and renders them as localized HTML links.
  • Referable elements: Headings (via customId), Figures (via referenceId), and Tables (via {#id} syntax) all implement the CrossReferenceableNode interface.
  • ID matching: The findDefinitionPair method in CrossReferenceResolverHook.kt matches 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →