Codegroup
Display code blocks with terminal-style chrome. Multiple fences become a tabbed view with language-based tab names. A single fence without labels renders as a clean code block with just the chrome — no tabs.
Basic usage
Tabs are automatically labeled by their code block language.
{% codegroup %}
```js
const x = 1;
```
```python
x = 1
```
{% /codegroup %}<section data-rune="code-group">
<div role="tablist" data-name="tabs">
<button data-name="tab" role="tab">
<span>JavaScript</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="js">
<code data-language="js">const x = 1;
</code>
</pre>
</div>
</div>
<div role="tabpanel" data-name="panel">
<div class="rf-codeblock">
<pre data-language="python">
<code data-language="python">x = 1
</code>
</pre>
</div>
</div>
</div>
</section>const x = 1;
x = 1
<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>JavaScript</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="js"><code data-language="js">const x = 1;
</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">x = 1
</code></pre>
</div>
</div>
</div>
</section>Custom labels
Use the labels attribute to override the default language-based tab names. Useful when multiple tabs share the same language.
{% codegroup labels="React, Vue, Svelte" %}
```jsx
export default function App() {
return <h1>Hello</h1>;
}
```
```html
<template>
<h1>Hello</h1>
</template>
```
```html
<h1>Hello</h1>
```
{% /codegroup %}<section data-rune="code-group">
<div role="tablist" data-name="tabs">
<button data-name="tab" role="tab">
<span>React</span>
</button>
<button data-name="tab" role="tab">
<span>Vue</span>
</button>
<button data-name="tab" role="tab">
<span>Svelte</span>
</button>
</div>
<div data-name="panels">
<div role="tabpanel" data-name="panel">
<div class="rf-codeblock">
<pre data-language="jsx">
<code data-language="jsx">export default function App() {
return <h1>Hello</h1>;
}
</code>
</pre>
</div>
</div>
<div role="tabpanel" data-name="panel">
<div class="rf-codeblock">
<pre data-language="html">
<code data-language="html"><template>
<h1>Hello</h1>
</template>
</code>
</pre>
</div>
</div>
<div role="tabpanel" data-name="panel">
<div class="rf-codeblock">
<pre data-language="html">
<code data-language="html"><h1>Hello</h1>
</code>
</pre>
</div>
</div>
</div>
</section>export default function App() {
return <h1>Hello</h1>;
}
<template>
<h1>Hello</h1>
</template>
<h1>Hello</h1>
<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>React</span>
</button>
<button data-name="tab" role="tab" class="rf-codegroup__tab">
<span>Vue</span>
</button>
<button data-name="tab" role="tab" class="rf-codegroup__tab">
<span>Svelte</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="jsx"><code data-language="jsx">export default function App() {
return <h1>Hello</h1>;
}
</code></pre>
</div>
</div>
<div role="tabpanel" data-name="panel" class="rf-codegroup__panel">
<div class="rf-codeblock">
<pre data-language="html"><code data-language="html"><template>
<h1>Hello</h1>
</template>
</code></pre>
</div>
</div>
<div role="tabpanel" data-name="panel" class="rf-codegroup__panel">
<div class="rf-codeblock">
<pre data-language="html"><code data-language="html"><h1>Hello</h1>
</code></pre>
</div>
</div>
</div>
</section>With title
Use the title attribute to display a filename or label in the topbar. With a single code fence and no labels, the codegroup renders as chrome-only — the topbar and code block without any tabs.
{% codegroup title="app.js" %}
```js
import express from 'express';
const app = express();
app.get('/', (req, res) => {
res.send('Hello World');
});
```
{% /codegroup %}<section data-rune="code-group" data-rune-fields="{"title":"app.js"}">
<div class="rf-codeblock">
<pre data-language="js">
<code data-language="js">import express from 'express';
const app = express();
app.get('/', (req, res) => {
res.send('Hello World');
});
</code>
</pre>
</div>
</section>import express from 'express';
const app = express();
app.get('/', (req, res) => {
res.send('Hello World');
});
<section class="rf-codegroup rf-codegroup--scroll" data-title="app.js" data-overflow="scroll" data-elevation="flat" data-rune="code-group" data-density="compact" data-code-host="true">
<div data-name="topbar" data-zone="topbar" data-zone-layout="bar" class="rf-codegroup__topbar" data-section="header">
<span data-meta-type="code">app.js</span>
</div>
<div class="rf-codeblock">
<pre data-language="js"><code data-language="js">import express from 'express';
const app = express();
app.get('/', (req, res) => {
res.send('Hello World');
});
</code></pre>
</div>
</section>To force a tab bar on a single fence, provide the labels attribute.
{% codegroup title="server" labels="Express" %}
```js
import express from 'express';
const app = express();
app.get('/', (req, res) => {
res.send('Hello World');
});
```
{% /codegroup %}<section data-rune="code-group" data-rune-fields="{"title":"server"}">
<div role="tablist" data-name="tabs">
<button data-name="tab" role="tab">
<span>Express</span>
</button>
</div>
<div data-name="panels">
<div role="tabpanel" data-name="panel">
<div class="rf-codeblock">
<pre data-language="js">
<code data-language="js">import express from 'express';
const app = express();
app.get('/', (req, res) => {
res.send('Hello World');
});
</code>
</pre>
</div>
</div>
</div>
</section>import express from 'express';
const app = express();
app.get('/', (req, res) => {
res.send('Hello World');
});
<section class="rf-codegroup rf-codegroup--scroll" data-title="server" data-overflow="scroll" data-elevation="flat" data-rune="code-group" data-density="compact" data-code-host="true">
<div data-name="topbar" data-zone="topbar" data-zone-layout="bar" class="rf-codegroup__topbar" data-section="header">
<span data-meta-type="code">server</span>
</div>
<div role="tablist" data-name="tabs" class="rf-codegroup__tabs">
<button data-name="tab" role="tab" class="rf-codegroup__tab">
<span>Express</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="js"><code data-language="js">import express from 'express';
const app = express();
app.get('/', (req, res) => {
res.send('Hello World');
});
</code></pre>
</div>
</div>
</div>
</section>Tab labels from fence annotations
When neither labels= nor a per-fence label annotation is set, codegroup derives each tab label from the fence's source annotation — automatically populated when the panel is a {% snippet %}, and authorable on hand-written fences. SPEC-062 / WORK-304.
{% codegroup %}
{% snippet path="packages/runes/src/lang-map.ts" lines="3-7" linenumbers=true highlight="4-5" /%}
{% snippet path="packages/runes/src/tags/codegroup.ts" lines="7-14" linenumbers=true highlight="8-9" /%}
{% /codegroup %}
Renders with tabs lang-map.ts:3-7 and codegroup.ts:7-14, each panel's gutter starting at the file's real line offset (3 and 7 respectively), and each panel's highlight range emphasized — all four annotations (source, lines, linenumbers, highlight) propagate through the snippet → fence → codegroup chain uniformly:
*
* 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 pluginsconst languageNames: Record<string, string> = {
js: 'JavaScript', ts: 'TypeScript', py: 'Python',
rb: 'Ruby', rs: 'Rust', go: 'Go', sh: 'Shell',
bash: 'Bash', zsh: 'Zsh', shell: 'Shell',
html: 'HTML', css: 'CSS', json: 'JSON', yaml: 'YAML',
sql: 'SQL', swift: 'Swift', kt: 'Kotlin', java: 'Java',
cpp: 'C++', c: 'C', cs: 'C#', php: 'PHP',
};The label resolution chain (first match wins):
- Group-level
labels=— positional override (labels="A, B, C"). - Per-fence
labelannotation —```ts {% label="SiteConfig" %}. - Derived from
source— basename of the path, with:linessuffix whenlines=is also set (e.g.theme.ts:74-125). - Prettified language name — today's default (
JavaScript,Python, etc.).
Same composition story snippet has elsewhere, just propagated through the fence-annotation surface.
Overflow control
Use the overflow attribute to control how long lines are handled. The default is scroll (horizontal scrollbar). Use wrap to wrap lines, or hide to clip without a scrollbar.
{% codegroup overflow="wrap" title="wrapped.ts" %}
```ts
const result = await fetchUserDataFromRemoteService(userId, { includeMetadata: true, resolveReferences: true, maxDepth: 3, timeout: 5000 });
```
{% /codegroup %}<section data-rune="code-group" data-rune-fields="{"title":"wrapped.ts","overflow":"wrap"}">
<div class="rf-codeblock">
<pre data-language="ts">
<code data-language="ts">const result = await fetchUserDataFromRemoteService(userId, { includeMetadata: true, resolveReferences: true, maxDepth: 3, timeout: 5000 });
</code>
</pre>
</div>
</section>const result = await fetchUserDataFromRemoteService(userId, { includeMetadata: true, resolveReferences: true, maxDepth: 3, timeout: 5000 });
<section class="rf-codegroup rf-codegroup--wrap" data-title="wrapped.ts" data-overflow="wrap" data-elevation="flat" data-rune="code-group" data-density="compact" data-code-host="true">
<div data-name="topbar" data-zone="topbar" data-zone-layout="bar" class="rf-codegroup__topbar" data-section="header">
<span data-meta-type="code">wrapped.ts</span>
</div>
<div class="rf-codeblock">
<pre data-language="ts"><code data-language="ts">const result = await fetchUserDataFromRemoteService(userId, { includeMetadata: true, resolveReferences: true, maxDepth: 3, timeout: 5000 });
</code></pre>
</div>
</section>Attributes
| Attribute | Type | Required | Description |
|---|---|---|---|
title | string | — | Title displayed above the code group |
labels | string | — | Comma-separated custom tab labels |
overflow | "scroll" | "wrap" | "hide" | — | How overflowing code is handled |
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) |
prominence
Header emphasis (SPEC-107). Scales a rune's page-section header; the skin maps it to a type register by attribute, so there is no BEM class.
| Attribute | Type | Required | Description |
|---|---|---|---|
prominence | "quiet" | "normal" | "prominent" | "display" | — | Section-header emphasis (only on page-section-header family 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 |