Snippet
Embed the contents of a file as a syntax-highlighted code block. The file lives anywhere in your project tree (relative to the project root); refrakt reads it at build time, slices it by line range if you ask, and renders it like any other fenced code block. When the source file changes, the embedded version updates automatically on the next build — no copy-paste drift.
The snippet rune is implemented as an AST preprocessor: by the time the transform phase reaches it, every {% snippet %} tag has been replaced with a Markdoc fence node. This is what makes it compose transparently inside {% codegroup %}, {% diff %}, and any future container rune that consumes fence nodes — they see snippets exactly the same as triple-backtick code blocks.
Embed a file
The minimum case — a path attribute relative to the project root.
{% snippet path="packages/runes/src/lang-map.ts" lines="1-40" /%}
Renders as a live embed of refrakt's own language-map module (the one snippet itself uses for extension inference):
/**
* Extension → language map for code-block syntax highlighting.
*
* Shared across the snippet rune, the inspect tool, the contracts generator,
* and any future rune that needs to infer a syntax-highlighting language
* from a file extension. Lives in `@refrakt-md/runes` (not in a plugin)
* because every consumer already depends on this package and plugins
* cannot be depended on by core.
*
* Authors can always override inferred languages with explicit `lang=`
* attributes; inference is the default, not the only path.
*/
/** Lowercase file extension (with leading dot) → highlight-language identifier. */
export const LANG_MAP: Readonly<Record<string, string>> = Object.freeze({
'.ts': 'typescript',
'.tsx': 'typescript',
'.js': 'javascript',
'.jsx': 'javascript',
'.mjs': 'javascript',
'.cjs': 'javascript',
'.svelte': 'svelte',
'.vue': 'vue',
'.md': 'markdoc',
'.markdoc': 'markdoc',
'.json': 'json',
'.jsonc': 'jsonc',
'.html': 'html',
'.css': 'css',
'.yml': 'yaml',
'.yaml': 'yaml',
'.toml': 'toml',
'.sh': 'bash',
'.bash': 'bash',
// SPEC-131 — these carry lexical entries in the language table
// (`lib/languages.ts`), so an extension missing here makes that entry
// unreachable by path and a `symbol=` anchor refuses for the wrong reason.
'.py': 'python',
'.rs': 'rust',
'.go': 'go',The reader is looking at the actual file in this repository, sliced to its first 40 lines. The build re-reads the file every time it runs, so this stays in sync.
Address by name, not by line number
A line range is a coordinate into a file nobody promised to hold still. Edit anything above the range and the snippet quietly starts quoting something else — it still renders, still highlights, still looks right. Prefer an anchor:
{% snippet path="packages/types/src/config.ts" symbol="SiteConfig" /%}
symbol names a declaration. The anchor is built from the language's own keywords, so the region is found wherever it has moved to — and if the symbol is renamed or deleted, the snippet fails loudly instead of rendering a plausible wrong span.
match is the general form: a raw regex applied per line. symbol is sugar over it, and match is what to reach for in a language whose keywords refrakt does not carry.
{% snippet path="packages/lumina/styles/runes/hint.css" match="^\.rf-hint\s*\{" /%}
Anchors are matched against the raw source, so a JSON key or a quoted selector works. A match landing inside a comment is skipped — a declaration mentioned in a comment is talking about code rather than being it.
How far the slice runs
extent picks the strategy. It is never inferred from the file, because guessing right most of the time and silently wrong the rest is the failure this feature exists to remove.
extent | Ends at | For |
|---|---|---|
auto (default) | a ; or the matching } | TypeScript, CSS, JSON |
dedent | the first line back at the anchor's indent | Python, YAML |
section | the next sibling at the anchor's own level | Markdown headings, TOML tables |
paired | the matching close token | Markdoc, HTML, Svelte |
auto only ends a construct in a brace-and-semicolon language. Point it at Python and it refuses, naming extent="dedent" — it will not hand back the rest of the file.
When an anchor will not do it
Anchoring is not a dead end. Every refusal names the way through, and all three stay supported permanently:
| Attribute | Use |
|---|---|
through="^}" | End at a regex, including the matching line |
until="^## " | End at a regex, excluding the matching line |
lines="10-25" | Address by coordinate, as before |
occurrence=2 | Take the Nth match when the anchor is ambiguous |
until and through are two attributes rather than one flag on purpose: in code the terminator line usually belongs in the slice, and in Markdown it usually does not. Either default would be silently wrong half the time.
occurrence belongs on this list, not beside symbol. It is counting, and this whole feature exists to replace counting with naming. It is a better count than a line number — inserting unrelated content above does not move it — but it still drifts the moment someone adds another match above the one you meant. Reach for a more specific match= first, and use occurrence only when the anchors are genuinely identical. An ambiguous anchor warns either way, naming every line that matched; occurrence selects one, it does not assert you picked correctly.
An occurrence past the end refuses rather than clamping to the last match.
Presentation
reindent strips the slice's common leading whitespace, so a class method or a nested key does not render with a ragged left edge. It defaults on for symbol/match and off for lines — an author who wrote a line range chose those columns, and changing how they render would be a silent visual change.
linenumbers and numeric highlight stay in file coordinates under an anchor, so the numbers remain a pointer back into the real file. But a numeric highlight under an anchor carries exactly the coordinate exposure anchoring removes — you are guessing at line numbers the resolver chose. Use highlight-match there, which takes regexes and highlights the lines that match.
Line ranges
Slice the file with the lines attribute. Four formats:
| Input | Meaning |
|---|---|
"10-25" | Lines 10 through 25, inclusive |
"10-" | Line 10 to end of file |
"-20" | Line 1 through line 20 |
"10" | Single line (line 10 only) — shorthand for "10-10" |
1-indexed (matches editor line numbers), inclusive on both ends. Out-of-range ends clamp to the file length with a build warning; out-of-range starts (entirely past EOF) are a build error.
The lines= invocations on this page are deliberate. They demonstrate the attribute, so they keep it — and they are the reason refrakt migrate snippets refuses most of what it finds here. Everywhere else, anchor by name: a line range is a coordinate into a file nobody promised to hold still.
Several of these also slice mid-construct — opening on a blank line, or in the middle of a comment block — which is itself a symptom of line addressing, and why the codemod cannot infer an anchor for them.
{% snippet path="packages/runes/src/lang-map.ts" lines="15-35" title="LANG_MAP definition" /%}
export const LANG_MAP: Readonly<Record<string, string>> = Object.freeze({
'.ts': 'typescript',
'.tsx': 'typescript',
'.js': 'javascript',
'.jsx': 'javascript',
'.mjs': 'javascript',
'.cjs': 'javascript',
'.svelte': 'svelte',
'.vue': 'vue',
'.md': 'markdoc',
'.markdoc': 'markdoc',
'.json': 'json',
'.jsonc': 'jsonc',
'.html': 'html',
'.css': 'css',
'.yml': 'yaml',
'.yaml': 'yaml',
'.toml': 'toml',
'.sh': 'bash',
'.bash': 'bash',
// SPEC-131 — these carry lexical entries in the language tableCaptions and titles
Snippet itself has no title attribute — it's structurally a fence by the time the transform runs, and fences don't carry titles. Label it by wrapping it in a container rune. Two fit, and they look different.
A caption below: figure
{% figure caption="…" %} captions the code the way it captions an image, below it. Use it when the label describes the excerpt.
{% figure caption="The language used when no extension matches" %}
{% snippet path="packages/runes/src/lang-map.ts" lines="42-50" /%}
{% /figure %}
'.cts': 'typescript',
});
/** Fallback language for extensions not covered by {@link LANG_MAP}. */
export const FALLBACK_LANG = 'text';
/**
* Infer the highlight-language identifier from a file path or extension.
*A title bar above: codegroup
{% codegroup title="…" %} puts title-bar chrome around a single fence, the way an editor tab names a file. Use it when the label is a filename or a heading.
{% codegroup title="FALLBACK_LANG constant" %}
{% snippet path="packages/runes/src/lang-map.ts" lines="42-50" /%}
{% /codegroup %}
'.cts': 'typescript',
});
/** Fallback language for extensions not covered by {@link LANG_MAP}. */
export const FALLBACK_LANG = 'text';
/**
* Infer the highlight-language identifier from a file path or extension.
*Language inference
The language is inferred from the file extension via a shared map:
| Extension | Language |
|---|---|
.ts, .tsx | typescript |
.js, .jsx, .mjs, .cjs | javascript |
.svelte | svelte |
.vue | vue |
.md, .markdoc | markdoc |
.json | json |
.jsonc | jsonc |
.html | html |
.css | css |
.yml, .yaml | yaml |
.toml | toml |
.sh, .bash | bash |
| (others) | text (no highlighting) |
Override with lang= when the extension doesn't tell the full story (a .config file that's actually JSONC, for instance).
{% snippet path="packages/runes/src/lang-map.ts" lines="1-5" lang="javascript" /%}
/**
* Extension → language map for code-block syntax highlighting.
*
* Shared across the snippet rune, the inspect tool, the contracts generator,
* and any future rune that needs to infer a syntax-highlighting languageComposition
The pre-resolve model means snippets work inside any container rune that matches fence nodes. No special handling required on either side — the container sees a fence, it consumes a fence.
Inside {% codegroup %}
Multiple snippets become tabs in a codegroup. Tab labels come from the inferred language (or set labels on codegroup for custom names).
{% codegroup %}
{% snippet path="packages/runes/src/lang-map.ts" lines="1-5" /%}
{% snippet path="packages/runes/src/util.ts" lines="1-5" /%}
{% /codegroup %}<section data-rune="code-group">
<div role="tablist" data-name="tabs">
<button data-name="tab" role="tab">
<span>lang-map.ts:1-5</span>
</button>
<button data-name="tab" role="tab">
<span>util.ts:1-5</span>
</button>
</div>
<div data-name="panels">
<div role="tabpanel" data-name="panel">
<div class="rf-codeblock">
<pre data-language="typescript" data-source="packages/runes/src/lang-map.ts" data-lines="1-5" style="--rf-start-line: 0">
<code data-language="typescript" data-source="packages/runes/src/lang-map.ts" data-lines="1-5">/**
* Extension → language map for code-block syntax highlighting.
*
* Shared across the snippet rune, the inspect tool, the contracts generator,
* and any future rune that needs to infer a syntax-highlighting language</code>
</pre>
</div>
</div>
<div role="tabpanel" data-name="panel">
<div class="rf-codeblock">
<pre data-language="typescript" data-source="packages/runes/src/util.ts" data-lines="1-5" style="--rf-start-line: 0">
<code data-language="typescript" data-source="packages/runes/src/util.ts" data-lines="1-5">import Markdoc from '@markdoc/markdoc';
import type { Tag, Config, Node, RenderableTreeNode } from '@markdoc/markdoc';
const { Ast } = Markdoc;
import { NodeFilter } from './interfaces.js';
import { isFilterMatching } from './lib/node.js';</code>
</pre>
</div>
</div>
</div>
</section>/**
* Extension → language map for code-block syntax highlighting.
*
* Shared across the snippet rune, the inspect tool, the contracts generator,
* and any future rune that needs to infer a syntax-highlighting languageimport Markdoc from '@markdoc/markdoc';
import type { Tag, Config, Node, RenderableTreeNode } from '@markdoc/markdoc';
const { Ast } = Markdoc;
import { NodeFilter } from './interfaces.js';
import { isFilterMatching } from './lib/node.js';<section class="rf-codegroup rf-codegroup--scroll" data-overflow="scroll" data-elevation="flat" data-rune="code-group" data-density="compact" data-code-host="true">
<div role="tablist" data-name="tabs" class="rf-codegroup__tabs">
<button data-name="tab" role="tab" class="rf-codegroup__tab">
<span>lang-map.ts:1-5</span>
</button>
<button data-name="tab" role="tab" class="rf-codegroup__tab">
<span>util.ts:1-5</span>
</button>
</div>
<div data-name="panels" class="rf-codegroup__panels">
<div role="tabpanel" data-name="panel" class="rf-codegroup__panel">
<div class="rf-codeblock">
<pre data-language="typescript" data-source="packages/runes/src/lang-map.ts" data-lines="1-5" style="--rf-start-line: 0"><code data-language="typescript" data-source="packages/runes/src/lang-map.ts" data-lines="1-5">/**
* Extension → language map for code-block syntax highlighting.
*
* Shared across the snippet rune, the inspect tool, the contracts generator,
* and any future rune that needs to infer a syntax-highlighting language</code></pre>
</div>
</div>
<div role="tabpanel" data-name="panel" class="rf-codegroup__panel">
<div class="rf-codeblock">
<pre data-language="typescript" data-source="packages/runes/src/util.ts" data-lines="1-5" style="--rf-start-line: 0"><code data-language="typescript" data-source="packages/runes/src/util.ts" data-lines="1-5">import Markdoc from '@markdoc/markdoc';
import type { Tag, Config, Node, RenderableTreeNode } from '@markdoc/markdoc';
const { Ast } = Markdoc;
import { NodeFilter } from './interfaces.js';
import { isFilterMatching } from './lib/node.js';</code></pre>
</div>
</div>
</div>
</section>Mixed children — snippets and triple-backtick fences in the same codegroup — work uniformly:
{% codegroup %}
{% snippet path="packages/runes/src/lang-map.ts" lines="1-5" /%}
```python
# An inline Python snippet alongside a real file
def hello(): pass
```
{% /codegroup %}<section data-rune="code-group">
<div role="tablist" data-name="tabs">
<button data-name="tab" role="tab">
<span>lang-map.ts:1-5</span>
</button>
<button data-name="tab" role="tab">
<span>Python</span>
</button>
</div>
<div data-name="panels">
<div role="tabpanel" data-name="panel">
<div class="rf-codeblock">
<pre data-language="typescript" data-source="packages/runes/src/lang-map.ts" data-lines="1-5" style="--rf-start-line: 0">
<code data-language="typescript" data-source="packages/runes/src/lang-map.ts" data-lines="1-5">/**
* Extension → language map for code-block syntax highlighting.
*
* Shared across the snippet rune, the inspect tool, the contracts generator,
* and any future rune that needs to infer a syntax-highlighting language</code>
</pre>
</div>
</div>
<div role="tabpanel" data-name="panel">
<div class="rf-codeblock">
<pre data-language="python">
<code data-language="python"># An inline Python snippet alongside a real file
def hello(): pass
</code>
</pre>
</div>
</div>
</div>
</section>/**
* Extension → language map for code-block syntax highlighting.
*
* Shared across the snippet rune, the inspect tool, the contracts generator,
* and any future rune that needs to infer a syntax-highlighting language# An inline Python snippet alongside a real file
def hello(): pass
<section class="rf-codegroup rf-codegroup--scroll" data-overflow="scroll" data-elevation="flat" data-rune="code-group" data-density="compact" data-code-host="true">
<div role="tablist" data-name="tabs" class="rf-codegroup__tabs">
<button data-name="tab" role="tab" class="rf-codegroup__tab">
<span>lang-map.ts:1-5</span>
</button>
<button data-name="tab" role="tab" class="rf-codegroup__tab">
<span>Python</span>
</button>
</div>
<div data-name="panels" class="rf-codegroup__panels">
<div role="tabpanel" data-name="panel" class="rf-codegroup__panel">
<div class="rf-codeblock">
<pre data-language="typescript" data-source="packages/runes/src/lang-map.ts" data-lines="1-5" style="--rf-start-line: 0"><code data-language="typescript" data-source="packages/runes/src/lang-map.ts" data-lines="1-5">/**
* Extension → language map for code-block syntax highlighting.
*
* Shared across the snippet rune, the inspect tool, the contracts generator,
* and any future rune that needs to infer a syntax-highlighting language</code></pre>
</div>
</div>
<div role="tabpanel" data-name="panel" class="rf-codegroup__panel">
<div class="rf-codeblock">
<pre data-language="python"><code data-language="python"># An inline Python snippet alongside a real file
def hello(): pass
</code></pre>
</div>
</div>
</div>
</section>Inside {% diff %}
Two snippets become before/after for a diff comparison.
{% diff mode="split" %}
{% snippet path="packages/runes/src/lang-map.ts" lines="42-50" /%}
{% snippet path="packages/runes/src/util.ts" lines="42-50" /%}
{% /diff %}<div data-rune="diff" data-rune-fields="{"mode":"split","language":""}" data-code-host>
<div data-name="header">packages/runes/src/lang-map.ts → packages/runes/src/util.ts</div>
<div data-name="split-container">
<div data-name="panel">
<pre data-name="code" data-copy-selector="[data-name="line-content"]">
<div data-name="rows">
<span data-name="line" data-line-status="remove">
<span data-name="gutter-num" data-side="before">42</span>
<span data-name="line-content" data-language="typescript"> '.cts': 'typescript',</span>
</span>
<span data-name="line" data-line-status="remove">
<span data-name="gutter-num" data-side="before">43</span>
<span data-name="line-content" data-language="typescript">});</span>
</span>
<span data-name="line" data-line-status="equal">
<span data-name="gutter-num" data-side="before">44</span>
<span data-name="line-content" data-language="typescript"></span>
</span>
<span data-name="line" data-line-status="remove">
<span data-name="gutter-num" data-side="before">45</span>
<span data-name="line-content" data-language="typescript">/** Fallback language for extensions not covered by {@link LANG_MAP}. */</span>
</span>
<span data-name="line" data-line-status="remove">
<span data-name="gutter-num" data-side="before">46</span>
<span data-name="line-content" data-language="typescript">export const FALLBACK_LANG = 'text';</span>
</span>
<span data-name="line" data-line-status="equal">
<span data-name="gutter-num" data-side="before">47</span>
<span data-name="line-content" data-language="typescript"></span>
</span>
<span data-name="line" data-line-status="remove">
<span data-name="gutter-num" data-side="before">48</span>
<span data-name="line-content" data-language="typescript">/**</span>
</span>
<span data-name="line" data-line-status="remove">
<span data-name="gutter-num" data-side="before">49</span>
<span data-name="line-content" data-language="typescript"> * Infer the highlight-language identifier from a file path or extension.</span>
</span>
<span data-name="line" data-line-status="remove">
<span data-name="gutter-num" data-side="before">50</span>
<span data-name="line-content" data-language="typescript"> *</span>
</span>
<span data-name="line" data-line-status="empty">
<span data-name="gutter-num" data-side="before"></span>
<span data-name="line-content"></span>
</span>
<span data-name="line" data-line-status="empty">
<span data-name="gutter-num" data-side="before"></span>
<span data-name="line-content"></span>
</span>
</div>
</pre>
</div>
<div data-name="panel">
<pre data-name="code" data-copy-selector="[data-name="line-content"]">
<div data-name="rows">
<span data-name="line" data-line-status="empty">
<span data-name="gutter-num" data-side="after"></span>
<span data-name="line-content"></span>
</span>
<span data-name="line" data-line-status="empty">
<span data-name="gutter-num" data-side="after"></span>
<span data-name="line-content"></span>
</span>
<span data-name="line" data-line-status="equal">
<span data-name="gutter-num" data-side="after">42</span>
<span data-name="line-content" data-language="typescript"></span>
</span>
<span data-name="line" data-line-status="add">
<span data-name="gutter-num" data-side="after">43</span>
<span data-name="line-content" data-language="typescript"> include?: NodeFilter[];</span>
</span>
<span data-name="line" data-line-status="add">
<span data-name="gutter-num" data-side="after">44</span>
<span data-name="line-content" data-language="typescript">}</span>
</span>
<span data-name="line" data-line-status="equal">
<span data-name="gutter-num" data-side="after">45</span>
<span data-name="line-content" data-language="typescript"></span>
</span>
<span data-name="line" data-line-status="add">
<span data-name="gutter-num" data-side="after">46</span>
<span data-name="line-content" data-language="typescript">export interface HeadingInfo {</span>
</span>
<span data-name="line" data-line-status="add">
<span data-name="gutter-num" data-side="after">47</span>
<span data-name="line-content" data-language="typescript"> level: number;</span>
</span>
<span data-name="line" data-line-status="add">
<span data-name="gutter-num" data-side="after">48</span>
<span data-name="line-content" data-language="typescript"> text: string;</span>
</span>
<span data-name="line" data-line-status="add">
<span data-name="gutter-num" data-side="after">49</span>
<span data-name="line-content" data-language="typescript"> id: string;</span>
</span>
<span data-name="line" data-line-status="add">
<span data-name="gutter-num" data-side="after">50</span>
<span data-name="line-content" data-language="typescript"> /** Canonical name of the known section this heading belongs to, if any. */</span>
</span>
</div>
</pre>
</div>
</div>
</div>42 '.cts': 'typescript',43});4445/** Fallback language for extensions not covered by {@link LANG_MAP}. */46export const FALLBACK_LANG = 'text';4748/**49 * Infer the highlight-language identifier from a file path or extension.50 *
4243 include?: NodeFilter[];44}4546export interface HeadingInfo {47 level: number;48 text: string;49 id: string;50 /** Canonical name of the known section this heading belongs to, if any. */
<div class="rf-diff rf-diff--split" data-code-host data-mode="split" data-rune="diff" data-density="full">
<div data-name="header" class="rf-diff__header">packages/runes/src/lang-map.ts → packages/runes/src/util.ts</div>
<div data-name="split-container" class="rf-diff__split-container">
<div data-name="panel" class="rf-diff__panel">
<pre data-name="code" data-copy-selector="[data-name="line-content"]" class="rf-diff__code"><div data-name="rows" class="rf-diff__rows"><span data-name="line" data-line-status="remove" class="rf-diff__line"><span data-name="gutter-num" data-side="before" class="rf-diff__gutter-num">42</span><span data-name="line-content" data-language="typescript" class="rf-diff__line-content"> '.cts': 'typescript',</span></span><span data-name="line" data-line-status="remove" class="rf-diff__line"><span data-name="gutter-num" data-side="before" class="rf-diff__gutter-num">43</span><span data-name="line-content" data-language="typescript" class="rf-diff__line-content">});</span></span><span data-name="line" data-line-status="equal" class="rf-diff__line"><span data-name="gutter-num" data-side="before" class="rf-diff__gutter-num">44</span><span data-name="line-content" data-language="typescript" class="rf-diff__line-content"></span></span><span data-name="line" data-line-status="remove" class="rf-diff__line"><span data-name="gutter-num" data-side="before" class="rf-diff__gutter-num">45</span><span data-name="line-content" data-language="typescript" class="rf-diff__line-content">/** Fallback language for extensions not covered by {@link LANG_MAP}. */</span></span><span data-name="line" data-line-status="remove" class="rf-diff__line"><span data-name="gutter-num" data-side="before" class="rf-diff__gutter-num">46</span><span data-name="line-content" data-language="typescript" class="rf-diff__line-content">export const FALLBACK_LANG = 'text';</span></span><span data-name="line" data-line-status="equal" class="rf-diff__line"><span data-name="gutter-num" data-side="before" class="rf-diff__gutter-num">47</span><span data-name="line-content" data-language="typescript" class="rf-diff__line-content"></span></span><span data-name="line" data-line-status="remove" class="rf-diff__line"><span data-name="gutter-num" data-side="before" class="rf-diff__gutter-num">48</span><span data-name="line-content" data-language="typescript" class="rf-diff__line-content">/**</span></span><span data-name="line" data-line-status="remove" class="rf-diff__line"><span data-name="gutter-num" data-side="before" class="rf-diff__gutter-num">49</span><span data-name="line-content" data-language="typescript" class="rf-diff__line-content"> * Infer the highlight-language identifier from a file path or extension.</span></span><span data-name="line" data-line-status="remove" class="rf-diff__line"><span data-name="gutter-num" data-side="before" class="rf-diff__gutter-num">50</span><span data-name="line-content" data-language="typescript" class="rf-diff__line-content"> *</span></span><span data-name="line" data-line-status="empty" class="rf-diff__line"><span data-name="gutter-num" data-side="before" class="rf-diff__gutter-num"></span><span data-name="line-content" class="rf-diff__line-content"></span></span><span data-name="line" data-line-status="empty" class="rf-diff__line"><span data-name="gutter-num" data-side="before" class="rf-diff__gutter-num"></span><span data-name="line-content" class="rf-diff__line-content"></span></span></div></pre>
</div>
<div data-name="panel" class="rf-diff__panel">
<pre data-name="code" data-copy-selector="[data-name="line-content"]" class="rf-diff__code"><div data-name="rows" class="rf-diff__rows"><span data-name="line" data-line-status="empty" class="rf-diff__line"><span data-name="gutter-num" data-side="after" class="rf-diff__gutter-num"></span><span data-name="line-content" class="rf-diff__line-content"></span></span><span data-name="line" data-line-status="empty" class="rf-diff__line"><span data-name="gutter-num" data-side="after" class="rf-diff__gutter-num"></span><span data-name="line-content" class="rf-diff__line-content"></span></span><span data-name="line" data-line-status="equal" class="rf-diff__line"><span data-name="gutter-num" data-side="after" class="rf-diff__gutter-num">42</span><span data-name="line-content" data-language="typescript" class="rf-diff__line-content"></span></span><span data-name="line" data-line-status="add" class="rf-diff__line"><span data-name="gutter-num" data-side="after" class="rf-diff__gutter-num">43</span><span data-name="line-content" data-language="typescript" class="rf-diff__line-content"> include?: NodeFilter[];</span></span><span data-name="line" data-line-status="add" class="rf-diff__line"><span data-name="gutter-num" data-side="after" class="rf-diff__gutter-num">44</span><span data-name="line-content" data-language="typescript" class="rf-diff__line-content">}</span></span><span data-name="line" data-line-status="equal" class="rf-diff__line"><span data-name="gutter-num" data-side="after" class="rf-diff__gutter-num">45</span><span data-name="line-content" data-language="typescript" class="rf-diff__line-content"></span></span><span data-name="line" data-line-status="add" class="rf-diff__line"><span data-name="gutter-num" data-side="after" class="rf-diff__gutter-num">46</span><span data-name="line-content" data-language="typescript" class="rf-diff__line-content">export interface HeadingInfo {</span></span><span data-name="line" data-line-status="add" class="rf-diff__line"><span data-name="gutter-num" data-side="after" class="rf-diff__gutter-num">47</span><span data-name="line-content" data-language="typescript" class="rf-diff__line-content"> level: number;</span></span><span data-name="line" data-line-status="add" class="rf-diff__line"><span data-name="gutter-num" data-side="after" class="rf-diff__gutter-num">48</span><span data-name="line-content" data-language="typescript" class="rf-diff__line-content"> text: string;</span></span><span data-name="line" data-line-status="add" class="rf-diff__line"><span data-name="gutter-num" data-side="after" class="rf-diff__gutter-num">49</span><span data-name="line-content" data-language="typescript" class="rf-diff__line-content"> id: string;</span></span><span data-name="line" data-line-status="add" class="rf-diff__line"><span data-name="gutter-num" data-side="after" class="rf-diff__gutter-num">50</span><span data-name="line-content" data-language="typescript" class="rf-diff__line-content"> /** Canonical name of the known section this heading belongs to, if any. */</span></span></div></pre>
</div>
</div>
</div>The diff doesn't know or care that its children came from snippet tags — it sees the same fence nodes a triple-backtick block would produce.
Sandbox rules
Paths are resolved relative to the directory containing refrakt.config.json. The resolver enforces the snippet sandbox at build time:
| Pattern | Outcome |
|---|---|
path="/etc/passwd" | Rejected — absolute paths are not allowed. |
path="../../etc/passwd" | Rejected — paths must stay inside the project root after normalization. |
path="link-to-outside" (symlink that points outside the project root) | Rejected — fs.realpath is checked too. |
path="src/" (a directory) | Rejected — must be a regular file. |
path="missing.ts" (file doesn't exist) | Rejected — build error names the resolved path and the page that referenced it. |
These rules make snippet safe to use on sites that accept untrusted author content (a hosted authoring product, an external editor) — the worst a malicious author can do with snippet is point at a file inside the project root.
Line numbers
Set linenumbers=true to show a numbered gutter. The starting number derives from lines= so the gutter reflects the file's real offsets — lines="74-125" starts the count at 74, not 1. SPEC-062 / WORK-304.
{% snippet path="packages/runes/src/lang-map.ts" lines="15-25" linenumbers=true /%}
Renders with the gutter starting at 15 (file coordinates), matching what you'd see in your editor:
export const LANG_MAP: Readonly<Record<string, string>> = Object.freeze({
'.ts': 'typescript',
'.tsx': 'typescript',
'.js': 'javascript',
'.jsx': 'javascript',
'.mjs': 'javascript',
'.cjs': 'javascript',
'.svelte': 'svelte',
'.vue': 'vue',
'.md': 'markdoc',
'.markdoc': 'markdoc',The implementation is pure CSS — pre[data-linenumbers] gets a counter-reset seeded by an inlined --rf-start-line custom property the fence schema computes from data-lines. No JS, no DOM rewriting.
Highlighting specific lines
highlight="74-78" emphasizes specific lines without cropping. Unlike lines= (which slices), highlight keeps the surrounding context visible and draws the eye to the lines that matter. Use it for "look at this part" callouts inside a larger code block.
{% snippet path="packages/runes/src/lang-map.ts" lines="10-40" linenumbers=true highlight="18-22" /%}
Renders the slice 10-40 with file lines 18-22 emphasized via a neutral surface tint and a primary-coloured left rail:
* Authors can always override inferred languages with explicit `lang=`
* attributes; inference is the default, not the only path.
*/
/** Lowercase file extension (with leading dot) → highlight-language identifier. */
export const LANG_MAP: Readonly<Record<string, string>> = Object.freeze({
'.ts': 'typescript',
'.tsx': 'typescript',
'.js': 'javascript',
'.jsx': 'javascript',
'.mjs': 'javascript',
'.cjs': 'javascript',
'.svelte': 'svelte',
'.vue': 'vue',
'.md': 'markdoc',
'.markdoc': 'markdoc',
'.json': 'json',
'.jsonc': 'jsonc',
'.html': 'html',
'.css': 'css',
'.yml': 'yaml',
'.yaml': 'yaml',
'.toml': 'toml',
'.sh': 'bash',
'.bash': 'bash',
// SPEC-131 — these carry lexical entries in the language table
// (`lib/languages.ts`), so an extension missing here makes that entry
// unreachable by path and a `symbol=` anchor refuses for the wrong reason.
'.py': 'python',
'.rs': 'rust',
'.go': 'go',Range syntax matches Shiki: single ranges ("74-78"), single lines ("82"), or comma-separated mixed ("74-78,82,90-92"). Indices are file coordinates — if you've set lines="10-40" highlight="20", the highlighted line is file line 20, not the 20th line of the slice. Less surprising for citing line numbers from a real file.
Both linenumbers and highlight are also fence-level annotations — ```ts {% linenumbers=true highlight="3-5" %} works on any hand-authored fence too, no snippet required.
Review markers
Anchoring answers am I quoting the right region. It cannot answer the next question up: the region is right, and the prose around it no longer describes what is in it.
A field is removed, a default flips, a parameter is reordered. The anchor resolves perfectly, the code block renders real current code, and the paragraph above it quietly becomes false. That is the ordinary case, because editing a declaration is far more common than renaming one.
{% snippet path="packages/types/src/config.ts" symbol="SiteConfig" reviewed="a3f91c4e:70b2d118" /%}
The marker records that a human read this version of the slice and confirmed the prose matched. It freezes nothing — the snippet still tracks HEAD and re-resolves every build. When the region changes, a diagnostic asks for a re-read; the page still renders the current code.
Never written by hand
refrakt snippet review site/content/runes/file-ref.md # stamp unmarked
refrakt snippet review --check # report, own exit code
refrakt snippet review --update # re-stamp what changed
--update shows the content diff of every slice it re-stamps, recovered from the commit that wrote the marker. A commit full of changed hex strings is unreviewable; a commit where someone saw each before/after is the entire point.
A change that alters the content but not its meaning — a reformat — is re-stamped without prompting, because two hash levels make "it was just the formatter" a fact the tool establishes rather than a judgement you make under time pressure. Everything else is held for you.
Mark selectively
Marking every snippet makes --check permanently noisy and trains everyone to ignore it. Mark the references whose prose makes specific claims about the code beside them.
This repository marks exactly one. Almost every snippet on these pages demonstrates the rune that quotes it — the prose says "here is what lines= does", not "this code means X" — so a change to the quoted file does not make the surrounding sentence false. A file-ref at package.json is the sharp counter-example: it changes on every dependency bump, and a marker there would fire constantly while certifying nothing.
If --check is noisy after a marking pass, the remedy is unmarking pages, not loosening the comparison. A marker that fires on a page whose prose did not depend on the slice is a selection error.
Attributes
| Attribute | Type | Required | Description |
|---|---|---|---|
path | string | ✓ | Path to the source file, relative to the project root. Rejected if it escapes the root. |
lines | string | — | Line range. Formats: "10-25", "10-" (to EOF), "-20" (from start), "10" (single line). 1-indexed, inclusive. A coordinate into a file nobody promised to hold still — prefer `symbol` or `match`, which survive edits above the region. Mutually exclusive with both. |
symbol | string | — | Name a declaration instead of a line range: `symbol="SiteConfig"`. The anchor is built from the language's keyword table, so a symbol that moves is still found and a symbol that is renamed fails loudly instead of rendering the wrong region. Includes the doc comment by default (see `doc`). |
match | string | — | Raw regex anchor, applied per line: `match="^\\.rf-hint\\s*\\{"`. The general form — `symbol` is sugar over it — and the way to address a language whose keywords the table does not carry. |
occurrence | number | — | Which match to take when the anchor is ambiguous, 1-based. An escape hatch, not a naming form: it is a better coordinate than a line number, but it still drifts when another match is inserted above the one addressed. Prefer a more specific `match=`. Out of range refuses rather than clamping. |
extent | "auto" | "dedent" | "section" | "paired" | — | How far the slice runs. `auto` (default) balances delimiters — TS, CSS, JSON. `dedent` consumes lines indented deeper than the anchor — Python, YAML. `section` runs to the next sibling at the anchor's own level — Markdown headings, TOML tables. `paired` balances a token pair — Markdoc, HTML, Svelte. Never inferred from the file. |
until | string | — | Regex ending the extent, excluding the matching line. Overrides `extent`. Refuses if it never matches rather than returning the rest of the file. |
through | string | — | As `until`, but including the matching line. Two attributes rather than one flag because `until="^}"` in code wants the brace in and `until="^## "` in Markdown wants the heading out — either default is silently wrong half the time. |
doc | boolean | — | Include the preceding doc comment. Defaults on for `symbol` (which names an entity and delegates the boundary) and off for `match` (which names a line the author already chose). Annotations and decorators attach regardless. |
reindent | boolean | — | Strip the slice's common leading whitespace, so a nested target does not render with a ragged left edge. Defaults on for `symbol`/`match` and off for `lines`, so no existing line-addressed invocation changes how it renders. Relative structure is preserved, so a dedented Python method stays valid. |
reviewed | string | — | Records that a human read this version of the slice and confirmed the prose around it matched. Freezes nothing — the snippet still tracks HEAD and re-resolves every build. When the slice changes, a diagnostic asks for a re-read; the page still renders the current code. Written by `refrakt snippet review`, never by hand. |
highlight-match | string | — | Comma-separated regexes; lines matching any of them are emphasized. The anchor-native form of `highlight` — under an anchor the author never saw a line number, so content is the only thing they can point at. |
lang | string | — | Syntax-highlighting language hint. Overrides the extension-based inference. |
linenumbers | boolean | — | Show line numbers in the gutter. Starting number derives from the `lines` range start (e.g. lines="74-125" → first line is 74), so numbers reflect the file's real offsets. WORK-304. |
highlight | string | — | Range(s) to emphasize without cropping — Shiki-style format: "74-78", "74-78,82,90-92". Indices are file coordinates (same frame as `lines=`), and stay so under an anchor. Note that a *numeric* highlight under `symbol`/`match` carries the same coordinate exposure anchoring otherwise removes — the resolver chose the region, so the author is guessing at its line numbers. Use `highlight-match` there. WORK-304. |
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 |
No title attribute. Snippet's output is a fence; to label it, wrap it in {% figure caption="..." %} (a caption below) or {% codegroup title="..." %} (a title bar above).
Output
A snippet renders as exactly what a fenced code block renders: a <pre>, with nothing wrapped around it. What sets it apart is provenance on the <pre> itself — data-source carries the project-relative path and data-lines the resolved range — so pre[data-source] is the selector for snippet-derived code, in a theme or in tooling that wants an "edit this file" link.
<pre data-language="typescript" data-source="packages/runes/src/lang-map.ts" data-lines="74-78">
<code data-language="typescript">…</code>
</pre>
There is no .rf-snippet element. Releases before v0.39.0 wrapped a standalone snippet in <figure class="rf-snippet" data-source-path="…">; the figure carried nothing the <pre> did not already, so it was removed. Move selectors on .rf-snippet or [data-source-path] to pre[data-source]. For chrome around a snippet, compose it — {% codegroup title="..." %} adds a title bar.
View source — recursively
Snippet's killer trick: feed it $file.path from the page-variable surface (Content variables) to embed the page's own source.
{% snippet path=$file.path lang="markdoc" /%}
Renders as the snippet block below — at build time it reads the page you're looking at right now and inlines its content. The reader sees the markdown that generates the page they're reading. The "see it in action" pitch in one rune.
---
title: Snippet
description: Embed a project file as a syntax-highlighted code block — composes inside codegroup, diff, and any future fence-consuming container
category: "Code & Data"
plugin: core
status: stable
type: rune
---
# Snippet
Embed the contents of a file as a syntax-highlighted code block. The file lives anywhere in your project tree (relative to the project root); refrakt reads it at build time, slices it by line range if you ask, and renders it like any other fenced code block. When the source file changes, the embedded version updates automatically on the next build — no copy-paste drift.
{% hint type="note" %}
The snippet rune is implemented as an AST preprocessor: by the time the transform phase reaches it, every `{% snippet %}` tag has been replaced with a Markdoc `fence` node. This is what makes it compose transparently inside `{% codegroup %}`, `{% diff %}`, and any future container rune that consumes fence nodes — they see snippets exactly the same as triple-backtick code blocks.
{% /hint %}
## Embed a file
The minimum case — a `path` attribute relative to the project root.
```markdoc
{% snippet path="packages/runes/src/lang-map.ts" lines="1-40" /%}
```
Renders as a live embed of refrakt's own language-map module (the one snippet itself uses for extension inference):
{% snippet path="packages/runes/src/lang-map.ts" lines="1-40" /%}
The reader is looking at the actual file in this repository, sliced to its first 40 lines. The build re-reads the file every time it runs, so this stays in sync.
## Address by name, not by line number
A line range is a coordinate into a file nobody promised to hold still. Edit anything above the range and the snippet quietly starts quoting something else — it still renders, still highlights, still looks right. Prefer an anchor:
```markdoc
{% snippet path="packages/types/src/config.ts" symbol="SiteConfig" /%}
```
`symbol` names a declaration. The anchor is built from the language's own keywords, so the region is found wherever it has moved to — and if the symbol is renamed or deleted, the snippet **fails loudly** instead of rendering a plausible wrong span.
`match` is the general form: a raw regex applied per line. `symbol` is sugar over it, and `match` is what to reach for in a language whose keywords refrakt does not carry.
```markdoc
{% snippet path="packages/lumina/styles/runes/hint.css" match="^\.rf-hint\s*\{" /%}
```
Anchors are matched against the **raw** source, so a JSON key or a quoted selector works. A match landing inside a comment is skipped — a declaration mentioned in a comment is talking about code rather than being it.
### How far the slice runs
`extent` picks the strategy. It is never inferred from the file, because guessing right most of the time and silently wrong the rest is the failure this feature exists to remove.
| `extent` | Ends at | For |
|---|---|---|
| `auto` *(default)* | a `;` or the matching `}` | TypeScript, CSS, JSON |
| `dedent` | the first line back at the anchor's indent | Python, YAML |
| `section` | the next sibling at the anchor's own level | Markdown headings, TOML tables |
| `paired` | the matching close token | Markdoc, HTML, Svelte |
`auto` only ends a construct in a brace-and-semicolon language. Point it at Python and it refuses, naming `extent="dedent"` — it will not hand back the rest of the file.
### When an anchor will not do it
Anchoring is not a dead end. Every refusal names the way through, and all three stay supported permanently:
| Attribute | Use |
|---|---|
| `through="^}"` | End at a regex, **including** the matching line |
| `until="^## "` | End at a regex, **excluding** the matching line |
| `lines="10-25"` | Address by coordinate, as before |
| `occurrence=2` | Take the *N*th match when the anchor is ambiguous |
`until` and `through` are two attributes rather than one flag on purpose: in code the terminator line usually belongs in the slice, and in Markdown it usually does not. Either default would be silently wrong half the time.
**`occurrence` belongs on this list, not beside `symbol`.** It is counting, and this whole feature exists to replace counting with naming. It is a *better* count than a line number — inserting unrelated content above does not move it — but it still drifts the moment someone adds another match above the one you meant. Reach for a more specific `match=` first, and use `occurrence` only when the anchors are genuinely identical. An ambiguous anchor warns either way, naming every line that matched; `occurrence` selects one, it does not assert you picked correctly.
An `occurrence` past the end refuses rather than clamping to the last match.
### Presentation
`reindent` strips the slice's common leading whitespace, so a class method or a nested key does not render with a ragged left edge. It defaults **on** for `symbol`/`match` and **off** for `lines` — an author who wrote a line range chose those columns, and changing how they render would be a silent visual change.
`linenumbers` and numeric `highlight` stay in **file** coordinates under an anchor, so the numbers remain a pointer back into the real file. But a numeric `highlight` under an anchor carries exactly the coordinate exposure anchoring removes — you are guessing at line numbers the resolver chose. Use `highlight-match` there, which takes regexes and highlights the lines that match.
## Line ranges
Slice the file with the `lines` attribute. Four formats:
| Input | Meaning |
|-------|---------|
| `"10-25"` | Lines 10 through 25, inclusive |
| `"10-"` | Line 10 to end of file |
| `"-20"` | Line 1 through line 20 |
| `"10"` | Single line (line 10 only) — shorthand for `"10-10"` |
1-indexed (matches editor line numbers), inclusive on both ends. Out-of-range ends clamp to the file length with a build warning; out-of-range starts (entirely past EOF) are a build error.
{% hint type="note" %}
**The `lines=` invocations on this page are deliberate.** They demonstrate the attribute, so they keep it — and they are the reason `refrakt migrate snippets` refuses most of what it finds here. Everywhere else, [anchor by name](#address-by-name-not-by-line-number): a line range is a coordinate into a file nobody promised to hold still.
Several of these also slice mid-construct — opening on a blank line, or in the middle of a comment block — which is itself a symptom of line addressing, and why the codemod cannot infer an anchor for them.
{% /hint %}
```markdoc
{% snippet path="packages/runes/src/lang-map.ts" lines="15-35" title="LANG_MAP definition" /%}
```
{% snippet path="packages/runes/src/lang-map.ts" lines="15-35" title="LANG_MAP definition" /%}
## Captions and titles
Snippet itself has no `title` attribute — it's structurally a `fence` by the time the transform runs, and fences don't carry titles. Label it by wrapping it in a container rune. Two fit, and they look different.
### A caption below: `figure`
`{% figure caption="…" %}` captions the code the way it captions an image, below it. Use it when the label describes the excerpt.
```markdoc
{% figure caption="The language used when no extension matches" %}
{% snippet path="packages/runes/src/lang-map.ts" lines="42-50" /%}
{% /figure %}
```
{% figure caption="The language used when no extension matches" %}
{% snippet path="packages/runes/src/lang-map.ts" lines="42-50" /%}
{% /figure %}
### A title bar above: `codegroup`
`{% codegroup title="…" %}` puts title-bar chrome around a single fence, the way an editor tab names a file. Use it when the label is a filename or a heading.
```markdoc
{% codegroup title="FALLBACK_LANG constant" %}
{% snippet path="packages/runes/src/lang-map.ts" lines="42-50" /%}
{% /codegroup %}
```
{% codegroup title="FALLBACK_LANG constant" %}
{% snippet path="packages/runes/src/lang-map.ts" lines="42-50" /%}
{% /codegroup %}
## Language inference
The language is inferred from the file extension via a shared map:
| Extension | Language |
|-----------|----------|
| `.ts`, `.tsx` | `typescript` |
| `.js`, `.jsx`, `.mjs`, `.cjs` | `javascript` |
| `.svelte` | `svelte` |
| `.vue` | `vue` |
| `.md`, `.markdoc` | `markdoc` |
| `.json` | `json` |
| `.jsonc` | `jsonc` |
| `.html` | `html` |
| `.css` | `css` |
| `.yml`, `.yaml` | `yaml` |
| `.toml` | `toml` |
| `.sh`, `.bash` | `bash` |
| (others) | `text` (no highlighting) |
Override with `lang=` when the extension doesn't tell the full story (a `.config` file that's actually JSONC, for instance).
```markdoc
{% snippet path="packages/runes/src/lang-map.ts" lines="1-5" lang="javascript" /%}
```
{% snippet path="packages/runes/src/lang-map.ts" lines="1-5" lang="javascript" /%}
## Composition
The pre-resolve model means snippets work inside any container rune that matches `fence` nodes. No special handling required on either side — the container sees a fence, it consumes a fence.
### Inside `{% codegroup %}`
Multiple snippets become tabs in a codegroup. Tab labels come from the inferred language (or set `labels` on codegroup for custom names).
{% preview source=true %}
{% codegroup %}
{% snippet path="packages/runes/src/lang-map.ts" lines="1-5" /%}
{% snippet path="packages/runes/src/util.ts" lines="1-5" /%}
{% /codegroup %}
{% /preview %}
Mixed children — snippets and triple-backtick fences in the same codegroup — work uniformly:
{% preview source=true %}
{% codegroup %}
{% snippet path="packages/runes/src/lang-map.ts" lines="1-5" /%}
```python
# An inline Python snippet alongside a real file
def hello(): pass
```
{% /codegroup %}
{% /preview %}
### Inside `{% diff %}`
Two snippets become before/after for a diff comparison.
{% preview source=true %}
{% diff mode="split" %}
{% snippet path="packages/runes/src/lang-map.ts" lines="42-50" /%}
{% snippet path="packages/runes/src/util.ts" lines="42-50" /%}
{% /diff %}
{% /preview %}
The diff doesn't know or care that its children came from snippet tags — it sees the same `fence` nodes a triple-backtick block would produce.
## Sandbox rules
Paths are resolved relative to the directory containing `refrakt.config.json`. The resolver enforces the snippet sandbox at build time:
| Pattern | Outcome |
|---------|---------|
| `path="/etc/passwd"` | **Rejected** — absolute paths are not allowed. |
| `path="../../etc/passwd"` | **Rejected** — paths must stay inside the project root after normalization. |
| `path="link-to-outside"` (symlink that points outside the project root) | **Rejected** — `fs.realpath` is checked too. |
| `path="src/"` (a directory) | **Rejected** — must be a regular file. |
| `path="missing.ts"` (file doesn't exist) | **Rejected** — build error names the resolved path and the page that referenced it. |
These rules make snippet safe to use on sites that accept untrusted author content (a hosted authoring product, an external editor) — the worst a malicious author can do with snippet is point at a file inside the project root.
## Line numbers
Set `linenumbers=true` to show a numbered gutter. The starting number derives from `lines=` so the gutter reflects the file's real offsets — `lines="74-125"` starts the count at 74, not 1. SPEC-062 / WORK-304.
```markdoc
{% snippet path="packages/runes/src/lang-map.ts" lines="15-25" linenumbers=true /%}
```
Renders with the gutter starting at 15 (file coordinates), matching what you'd see in your editor:
{% snippet path="packages/runes/src/lang-map.ts" lines="15-25" linenumbers=true /%}
The implementation is pure CSS — `pre[data-linenumbers]` gets a `counter-reset` seeded by an inlined `--rf-start-line` custom property the fence schema computes from `data-lines`. No JS, no DOM rewriting.
## Highlighting specific lines
`highlight="74-78"` emphasizes specific lines without cropping. Unlike `lines=` (which slices), highlight keeps the surrounding context visible and draws the eye to the lines that matter. Use it for "look at this part" callouts inside a larger code block.
```markdoc
{% snippet path="packages/runes/src/lang-map.ts" lines="10-40" linenumbers=true highlight="18-22" /%}
```
Renders the slice 10-40 with file lines 18-22 emphasized via a neutral surface tint and a primary-coloured left rail:
{% snippet path="packages/runes/src/lang-map.ts" lines="10-40" linenumbers=true highlight="18-22" /%}
Range syntax matches Shiki: single ranges (`"74-78"`), single lines (`"82"`), or comma-separated mixed (`"74-78,82,90-92"`). Indices are **file coordinates** — if you've set `lines="10-40" highlight="20"`, the highlighted line is file line 20, not the 20th line of the slice. Less surprising for citing line numbers from a real file.
Both `linenumbers` and `highlight` are also fence-level annotations — `` ```ts {% linenumbers=true highlight="3-5" %} `` works on any hand-authored fence too, no snippet required.
## Review markers
Anchoring answers *am I quoting the right region*. It cannot answer the next question up: **the region is right, and the prose around it no longer describes what is in it.**
A field is removed, a default flips, a parameter is reordered. The anchor resolves perfectly, the code block renders real current code, and the paragraph above it quietly becomes false. That is the *ordinary* case, because editing a declaration is far more common than renaming one.
```markdoc
{% snippet path="packages/types/src/config.ts" symbol="SiteConfig" reviewed="a3f91c4e:70b2d118" /%}
```
The marker records that a human read this version of the slice and confirmed the prose matched. **It freezes nothing** — the snippet still tracks HEAD and re-resolves every build. When the region changes, a diagnostic asks for a re-read; the page still renders the current code.
### Never written by hand
```bash
refrakt snippet review site/content/runes/file-ref.md # stamp unmarked
refrakt snippet review --check # report, own exit code
refrakt snippet review --update # re-stamp what changed
```
`--update` shows the **content diff** of every slice it re-stamps, recovered from the commit that wrote the marker. A commit full of changed hex strings is unreviewable; a commit where someone saw each before/after is the entire point.
A change that alters the content but not its meaning — a reformat — is re-stamped without prompting, because two hash levels make "it was just the formatter" a fact the tool establishes rather than a judgement you make under time pressure. Everything else is held for you.
### Mark selectively
Marking every snippet makes `--check` permanently noisy and trains everyone to ignore it. Mark the references whose prose makes **specific claims** about the code beside them.
This repository marks exactly one. Almost every `snippet` on these pages demonstrates the rune that quotes it — the prose says "here is what `lines=` does", not "this code means X" — so a change to the quoted file does not make the surrounding sentence false. A `file-ref` at `package.json` is the sharp counter-example: it changes on every dependency bump, and a marker there would fire constantly while certifying nothing.
If `--check` is noisy after a marking pass, the remedy is **unmarking pages**, not loosening the comparison. A marker that fires on a page whose prose did not depend on the slice is a selection error.
## Attributes
{% include file="rune-attributes.md" variables={r: "rune:snippet"} /%}
No `title` attribute. Snippet's output is a fence; to label it, wrap it in `{% figure caption="..." %}` (a caption below) or `{% codegroup title="..." %}` (a title bar above).
## Output
A snippet renders as exactly what a fenced code block renders: a `<pre>`, with nothing wrapped around it. What sets it apart is provenance on the `<pre>` itself — `data-source` carries the project-relative path and `data-lines` the resolved range — so `pre[data-source]` is the selector for snippet-derived code, in a theme or in tooling that wants an "edit this file" link.
```html
<pre data-language="typescript" data-source="packages/runes/src/lang-map.ts" data-lines="74-78">
<code data-language="typescript">…</code>
</pre>
```
There is no `.rf-snippet` element. Releases before v0.39.0 wrapped a standalone snippet in `<figure class="rf-snippet" data-source-path="…">`; the figure carried nothing the `<pre>` did not already, so it was removed. Move selectors on `.rf-snippet` or `[data-source-path]` to `pre[data-source]`. For chrome around a snippet, compose it — `{% codegroup title="..." %}` adds a title bar.
## View source — recursively
Snippet's killer trick: feed it `$file.path` from the page-variable surface ({% xref "/docs/authoring/variables" /%}) to embed the page's own source.
```markdoc
{% snippet path=$file.path lang="markdoc" /%}
```
Renders as the snippet block below — at build time it reads the page you're looking at right now and inlines its content. The reader sees the markdown that generates the page they're reading. The "see it in action" pitch in one rune.
{% snippet path=$file.path lang="markdoc" /%}
## See also
- [Codegroup](/runes/codegroup) — tabbed code blocks; consumes snippet children transparently.
- [Figure](/runes/figure) — a captioned container; captions a snippet below the code.
- [Diff](/runes/diff) — before/after code comparison; consumes snippet children transparently.
- [Content variables](/docs/authoring/variables) — `$file.path` for the view-source-of-current-page pattern.
See also
- Codegroup — tabbed code blocks; consumes snippet children transparently.
- Figure — a captioned container; captions a snippet below the code.
- Diff — before/after code comparison; consumes snippet children transparently.
- Content variables —
$file.pathfor the view-source-of-current-page pattern.