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-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" label="SiteThemeConfig" /%}
for the shape.
Anchoring to a line range
lines accepts a single line ("42") or a range ("42-58"). Drives both the GitHub #L42-L58 anchor and the snippet slice when previewing.
{% file-ref path="packages/types/src/theme.ts" lines="42-58" label="SiteThemeConfig" /%}
The href becomes https://github.com/{owner}/{repo}/blob/{repoBranch}/packages/types/src/theme.ts#L42-L58 — clicking jumps straight to the highlighted range on GitHub.
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/theme.ts" lines="74-125" label="SiteConfig" preview="drawer" /%} for the full shape.<p>
See
<span data-rune="file-ref">
<meta data-field="file-ref-path" content="packages/types/src/theme.ts">
<meta data-field="file-ref-lines" content="74-125">
<meta data-field="file-ref-label" content="SiteConfig">
<meta data-field="file-ref-preview" content="drawer">
<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-theme.ts-L74-L125" aria-controls="drawer-packages-types-src-theme.ts-L74-L125" 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, pass an explicit label. Until a future symbol="…" attribute lands (file-ref rune + shared preview attribute on reference runes future extensions), label is the only knob:
{# Refers to the file: filename is fine #}
{% file-ref path="package.json" /%}
{# Refers to a symbol in the file: pass a label #}
{% file-ref path="packages/types/src/theme.ts" lines="74-125" 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"`. |
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">
<figure class="rf-snippet" data-source-path="path/to/file.ts" data-lines="42-58">
<pre data-language="typescript"><code>…</code></pre>
</figure>
</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
/** Default og:image for pages without their own image (path relative to site root, e.g. "/og-image.png"). Recommended size: 1200x630px. */
defaultImage?: string;
/** Site logo for Organization JSON-LD schema (path relative to site root, e.g. "/favicon-192.png") */
logo?: string;
/** Rune-to-component mappings keyed by typeof name */
components: Record<string, ComponentDefinition>;
/** Behavior when a rune has no matching component */
unsupportedRuneBehavior?: 'fallback' | 'passthrough' | 'hide';
/** Relative path to fallback component for unsupported runes */
fallbackComponent?: string;
}
export interface LayoutDefinition {
/** Relative path to layout component file */
component: string;
/** All region names this layout supports */
regions: string[];
/** Regions that must be provided for this layout to render correctly */
requiredRegions?: string[];
}
export interface ComponentDefinition {
/** Relative path to component file */
component: string;
/** Maps rune attribute names to component prop names */
propMapping?: Record<string, string>;
/** Whether this component accepts children */
acceptsChildren?: boolean;
/** Context-dependent component overrides: key is parent rune name */
contextOverrides?: Record<string, { component: string }>;
}