Diff
Renders the difference between two code blocks. The first code block is the "before" state, the second is the "after" state.
Split diff
Use mode="split" to show before and after side by side. Lines that match appear on both sides, removed lines carry a red edge on the left, and added lines carry a green edge on the right — the column they sit in disambiguates direction, so no per-column label is needed. Pass an optional title to render a filename or context line as a full-width header above the diff.
{% diff mode="split" language="javascript" title="src/server.js" %}
```javascript
import express from 'express';
const app = express();
app.get('/users', (req, res) => {
const users = db.query('SELECT * FROM users');
res.json(users);
});
app.listen(3000);
```
```javascript
import express from 'express';
const app = express();
app.use(express.json());
app.get('/users', async (req, res) => {
const { page = 1, limit = 20 } = req.query;
const users = await db.query('SELECT * FROM users LIMIT ? OFFSET ?', [limit, (page - 1) * limit]);
res.json({ users, page, limit });
});
app.listen(3000);
```
{% /diff %}<div data-rune="diff" data-rune-fields="{"mode":"split","language":"javascript"}" data-code-host>
<div data-name="header">src/server.js</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="equal">
<span data-name="gutter-num" data-side="before">1</span>
<span data-name="line-content" data-language="javascript">import express from 'express';</span>
</span>
<span data-name="line" data-line-status="equal">
<span data-name="gutter-num" data-side="before">2</span>
<span data-name="line-content" data-language="javascript"></span>
</span>
<span data-name="line" data-line-status="equal">
<span data-name="gutter-num" data-side="before">3</span>
<span data-name="line-content" data-language="javascript">const app = express();</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="equal">
<span data-name="gutter-num" data-side="before">4</span>
<span data-name="line-content" data-language="javascript"></span>
</span>
<span data-name="line" data-line-status="remove">
<span data-name="gutter-num" data-side="before">5</span>
<span data-name="line-content" data-language="javascript">app.get('/users', (req, res) => {</span>
</span>
<span data-name="line" data-line-status="remove">
<span data-name="gutter-num" data-side="before">6</span>
<span data-name="line-content" data-language="javascript"> const users = db.query('SELECT * FROM users');</span>
</span>
<span data-name="line" data-line-status="remove">
<span data-name="gutter-num" data-side="before">7</span>
<span data-name="line-content" data-language="javascript"> res.json(users);</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="equal">
<span data-name="gutter-num" data-side="before">8</span>
<span data-name="line-content" data-language="javascript">});</span>
</span>
<span data-name="line" data-line-status="equal">
<span data-name="gutter-num" data-side="before">9</span>
<span data-name="line-content" data-language="javascript"></span>
</span>
<span data-name="line" data-line-status="equal">
<span data-name="gutter-num" data-side="before">10</span>
<span data-name="line-content" data-language="javascript">app.listen(3000);</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="equal">
<span data-name="gutter-num" data-side="after">1</span>
<span data-name="line-content" data-language="javascript">import express from 'express';</span>
</span>
<span data-name="line" data-line-status="equal">
<span data-name="gutter-num" data-side="after">2</span>
<span data-name="line-content" data-language="javascript"></span>
</span>
<span data-name="line" data-line-status="equal">
<span data-name="gutter-num" data-side="after">3</span>
<span data-name="line-content" data-language="javascript">const app = express();</span>
</span>
<span data-name="line" data-line-status="add">
<span data-name="gutter-num" data-side="after">4</span>
<span data-name="line-content" data-language="javascript">app.use(express.json());</span>
</span>
<span data-name="line" data-line-status="equal">
<span data-name="gutter-num" data-side="after">5</span>
<span data-name="line-content" data-language="javascript"></span>
</span>
<span data-name="line" data-line-status="add">
<span data-name="gutter-num" data-side="after">6</span>
<span data-name="line-content" data-language="javascript">app.get('/users', async (req, res) => {</span>
</span>
<span data-name="line" data-line-status="add">
<span data-name="gutter-num" data-side="after">7</span>
<span data-name="line-content" data-language="javascript"> const { page = 1, limit = 20 } = req.query;</span>
</span>
<span data-name="line" data-line-status="add">
<span data-name="gutter-num" data-side="after">8</span>
<span data-name="line-content" data-language="javascript"> const users = await db.query('SELECT * FROM users LIMIT ? OFFSET ?', [limit, (page - 1) * limit]);</span>
</span>
<span data-name="line" data-line-status="add">
<span data-name="gutter-num" data-side="after">9</span>
<span data-name="line-content" data-language="javascript"> res.json({ users, page, limit });</span>
</span>
<span data-name="line" data-line-status="equal">
<span data-name="gutter-num" data-side="after">10</span>
<span data-name="line-content" data-language="javascript">});</span>
</span>
<span data-name="line" data-line-status="equal">
<span data-name="gutter-num" data-side="after">11</span>
<span data-name="line-content" data-language="javascript"></span>
</span>
<span data-name="line" data-line-status="equal">
<span data-name="gutter-num" data-side="after">12</span>
<span data-name="line-content" data-language="javascript">app.listen(3000);</span>
</span>
</div>
</pre>
</div>
</div>
</div>1import express from 'express';23const app = express();45app.get('/users', (req, res) => {6 const users = db.query('SELECT * FROM users');7 res.json(users);8});910app.listen(3000);
1import express from 'express';23const app = express();4app.use(express.json());56app.get('/users', async (req, res) => {7 const { page = 1, limit = 20 } = req.query;8 const users = await db.query('SELECT * FROM users LIMIT ? OFFSET ?', [limit, (page - 1) * limit]);9 res.json({ users, page, limit });10});1112app.listen(3000);
<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">src/server.js</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="equal" class="rf-diff__line"><span data-name="gutter-num" data-side="before" class="rf-diff__gutter-num">1</span><span data-name="line-content" data-language="javascript" class="rf-diff__line-content">import express from 'express';</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">2</span><span data-name="line-content" data-language="javascript" 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">3</span><span data-name="line-content" data-language="javascript" class="rf-diff__line-content">const app = express();</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="equal" class="rf-diff__line"><span data-name="gutter-num" data-side="before" class="rf-diff__gutter-num">4</span><span data-name="line-content" data-language="javascript" 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">5</span><span data-name="line-content" data-language="javascript" class="rf-diff__line-content">app.get('/users', (req, res) => {</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">6</span><span data-name="line-content" data-language="javascript" class="rf-diff__line-content"> const users = db.query('SELECT * FROM users');</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">7</span><span data-name="line-content" data-language="javascript" class="rf-diff__line-content"> res.json(users);</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="equal" class="rf-diff__line"><span data-name="gutter-num" data-side="before" class="rf-diff__gutter-num">8</span><span data-name="line-content" data-language="javascript" 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">9</span><span data-name="line-content" data-language="javascript" 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">10</span><span data-name="line-content" data-language="javascript" class="rf-diff__line-content">app.listen(3000);</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="equal" class="rf-diff__line"><span data-name="gutter-num" data-side="after" class="rf-diff__gutter-num">1</span><span data-name="line-content" data-language="javascript" class="rf-diff__line-content">import express from 'express';</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">2</span><span data-name="line-content" data-language="javascript" 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">3</span><span data-name="line-content" data-language="javascript" class="rf-diff__line-content">const app = express();</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">4</span><span data-name="line-content" data-language="javascript" class="rf-diff__line-content">app.use(express.json());</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">5</span><span data-name="line-content" data-language="javascript" 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">6</span><span data-name="line-content" data-language="javascript" class="rf-diff__line-content">app.get('/users', async (req, res) => {</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">7</span><span data-name="line-content" data-language="javascript" class="rf-diff__line-content"> const { page = 1, limit = 20 } = req.query;</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">8</span><span data-name="line-content" data-language="javascript" class="rf-diff__line-content"> const users = await db.query('SELECT * FROM users LIMIT ? OFFSET ?', [limit, (page - 1) * limit]);</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">9</span><span data-name="line-content" data-language="javascript" class="rf-diff__line-content"> res.json({ users, page, limit });</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">10</span><span data-name="line-content" data-language="javascript" 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">11</span><span data-name="line-content" data-language="javascript" 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">12</span><span data-name="line-content" data-language="javascript" class="rf-diff__line-content">app.listen(3000);</span></span></div></pre>
</div>
</div>
</div>Unified diff
The default mode shows changes in a single column with line numbers, +/- prefixes, and colored backgrounds.
{% diff mode="unified" language="css" %}
```css
.button {
display: inline-block;
padding: 8px 16px;
background: blue;
color: white;
border: none;
cursor: pointer;
}
```
```css
.button {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background: var(--color-primary);
color: white;
border: none;
border-radius: 0.375rem;
cursor: pointer;
}
```
{% /diff %}<div data-rune="diff" data-rune-fields="{"mode":"unified","language":"css"}" data-code-host>
<pre data-name="code" data-copy-selector="[data-name="line-content"]">
<div data-name="rows">
<span data-name="line" data-line-status="equal">
<span data-name="gutter-num" data-side="before">1</span>
<span data-name="gutter-num" data-side="after">1</span>
<span data-name="line-content" data-language="css">.button {</span>
</span>
<span data-name="line" data-line-status="remove">
<span data-name="gutter-num" data-side="before">2</span>
<span data-name="gutter-num" data-side="after"></span>
<span data-name="line-content" data-language="css"> display: inline-block;</span>
</span>
<span data-name="line" data-line-status="remove">
<span data-name="gutter-num" data-side="before">3</span>
<span data-name="gutter-num" data-side="after"></span>
<span data-name="line-content" data-language="css"> padding: 8px 16px;</span>
</span>
<span data-name="line" data-line-status="remove">
<span data-name="gutter-num" data-side="before">4</span>
<span data-name="gutter-num" data-side="after"></span>
<span data-name="line-content" data-language="css"> background: blue;</span>
</span>
<span data-name="line" data-line-status="add">
<span data-name="gutter-num" data-side="before"></span>
<span data-name="gutter-num" data-side="after">2</span>
<span data-name="line-content" data-language="css"> display: inline-flex;</span>
</span>
<span data-name="line" data-line-status="add">
<span data-name="gutter-num" data-side="before"></span>
<span data-name="gutter-num" data-side="after">3</span>
<span data-name="line-content" data-language="css"> align-items: center;</span>
</span>
<span data-name="line" data-line-status="add">
<span data-name="gutter-num" data-side="before"></span>
<span data-name="gutter-num" data-side="after">4</span>
<span data-name="line-content" data-language="css"> gap: 0.5rem;</span>
</span>
<span data-name="line" data-line-status="add">
<span data-name="gutter-num" data-side="before"></span>
<span data-name="gutter-num" data-side="after">5</span>
<span data-name="line-content" data-language="css"> padding: 0.5rem 1rem;</span>
</span>
<span data-name="line" data-line-status="add">
<span data-name="gutter-num" data-side="before"></span>
<span data-name="gutter-num" data-side="after">6</span>
<span data-name="line-content" data-language="css"> background: var(--color-primary);</span>
</span>
<span data-name="line" data-line-status="equal">
<span data-name="gutter-num" data-side="before">5</span>
<span data-name="gutter-num" data-side="after">7</span>
<span data-name="line-content" data-language="css"> color: white;</span>
</span>
<span data-name="line" data-line-status="equal">
<span data-name="gutter-num" data-side="before">6</span>
<span data-name="gutter-num" data-side="after">8</span>
<span data-name="line-content" data-language="css"> border: none;</span>
</span>
<span data-name="line" data-line-status="add">
<span data-name="gutter-num" data-side="before"></span>
<span data-name="gutter-num" data-side="after">9</span>
<span data-name="line-content" data-language="css"> border-radius: 0.375rem;</span>
</span>
<span data-name="line" data-line-status="equal">
<span data-name="gutter-num" data-side="before">7</span>
<span data-name="gutter-num" data-side="after">10</span>
<span data-name="line-content" data-language="css"> cursor: pointer;</span>
</span>
<span data-name="line" data-line-status="equal">
<span data-name="gutter-num" data-side="before">8</span>
<span data-name="gutter-num" data-side="after">11</span>
<span data-name="line-content" data-language="css">}</span>
</span>
</div>
</pre>
</div>11.button {2 display: inline-block;3 padding: 8px 16px;4 background: blue;2 display: inline-flex;3 align-items: center;4 gap: 0.5rem;5 padding: 0.5rem 1rem;6 background: var(--color-primary);57 color: white;68 border: none;9 border-radius: 0.375rem;710 cursor: pointer;811}
<div class="rf-diff rf-diff--unified" data-code-host data-mode="unified" data-rune="diff" data-density="full">
<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="equal" class="rf-diff__line"><span data-name="gutter-num" data-side="before" class="rf-diff__gutter-num">1</span><span data-name="gutter-num" data-side="after" class="rf-diff__gutter-num">1</span><span data-name="line-content" data-language="css" class="rf-diff__line-content">.button {</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">2</span><span data-name="gutter-num" data-side="after" class="rf-diff__gutter-num"></span><span data-name="line-content" data-language="css" class="rf-diff__line-content"> display: inline-block;</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">3</span><span data-name="gutter-num" data-side="after" class="rf-diff__gutter-num"></span><span data-name="line-content" data-language="css" class="rf-diff__line-content"> padding: 8px 16px;</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">4</span><span data-name="gutter-num" data-side="after" class="rf-diff__gutter-num"></span><span data-name="line-content" data-language="css" class="rf-diff__line-content"> background: blue;</span></span><span data-name="line" data-line-status="add" class="rf-diff__line"><span data-name="gutter-num" data-side="before" class="rf-diff__gutter-num"></span><span data-name="gutter-num" data-side="after" class="rf-diff__gutter-num">2</span><span data-name="line-content" data-language="css" class="rf-diff__line-content"> display: inline-flex;</span></span><span data-name="line" data-line-status="add" class="rf-diff__line"><span data-name="gutter-num" data-side="before" class="rf-diff__gutter-num"></span><span data-name="gutter-num" data-side="after" class="rf-diff__gutter-num">3</span><span data-name="line-content" data-language="css" class="rf-diff__line-content"> align-items: center;</span></span><span data-name="line" data-line-status="add" class="rf-diff__line"><span data-name="gutter-num" data-side="before" class="rf-diff__gutter-num"></span><span data-name="gutter-num" data-side="after" class="rf-diff__gutter-num">4</span><span data-name="line-content" data-language="css" class="rf-diff__line-content"> gap: 0.5rem;</span></span><span data-name="line" data-line-status="add" class="rf-diff__line"><span data-name="gutter-num" data-side="before" class="rf-diff__gutter-num"></span><span data-name="gutter-num" data-side="after" class="rf-diff__gutter-num">5</span><span data-name="line-content" data-language="css" class="rf-diff__line-content"> padding: 0.5rem 1rem;</span></span><span data-name="line" data-line-status="add" class="rf-diff__line"><span data-name="gutter-num" data-side="before" class="rf-diff__gutter-num"></span><span data-name="gutter-num" data-side="after" class="rf-diff__gutter-num">6</span><span data-name="line-content" data-language="css" class="rf-diff__line-content"> background: var(--color-primary);</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">5</span><span data-name="gutter-num" data-side="after" class="rf-diff__gutter-num">7</span><span data-name="line-content" data-language="css" class="rf-diff__line-content"> color: white;</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">6</span><span data-name="gutter-num" data-side="after" class="rf-diff__gutter-num">8</span><span data-name="line-content" data-language="css" class="rf-diff__line-content"> border: none;</span></span><span data-name="line" data-line-status="add" class="rf-diff__line"><span data-name="gutter-num" data-side="before" class="rf-diff__gutter-num"></span><span data-name="gutter-num" data-side="after" class="rf-diff__gutter-num">9</span><span data-name="line-content" data-language="css" class="rf-diff__line-content"> border-radius: 0.375rem;</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">7</span><span data-name="gutter-num" data-side="after" class="rf-diff__gutter-num">10</span><span data-name="line-content" data-language="css" class="rf-diff__line-content"> cursor: pointer;</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">8</span><span data-name="gutter-num" data-side="after" class="rf-diff__gutter-num">11</span><span data-name="line-content" data-language="css" class="rf-diff__line-content">}</span></span></div></pre>
</div>Header from fence source
When no title= is set, diff derives the header from each panel's fence source annotation (auto-populated when the panel is a {% snippet %}, authorable on hand-written fences). Matching paths collapse to a single label; differing paths render as before → after. SPEC-062 / WORK-304.
11const app = express();2app.get('/users', (req, res) => {3 res.json(db.query('SELECT * FROM users'));2app.use(express.json());3app.get('/users', async (req, res) => {4 res.json(await db.query('SELECT * FROM users LIMIT 20'));45});
The fence on each side annotates the diff header with src/server.js. Explicit title= always wins when you'd rather pin the label; differing source paths between sides render as before → after.
Line numbers using file coordinates
linenumbers=true on each panel's fence shifts that side's gutter to start at the fence's lines offset rather than 1 — so a diff between two slices of the same file shows the actual file line numbers per side.
74contentDir: string;75theme: string | SiteThemeConfig;76target?: string;77overrides?: Record<string, string>;78routeRules?: RouteRule[];
74contentDir: string;75theme: string | SiteThemeConfig;76target?: string;77overrides?: Record<string, string>;78routeRules?: RouteRule[];79entityRoutes?: EntityRoute[];
Both gutter columns start at 74 (matching the file's real coordinates), the header reads theme.ts, and the added line shows in the right column with entityRoutes highlighted as the new value.
highlight= on a fence inside {% diff %} is silently ignored. Diff's add/remove channel is the primary line-level signal; a separate highlight layer on top would muddy the +/- semantics. Use a standalone {% snippet highlight="..." /%} instead when you want emphasis.
Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
mode | string | unified | Display mode: unified, split, or inline |
language | string | — | Language for syntax highlighting |
title | string | — | Optional title or filename rendered as a full-width header above the diff |
Common attributes
All block runes share these attributes for layout and theming.
| Attribute | Type | Default | Description |
|---|---|---|---|
width | string | content | Page grid width: content, wide, or full |
spacing | string | — | Vertical spacing: flush, tight, default, loose, or breathe |
inset | string | — | Horizontal padding: flush, tight, default, loose, or breathe |
tint | string | — | Named colour tint from theme configuration |
tint-mode | string | auto | Colour scheme override: auto, dark, or light |
bg | string | — | Named background preset from theme configuration |