File-ref
{% file-ref %} is the path-based sibling of xref and expand: where those resolve a registered entity by id, file-ref points at an arbitrary project file by path. The inline form renders an <a> to the file's canonical GitHub URL; the preview="drawer" form hoists a drawer containing the file's snippet plus a "View source on GitHub →" footer link, leaving an inline link in prose that opens it (SPEC-078).
File-ref requires a repoUrl (and optional repoBranch) on the site config so the canonical GitHub URL can be built. See repoUrl in the configuration reference.
Linking to a file
The minimal call points at a file by path (project-root-relative, same sandbox snippet uses). The link text defaults to the filename:
See {% file-ref path="package.json" /%} for the project metadata.<p>
See
<span data-rune="file-ref">
<meta data-field="file-ref-path" content="package.json">
<meta data-field="file-ref-lines" content="">
<meta data-field="file-ref-label" content="">
<meta data-field="file-ref-preview" content="">
<meta data-field="file-ref-symbol" content="">
<meta data-field="file-ref-match" content="">
<meta data-field="file-ref-occurrence" content="">
<meta data-field="file-ref-extent" content="">
<meta data-field="file-ref-until" content="">
<meta data-field="file-ref-through" content="">
<meta data-field="file-ref-doc" content="">
<meta data-field="file-ref-reindent" content="">
<meta data-field="__file-ref-sentinel" content="true">
<a data-name="link">package.json</a>
</span>
for the project metadata.
</p>See package.json for the project metadata.
<p>
See
<span class="rf-file-ref" data-rune="file-ref" data-density="full">
<a href="https://github.com/refrakt-md/refrakt/blob/main/package.json">package.json</a>
</span>
for the project metadata.
</p>Pass an explicit label when you're referring to a symbol inside the file rather than the file itself — that's the usual case:
See {% file-ref path="packages/types/src/theme.ts" symbol="SiteThemeConfig" label="SiteThemeConfig" /%}
for the shape.
Anchoring to a line range
lines accepts a single line ("19") or a range ("19-49"). Drives both the GitHub #L19-L49 anchor and the snippet slice when previewing.
{% file-ref path="CHANGELOG.md" lines="1-20" label="the latest release notes" /%}
The href becomes https://github.com/{owner}/{repo}/blob/{repoBranch}/CHANGELOG.md#L1-L20 — clicking jumps straight to the highlighted range on GitHub.
The example deliberately points at a file with no symbols in it, because that is when a line range is the right tool. When the target is a declaration, anchor by name instead: a line range into a source file is a coordinate that drifts, and a file-ref labelled with a symbol is the case where that hurts most — the label keeps claiming one thing while the drawer shows whatever now occupies those lines.
Anchoring by name
A line range is a coordinate into a file nobody promised to hold still, and a file-ref labelled with a symbol is the case where that hurts most: the label claims one thing and the drawer shows whatever now occupies those lines. Name the declaration instead:
{% file-ref path="packages/types/src/config.ts" symbol="SiteConfig" preview="drawer" /%}
The drawer body is resolved from the current file, so it follows the symbol as it moves and fails loudly if the symbol is gone. match, extent, until, through, doc and reindent all work as they do on snippet.
The GitHub link loses its line fragment under an anchor. The inline <a> is built before the file is read, so the resolved range is not yet known — an anchored file-ref links to the whole file, and the drawer shows the exact region. Keep lines= when the deep link matters more than drift resistance.
Preview drawer
preview="drawer" is where file-ref earns its keep for docs. The inline link stays in prose, and clicking opens a hoisted drawer containing the file's snippet:
See {% file-ref path="packages/types/src/config.ts" symbol="SiteConfig" label="SiteConfig" preview="drawer" reviewed="bf51213fda87:835cf77d7ae8" /%} for the full shape.<p>
See
<span data-rune="file-ref">
<meta data-field="file-ref-path" content="packages/types/src/config.ts">
<meta data-field="file-ref-lines" content="">
<meta data-field="file-ref-label" content="SiteConfig">
<meta data-field="file-ref-preview" content="drawer">
<meta data-field="file-ref-symbol" content="SiteConfig">
<meta data-field="file-ref-match" content="">
<meta data-field="file-ref-occurrence" content="">
<meta data-field="file-ref-extent" content="">
<meta data-field="file-ref-until" content="">
<meta data-field="file-ref-through" content="">
<meta data-field="file-ref-doc" content="">
<meta data-field="file-ref-reindent" content="">
<meta data-field="__file-ref-sentinel" content="true">
<a data-name="link">SiteConfig</a>
</span>
for the full shape.
</p>See SiteConfig for the full shape.
<p>
See
<span class="rf-file-ref" data-rune="file-ref" data-density="full">
<a href="#drawer-packages-types-src-config.ts" aria-controls="drawer-packages-types-src-config.ts" aria-expanded="false" data-target-type="drawer">SiteConfig</a>
</span>
for the full shape.
</p>- The inline
<a>getshref="#drawer-{slug}",aria-controls,aria-expanded— the drawer behavior layer flipsaria-expandedon open. - The drawer body is a
{% snippet path=… lines=… /%}of the file. Syntax-highlighted via the standard highlight pass. - The drawer chrome footer holds a
View source on GitHub →link with the line-range anchor. - Per-page dedup: N mentions of the same
path+linescollapse to one hoisted drawer.
Without JS
The inline href="#drawer-{slug}" is a real in-page anchor that scrolls to the hoisted drawer's visible block fallback (the same SSR shape any {% drawer %} produces). Readers without JS get the snippet inline instead of behind chrome — graceful degradation.
Nested-preview caveat. A {% file-ref … preview="drawer" /%} placed inside another drawer's body or footer still hoists, producing a drawer-from-within-a-drawer shape. Supported but discouraged; the build emits an info-level note when detected. Same rule as the xref preview drawer.
Label conventions
The filename default (e.g. theme.ts) is conservative — when the file-ref refers to a symbol inside the file, name the symbol with symbol= and pass a matching label. The two do different jobs: symbol decides what is quoted, label decides what the link reads as.
{# Refers to the file: filename is fine #}
{% file-ref path="package.json" /%}
{# Refers to a symbol in the file: name it, and pass a label #}
{% file-ref path="packages/types/src/config.ts" symbol="SiteConfig" label="SiteConfig" /%}
Attributes
| Attribute | Type | Required | Description |
|---|---|---|---|
path | string | ✓ | Project-root-relative file path. Same sandbox as snippet — absolute paths, traversal escapes, and out-of-root symlinks are rejected. |
lines | string | — | Line range. `"42-58"` (range), `"42"` (single line). Drives both the GitHub anchor (#L42-L58) and the drawer-body snippet slice when `preview="drawer"`. A coordinate into a file nobody promised to hold still — prefer `symbol` or `match`. Mutually exclusive with both. |
symbol | string | — | Name a declaration instead of a line range: `symbol="SiteConfig"`. The drawer body shows the resolved region, recomputed from the current file rather than frozen at authoring time. Note the trade: the GitHub link loses its `#L` fragment and points at the whole file, because the range is not known until the file is read, which happens after the link is built. Includes the doc comment by default. |
match | string | — | Raw regex anchor, applied per line. The general form; `symbol` is sugar over it. |
occurrence | number | — | Which match to take when the anchor is ambiguous, 1-based. An escape hatch rather than a naming form; out of range refuses rather than clamping. |
extent | "auto" | "dedent" | "section" | "paired" | — | How far the slice runs: `auto` (delimiters), `dedent` (indentation), `section` (next sibling), `paired` (token pair). Never inferred from the file. |
until | string | — | Regex ending the extent, excluding the matching line. Overrides `extent`. |
through | string | — | As `until`, but including the matching line. |
doc | boolean | — | Include the preceding doc comment. Defaults on for `symbol`, off for `match`. Annotations attach regardless. |
reindent | boolean | — | Strip the drawer slice's common leading whitespace. Defaults on for anchors, off for `lines`. |
reviewed | string | — | Records that a human read this version of the slice and confirmed the prose around it matched. Written by `refrakt snippet review`, never by hand. |
label | string | — | Display text for the inline link. Defaults to the filename (e.g. `token-contract.ts`). Pass an explicit label when the file-ref refers to a symbol within the file rather than the file itself. |
preview | "drawer" | — | Preview target. `"drawer"` hoists a drawer with the file's snippet + a GitHub footer link, leaving an inline link at the call site that opens it. Absent → no preview, just the inline link. |
Universal attributes
bg
The background layer (SPEC-088): image, video, gradient, flat overlay wash and legibility scrim, in a single injected layer behind the rune's content.
| Attribute | Type | Required | Description |
|---|---|---|---|
bg | string | — | Background preset applied to this block |
bg-from | string | — | Gradient start colour — a semantic token name (→ var(--rf-color-*)) |
bg-gradient | "to-t" | "to-b" | "to-l" | "to-r" | "to-tr" | "to-br" | "to-bl" | "to-tl" | — | Gradient direction (bounded named set) |
bg-gradient-type | "linear" | "radial" | "conic" | — | Gradient type |
bg-to | string | — | Gradient end colour — a semantic token name |
bg-via | string | — | Optional middle gradient stop — a semantic token name |
scrim | "top" | "bottom" | "left" | "right" | "none" | — | Scrim direction (heaviest edge); presence turns the scrim on, "none" opts out of the default cover scrim |
scrim-blur | "none" | "sm" | "md" | "lg" | — | Frost scrim blur amount |
scrim-strength | "sm" | "md" | "lg" | — | Gradient scrim strength |
scrim-tone | "dark" | "light" | — | Whether the scrim darkens (for light text) or lightens (for dark text) |
scrim-type | "gradient" | "frost" | — | Scrim treatment: gradient (default) or frost (backdrop blur) |
elevation
The chrome/depth ladder (SPEC-107). The skin maps each rung to a chrome bundle by attribute, so there is no BEM class.
| Attribute | Type | Required | Description |
|---|---|---|---|
elevation | "sunken" | "flush" | "flat" | "raised" | "floating" | "overlay" | "none" | "sm" | "md" | "lg" | — | Surface depth on the SPEC-107 ladder (sunken→overlay); none/sm/md/lg are deprecated aliases |
inset
Internal padding override.
| Attribute | Type | Required | Description |
|---|---|---|---|
inset | "flush" | "tight" | "default" | "loose" | "breathe" | — | Inner padding of this block |
motion
Scroll-reveal entrance (SPEC-105). The author declares the character, the theme owns the choreography, a behaviour owns the timing.
| Attribute | Type | Required | Description |
|---|---|---|---|
reveal | "none" | "fade" | "slide" | "scale" | "blur" | — | Scroll-reveal entrance character (none|fade|slide|scale|blur); the theme owns the choreography |
stagger | boolean | — | Cascade this block's items in as it reveals (no-op on single-child runes) |
spacing
Block-level rhythm override.
| Attribute | Type | Required | Description |
|---|---|---|---|
spacing | "flush" | "tight" | "default" | "loose" | "breathe" | — | Vertical spacing above and below this block |
substrate
Generated pattern fills (SPEC-087). Markers only — the engine sets the attributes and cell/opacity custom properties, CSS draws the pattern.
| Attribute | Type | Required | Description |
|---|---|---|---|
substrate | "dots" | "grid" | "lines" | "cross" | "checker" | "none" | — | Generated surface pattern |
substrate-fill | "inherit" | "inset" | — | Surface fill the pattern sits on (full colour stays with tint) |
substrate-opacity | "sm" | "md" | "lg" | — | Pattern ink strength |
substrate-size | "sm" | "md" | "lg" | — | Pattern cell size |
substrate-target | "self" | "media" | — | Which surface the pattern fills (overrides the rune/theme default) |
tint
Per-rune colour override (SPEC-053): a named tint from the theme registry, with inline per-token overrides layered on top.
| Attribute | Type | Required | Description |
|---|---|---|---|
tint | string | — | Color tint preset applied to this block |
tint-mode | "auto" | "dark" | "light" | — | Whether the tint adapts to auto, dark, or light mode |
width
The track a block rune occupies.
| Attribute | Type | Required | Description |
|---|---|---|---|
width | "compact" | "narrow" | "content" | "wide" | "full" | — | Maximum width constraint for this block |
| Not available | Why |
|---|---|
| dropcap, reading | this rune declares no prose body |
| frame | this rune declares neither a `frameTarget` nor a media section |
| prominence | this rune has no page-section header |
Site configuration
The GitHub URL is built from SiteConfig.repoUrl + SiteConfig.repoBranch. Both live in refrakt.config.json:
{
"site": {
"contentDir": "./content",
"theme": { "package": "@refrakt-md/lumina" },
"repoUrl": "https://github.com/owner/repo",
"repoBranch": "main"
}
}
| Field | Default | Notes |
|---|---|---|
repoUrl | — | Canonical repo URL. When absent, the inline link has no href (or falls back to an in-page snippet anchor when one exists on the page) and a one-time per-page build warning fires. |
repoBranch | "main" | Accepts any git ref — branch / tag / commit SHA. Use a SHA for archival URLs that won't drift when the file is edited. |
Output contract
Without preview, the inline form:
<span class="rf-file-ref" data-rune="file-ref">
<a href="https://github.com/.../blob/main/path/to/file.ts#L42-L58">label</a>
</span>
With preview="drawer":
<span class="rf-file-ref" data-rune="file-ref">
<a href="#drawer-path-to-file-ts-L42-L58"
aria-controls="drawer-path-to-file-ts-L42-L58"
aria-expanded="false"
data-target-type="drawer">label</a>
</span>
<!-- ... and at the page root: -->
<section class="rf-drawer" id="drawer-path-to-file-ts-L42-L58" data-rune="drawer">
<header class="rf-drawer__header">…</header>
<div class="rf-drawer__body">
<pre data-language="typescript" data-source="path/to/file.ts" data-lines="42-58"><code>…</code></pre>
</div>
<footer class="rf-drawer__footer">
<a href="https://github.com/.../#L42-L58">View source on GitHub →</a>
</footer>
</section>
See also
- xref — id-based sibling. Same
preview="drawer"attribute, different body shape (entity expand vs file snippet). - expand — id-based content-inlining counterpart.
xref preview="drawer"is the on-demand reveal;expandis the in-flow substitution. - snippet — the block-level file embedding rune.
file-refuses snippet's sandbox + rendering for its drawer body. - drawer — the chrome the preview hoists into. The same
<section class="rf-drawer">shape author-declared drawers use.
SiteConfig
/** Per-site configuration. A project may declare a single site (via `site`) or
* multiple named sites (via `sites: { name: SiteConfig }`).
* Only `contentDir` and `theme` are strictly required for the site to load. */
export interface SiteConfig {
/** Path to content directory, relative to project root */
contentDir: string;
/** Active theme — accepts either a package name string (legacy shorthand)
* or a full {@link SiteThemeConfig} with presets, token overrides, mode
* overlays, and `colorScheme`. */
theme: string | SiteThemeConfig;
/** Documentation-only adapter hint (`svelte`, `astro`, `next`, `nuxt`, `eleventy`, `html`).
* No adapter reads or validates this field today; it serves as an in-config record of
* which adapter the site is intended for. Slated for removal in v1.0. */
target?: string;
/** Component overrides — maps typeof names to relative paths of replacement components */
overrides?: Record<string, string>;
/** Route-to-layout mapping rules, evaluated in order (first match wins) */
routeRules?: RouteRule[];
/** Declarative entity → page routes (SPEC-069). Each rule generates one page
* per registered entity matching `type` + optional `filter`. */
entityRoutes?: EntityRoute[];
/** Whether to render the site-wide search UI — the header search button
* plus the Cmd/Ctrl+K dialog, powered by Pagefind. Defaults to `true`.
*
* Set to `false` to omit the search chrome entirely: the `search` client
* behavior is never initialized and the trigger button is not rendered in
* any layout. Leave it enabled (the default) to keep the button; note that
* results only appear once a Pagefind index has been built and served at
* `/pagefind/` — see the "Setting up search" guide. Until then the button
* opens to a graceful "Search is not available." message. */
search?: boolean;
/** Syntax highlighting options */
highlight?: {
theme?: string | { light: string; dark: string };
};
/** Custom icon SVGs — merged into the theme's global icon group */
icons?: Record<string, string>;
/** Plugins to merge into this site's ThemeConfig (runes, layouts, hooks, etc.) */
plugins?: string[];
/** SPEC-035 — active locale (BCP 47, e.g. `'de'`). Selects first-party and
* plugin translation bundles and drives locale-aware formatting. Defaults to
* `'en'` (zero-config English). */
locale?: string;
/** SPEC-035 — site-level translation overrides, keyed by the auto-derived
* i18n key (`{scope}.{block}.{ref}`). Highest precedence — these win over
* first-party and plugin bundles (Decision D5). Values are `string` or a
* CLDR plural-category map. */
strings?: Record<string, string | Record<string, string>>;
/** Project-level tint presets — merged after theme tints (last wins).
* Field shape matches `TintDefinition` from `@refrakt-md/transform` per
* SPEC-053; `Record<string, unknown>` is used here to avoid a cross-package
* type dependency in `@refrakt-md/types`. */
tints?: Record<string, Record<string, unknown>>;
/** Project-level background presets — merged after theme backgrounds (last wins) */
backgrounds?: Record<string, Record<string, unknown>>;
/** Sandbox configuration. The runtime program-source directory (SPEC-104). */
sandbox?: {
/** Directory of sandbox program sources, scanned at build time
* (ADR-022). Relative to the project root. */
dir?: string;
/** @deprecated Renamed to `dir` (ADR-022). Still accepted as input — the
* config normalizer coalesces it into `dir` and emits a deprecation
* warning — but will be removed in a future release. */
examplesDir?: string;
};
/** Content validation (SPEC-132). The build runs `Markdoc.validate()` over
* every page and reports findings as pipeline warnings; nothing is rendered
* into the page. Omit for the documented defaults.
*
* `critical` findings are reported whatever is set here — they mean the
* document could not be understood, which is not a matter of preference
* (D11). Suppression is per-site and per-error-id only: there is
* deliberately no per-page or per-tag switch, because that granularity
* silences the one call site that revealed a real bug (D5). */
validation?: {
/** `false` stops reporting every suppressible finding for this site. */
enabled?: boolean;
/** Replace the default error ids entirely. Adding `variable-undefined`
* is a mistake the framework will let you make — see D4. */
ids?: string[];
/** Error ids to stop treating as problems. Demoted to `info` rather than
* dropped, so they stay visible without adding build-summary noise. */
disableIds?: string[];
};
/** Base URL for canonical links and og:url */
baseUrl?: string;
/** Human-readable site name for og:site_name */
siteName?: string;
/** Default og:image for pages without their own image */
defaultImage?: string;
/** Site logo for Organization JSON-LD schema */
logo?: string;
/** Canonical GitHub (or compatible) repository URL — e.g.
* `"https://github.com/owner/repo"`. Used by file-ref (SPEC-078) to
* build deep-link "View source" URLs of the form
* `{repoUrl}/blob/{repoBranch}/{path}#L{start}-L{end}`. When absent,
* `file-ref` falls back to a no-href link / in-page anchor with a
* build warning. */
repoUrl?: string;
/** Git ref appended to GitHub source URLs (branch / tag / commit SHA).
* Defaults to `"main"` when omitted. Use a commit SHA for archival
* URLs that won't drift when the file is edited later. */
repoBranch?: string;
/** Rune resolution configuration */
runes?: {
/** Which provider wins a rune name more than one provides: a plugin's
* short name, `"__core__"` to keep a core rune, or `"__project__"` to
* let a project rune shadow a plugin's (SPEC-153 D8). */
prefer?: Record<string, string>;
aliases?: Record<string, string>;
/** Unpublished plugin development: rune name → a JS module exporting a
* rune entry with a `transform`. Not for composed rune definitions,
* which go in `dir` (SPEC-153 D7). */
local?: Record<string, string>;
/** SPEC-153 D4 — the project's directory of composed rune definitions,
* relative to the project root. Every `<rune>.md` in it is a composed
* rune named after the file, read through `ProjectFiles` (D5). Defaults
* to `runes`; an absent directory defines no runes. */
dir?: string;
};
}