Code & DataSnippet

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.

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.

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',
});

/** Fallback language for extensions not covered by {@link LANG_MAP}. */
export const FALLBACK_LANG = 'text';

/**

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.

Line ranges

Slice the file with the lines attribute. Four formats:

InputMeaning
"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.

{% 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',
});

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. For a labelled chrome, wrap the snippet in {% codegroup %}, which produces title-bar chrome around a single fence:

{% codegroup title="FALLBACK_LANG constant" %}
{% snippet path="packages/runes/src/lang-map.ts" lines="42-50" /%}
{% /codegroup %}
FALLBACK_LANG constant
 *
 * Accepts a full path (`"src/lib/foo.ts"`), a bare extension with the dot
 * (`".ts"`), or a bare extension without (`"ts"`). Unknown extensions
 * return {@link FALLBACK_LANG}.
 *
 * @example
 * ```ts
 * inferLanguage('src/lib/foo.ts');   // 'typescript'
 * inferLanguage('.svelte');           // 'svelte'

Language inference

The language is inferred from the file extension via a shared map:

ExtensionLanguage
.ts, .tsxtypescript
.js, .jsx, .mjs, .cjsjavascript
.sveltesvelte
.vuevue
.md, .markdocmarkdoc
.jsonjson
.jsoncjsonc
.htmlhtml
.csscss
.yml, .yamlyaml
.tomltoml
.sh, .bashbash
(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 language

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).

{% 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 language
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';
<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="{&quot;mode&quot;:&quot;split&quot;,&quot;language&quot;:&quot;&quot;}" 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=&quot;line-content&quot;]">
        <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"> *</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"> * Accepts a full path (`&quot;src/lib/foo.ts&quot;`), a bare extension with the dot</span>
          </span>
          <span data-name="line" data-line-status="remove">
            <span data-name="gutter-num" data-side="before">44</span>
            <span data-name="line-content" data-language="typescript"> * (`&quot;.ts&quot;`), or a bare extension without (`&quot;ts&quot;`). Unknown extensions</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"> * return {@link FALLBACK_LANG}.</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"> *</span>
          </span>
          <span data-name="line" data-line-status="remove">
            <span data-name="gutter-num" data-side="before">47</span>
            <span data-name="line-content" data-language="typescript"> * @example</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"> * ```ts</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"> * inferLanguage('src/lib/foo.ts');   // 'typescript'</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"> * inferLanguage('.svelte');           // 'svelte'</span>
          </span>
        </div>
      </pre>
    </div>
    <div data-name="panel">
      <pre data-name="code" data-copy-selector="[data-name=&quot;line-content&quot;]">
        <div data-name="rows">
          <span data-name="line" data-line-status="add">
            <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="add">
            <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>
packages/runes/src/lang-map.ts → packages/runes/src/util.ts
42 *43 * Accepts a full path (`"src/lib/foo.ts"`), a bare extension with the dot44 * (`".ts"`), or a bare extension without (`"ts"`). Unknown extensions45 * return {@link FALLBACK_LANG}.46 *47 * @example48 * ```ts49 * inferLanguage('src/lib/foo.ts'); // 'typescript'50 * inferLanguage('.svelte'); // 'svelte'
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=&quot;line-content&quot;]" 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"> *</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"> * Accepts a full path (`&quot;src/lib/foo.ts&quot;`), a bare extension with the dot</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">44</span><span data-name="line-content" data-language="typescript" class="rf-diff__line-content"> * (`&quot;.ts&quot;`), or a bare extension without (`&quot;ts&quot;`). Unknown extensions</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"> * return {@link FALLBACK_LANG}.</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"> *</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">47</span><span data-name="line-content" data-language="typescript" class="rf-diff__line-content"> * @example</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"> * ```ts</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"> * inferLanguage('src/lib/foo.ts');   // '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">50</span><span data-name="line-content" data-language="typescript" class="rf-diff__line-content"> * inferLanguage('.svelte');           // 'svelte'</span></span></div></pre>
    </div>
    <div data-name="panel" class="rf-diff__panel">
      <pre data-name="code" data-copy-selector="[data-name=&quot;line-content&quot;]" class="rf-diff__code"><div data-name="rows" class="rf-diff__rows"><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">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="add" 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:

PatternOutcome
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)Rejectedfs.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',
});

/** Fallback language for extensions not covered by {@link LANG_MAP}. */
export const FALLBACK_LANG = 'text';

/**

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.

Attributes

AttributeTypeRequiredDescription
pathstringPath to the source file, relative to the project root. Rejected if it escapes the root.
linesstringLine range. Formats: "10-25", "10-" (to EOF), "-20" (from start), "10" (single line). 1-indexed, inclusive.
langstringSyntax-highlighting language hint. Overrides the extension-based inference.
linenumbersbooleanShow 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.
highlightstringRange(s) to emphasize without cropping — Shiki-style format: "74-78", "74-78,82,90-92". Indices are file coordinates (same frame as `lines=`). Use this when you want full context visible but want to draw the eye to specific lines. 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.

AttributeTypeRequiredDescription
bgstringBackground preset applied to this block
bg-fromstringGradient 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-tostringGradient end colour — a semantic token name
bg-viastringOptional 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.

AttributeTypeRequiredDescription
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.

AttributeTypeRequiredDescription
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.

AttributeTypeRequiredDescription
reveal"none" | "fade" | "slide" | "scale" | "blur"Scroll-reveal entrance character (none|fade|slide|scale|blur); the theme owns the choreography
staggerbooleanCascade this block's items in as it reveals (no-op on single-child runes)
spacing

Block-level rhythm override.

AttributeTypeRequiredDescription
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.

AttributeTypeRequiredDescription
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.

AttributeTypeRequiredDescription
tintstringColor 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.

AttributeTypeRequiredDescription
width"compact" | "narrow" | "content" | "wide" | "full"Maximum width constraint for this block
Not availableWhy
dropcap, readingthis rune declares no prose body
framethis rune declares neither a `frameTarget` nor a media section
prominencethis rune has no page-section header

No title attribute. Snippet's output is a fence; for a labelled chrome wrap in {% codegroup title="..." %}.

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.

## 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.

```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. For a labelled chrome, wrap the snippet in `{% codegroup %}`, which produces title-bar chrome around a single fence:

```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.

## Attributes

{% include file="rune-attributes.md" variables={r: "rune:snippet"} /%}

No `title` attribute. Snippet's output is a fence; for a labelled chrome wrap in `{% codegroup title="..." %}`.

## 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.
- [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.
  • Diff — before/after code comparison; consumes snippet children transparently.
  • Content variables$file.path for the view-source-of-current-page pattern.