This rune is part of @refrakt-md/marketing. Install with npm install @refrakt-md/marketing and add "@refrakt-md/marketing" to the packages array in your refrakt.config.json.
Feature
Feature showcases. List items become feature definitions — bold text is the feature name, the following paragraph is the description.
Basic usage
A feature grid with named items and descriptions.
{% feature %}
what you get
## Structured data
- **Semantic runes**
Markdown primitives take on different meaning depending on the wrapping rune. Write Markdown — the rune decides what it means.
- **Type-safe output**
Every rune produces typed, validated content that your theme components can rely on.
- **Layout inheritance**
Define regions once in a parent layout. Child pages inherit and can override with prepend, append, or replace modes.
{% /feature %}<section data-field="content-section" data-rune="feature">
<meta content="stacked" data-field="layout">
<meta content="center" data-field="align">
<div data-name="content">
<header>
<p data-name="eyebrow">what you get</p>
<h2 id="structured-data" data-name="headline">Structured data</h2>
</header>
<dl data-layout="grid" data-columns="3">
<div data-name="feature-item">
<dt>
<span data-name="title">Semantic runes</span>
</dt>
<dd data-name="description">Markdown primitives take on different meaning depending on the wrapping rune. Write Markdown — the rune decides what it means.</dd>
</div>
<div data-name="feature-item">
<dt>
<span data-name="title">Type-safe output</span>
</dt>
<dd data-name="description">Every rune produces typed, validated content that your theme components can rely on.</dd>
</div>
<div data-name="feature-item">
<dt>
<span data-name="title">Layout inheritance</span>
</dt>
<dd data-name="description">Define regions once in a parent layout. Child pages inherit and can override with prepend, append, or replace modes.</dd>
</div>
</dl>
</div>
</section>what you get
Structured data
- Semantic runes
- Markdown primitives take on different meaning depending on the wrapping rune. Write Markdown — the rune decides what it means.
- Type-safe output
- Every rune produces typed, validated content that your theme components can rely on.
- Layout inheritance
- Define regions once in a parent layout. Child pages inherit and can override with prepend, append, or replace modes.
<section data-field="content-section" class="rf-feature rf-feature--stacked rf-feature--center rf-feature--full" data-layout="stacked" data-align="center" data-ratio="1 1" data-valign="top" data-gap="default" data-width="full" data-rune="feature" data-density="full" style="--split-ratio: 1fr 1fr; --split-valign: start; --split-gap: var(--rf-spacing-md)">
<div data-name="content" class="rf-feature__content">
<header data-name="preamble" class="rf-feature__preamble" data-section="preamble">
<p data-name="eyebrow" class="rf-feature__eyebrow">what you get</p>
<h2 id="structured-data" data-name="headline" class="rf-feature__headline" data-section="title">Structured data</h2>
</header>
<dl data-layout="grid" data-columns="3" data-name="definitions" class="rf-feature__definitions">
<div data-name="feature-item" class="rf-feature__feature-item">
<dt>
<span data-name="title">Semantic runes</span>
</dt>
<dd data-name="description" class="rf-feature__description">Markdown primitives take on different meaning depending on the wrapping rune. Write Markdown — the rune decides what it means.</dd>
</div>
<div data-name="feature-item" class="rf-feature__feature-item">
<dt>
<span data-name="title">Type-safe output</span>
</dt>
<dd data-name="description" class="rf-feature__description">Every rune produces typed, validated content that your theme components can rely on.</dd>
</div>
<div data-name="feature-item" class="rf-feature__feature-item">
<dt>
<span data-name="title">Layout inheritance</span>
</dt>
<dd data-name="description" class="rf-feature__description">Define regions once in a parent layout. Child pages inherit and can override with prepend, append, or replace modes.</dd>
</div>
</dl>
</div>
</section>With heading and description
Add a paragraph after the heading to introduce the feature set.
{% feature %}
what you get
## Built for content teams
Refrakt gives you the building blocks to ship structured content sites without fighting your framework.
- **Semantic runes**
Markdown primitives take on different meaning depending on the wrapping rune. Write Markdown — the rune decides what it means.
- **Type-safe output**
Every rune produces typed, validated content that your theme components can rely on.
{% /feature %}<section data-field="content-section" data-rune="feature">
<meta content="stacked" data-field="layout">
<meta content="center" data-field="align">
<div data-name="content">
<header>
<p data-name="eyebrow">what you get</p>
<h2 id="built-for-content-teams" data-name="headline">Built for content teams</h2>
<p data-name="blurb">Refrakt gives you the building blocks to ship structured content sites without fighting your framework.</p>
</header>
<dl data-layout="grid" data-columns="2">
<div data-name="feature-item">
<dt>
<span data-name="title">Semantic runes</span>
</dt>
<dd data-name="description">Markdown primitives take on different meaning depending on the wrapping rune. Write Markdown — the rune decides what it means.</dd>
</div>
<div data-name="feature-item">
<dt>
<span data-name="title">Type-safe output</span>
</dt>
<dd data-name="description">Every rune produces typed, validated content that your theme components can rely on.</dd>
</div>
</dl>
</div>
</section>what you get
Built for content teams
Refrakt gives you the building blocks to ship structured content sites without fighting your framework.
- Semantic runes
- Markdown primitives take on different meaning depending on the wrapping rune. Write Markdown — the rune decides what it means.
- Type-safe output
- Every rune produces typed, validated content that your theme components can rely on.
<section data-field="content-section" class="rf-feature rf-feature--stacked rf-feature--center rf-feature--full" data-layout="stacked" data-align="center" data-ratio="1 1" data-valign="top" data-gap="default" data-width="full" data-rune="feature" data-density="full" style="--split-ratio: 1fr 1fr; --split-valign: start; --split-gap: var(--rf-spacing-md)">
<div data-name="content" class="rf-feature__content">
<header data-name="preamble" class="rf-feature__preamble" data-section="preamble">
<p data-name="eyebrow" class="rf-feature__eyebrow">what you get</p>
<h2 id="built-for-content-teams" data-name="headline" class="rf-feature__headline" data-section="title">Built for content teams</h2>
<p data-name="blurb" class="rf-feature__blurb" data-section="description">Refrakt gives you the building blocks to ship structured content sites without fighting your framework.</p>
</header>
<dl data-layout="grid" data-columns="2" data-name="definitions" class="rf-feature__definitions">
<div data-name="feature-item" class="rf-feature__feature-item">
<dt>
<span data-name="title">Semantic runes</span>
</dt>
<dd data-name="description" class="rf-feature__description">Markdown primitives take on different meaning depending on the wrapping rune. Write Markdown — the rune decides what it means.</dd>
</div>
<div data-name="feature-item" class="rf-feature__feature-item">
<dt>
<span data-name="title">Type-safe output</span>
</dt>
<dd data-name="description" class="rf-feature__description">Every rune produces typed, validated content that your theme components can rely on.</dd>
</div>
</dl>
</div>
</section>Split layout
Use layout="split" to place definitions alongside a media column — an image, code block, or any other content. A horizontal rule (---) separates the two sections.
{% feature layout="split" %}
why Refrakt
## Built for versatility
- **Zero config**
Drop Markdown files into your content directory. Routing, layouts, and type generation happen automatically.
- **Framework agnostic**
The identity transform is pure data. Render with Svelte, React, or anything else.
---
{% codegroup %}
```yaml title="refrakt.config.ts"
export default {
content: './content',
theme: '@refrakt-md/lumina'
}
```
```md title="content/index.md"
---
title: Home
---
{% hero %}
# Welcome
{% /hero %}
```
{% /codegroup %}
{% /feature %}<section data-field="content-section" data-rune="feature">
<meta content="split" data-field="layout">
<meta content="center" data-field="align">
<meta content="1 1" data-field="ratio">
<meta content="top" data-field="valign">
<div data-name="content">
<header>
<p data-name="eyebrow">why Refrakt</p>
<h2 id="built-for-versatility" data-name="headline">Built for versatility</h2>
</header>
<dl>
<div data-name="feature-item">
<dt>
<span data-name="title">Zero config</span>
</dt>
<dd data-name="description">Drop Markdown files into your content directory. Routing, layouts, and type generation happen automatically.</dd>
</div>
<div data-name="feature-item">
<dt>
<span data-name="title">Framework agnostic</span>
</dt>
<dd data-name="description">The identity transform is pure data. Render with Svelte, React, or anything else.</dd>
</div>
</dl>
</div>
<div data-name="media">
<section data-rune="code-group">
<div role="tablist" data-name="tabs">
<button data-name="tab" role="tab">
<span>YAML</span>
</button>
<button data-name="tab" role="tab">
<span>Md</span>
</button>
</div>
<div data-name="panels">
<div role="tabpanel" data-name="panel">
<div class="rf-codeblock">
<pre data-language="yaml">
<code data-language="yaml">export default {
content: './content',
theme: '@refrakt-md/lumina'
}
</code>
</pre>
</div>
</div>
<div role="tabpanel" data-name="panel">
<div class="rf-codeblock">
<pre data-language="md">
<code data-language="md">---
title: Home
---
{% hero %}
# Welcome
{% /hero %}
</code>
</pre>
</div>
</div>
</div>
</section>
</div>
</section>why Refrakt
Built for versatility
- Zero config
- Drop Markdown files into your content directory. Routing, layouts, and type generation happen automatically.
- Framework agnostic
- The identity transform is pure data. Render with Svelte, React, or anything else.
export default {
content: './content',
theme: '@refrakt-md/lumina'
}
---
title: Home
---
{% hero %}
# Welcome
{% /hero %}
<section data-field="content-section" class="rf-feature rf-feature--split rf-feature--center rf-feature--full" data-layout="split" data-align="center" data-ratio="1 1" data-valign="top" data-gap="default" data-width="full" data-rune="feature" data-density="full" style="--split-ratio: 1fr 1fr; --split-valign: start; --split-gap: var(--rf-spacing-md)">
<div data-name="content" class="rf-feature__content">
<header data-name="preamble" class="rf-feature__preamble" data-section="preamble">
<p data-name="eyebrow" class="rf-feature__eyebrow">why Refrakt</p>
<h2 id="built-for-versatility" data-name="headline" class="rf-feature__headline" data-section="title">Built for versatility</h2>
</header>
<dl data-name="definitions" class="rf-feature__definitions">
<div data-name="feature-item" class="rf-feature__feature-item">
<dt>
<span data-name="title">Zero config</span>
</dt>
<dd data-name="description" class="rf-feature__description">Drop Markdown files into your content directory. Routing, layouts, and type generation happen automatically.</dd>
</div>
<div data-name="feature-item" class="rf-feature__feature-item">
<dt>
<span data-name="title">Framework agnostic</span>
</dt>
<dd data-name="description" class="rf-feature__description">The identity transform is pure data. Render with Svelte, React, or anything else.</dd>
</div>
</dl>
</div>
<div data-name="media" class="rf-feature__media" data-section="media" data-media="cover">
<section class="rf-codegroup rf-codegroup--scroll" data-overflow="scroll" data-rune="code-group" data-density="compact">
<div data-name="topbar" class="rf-codegroup__topbar" data-section="header">
<span data-name="dot" class="rf-codegroup__dot"></span>
<span data-name="dot" class="rf-codegroup__dot"></span>
<span data-name="dot" class="rf-codegroup__dot"></span>
</div>
<div role="tablist" data-name="tabs" class="rf-codegroup__tabs">
<button data-name="tab" role="tab" class="rf-codegroup__tab">
<span>YAML</span>
</button>
<button data-name="tab" role="tab" class="rf-codegroup__tab">
<span>Md</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="yaml"><code data-language="yaml">export default {
content: './content',
theme: '@refrakt-md/lumina'
}
</code></pre>
</div>
</div>
<div role="tabpanel" data-name="panel" class="rf-codegroup__panel">
<div class="rf-codeblock">
<pre data-language="md"><code data-language="md">---
title: Home
---
{% hero %}
# Welcome
{% /hero %}
</code></pre>
</div>
</div>
</div>
</section>
</div>
</section>Split reversed
Use layout="split-reverse" to swap the column order — media on the left, definitions on the right.
{% feature layout="split-reverse" %}
pipeline
## How it works
- **Parse**
Markdoc turns your Markdown into an AST.
- **Transform**
Rune schemas reinterpret the AST nodes based on context.
- **Render**
The identity transform adds BEM classes and structural elements. Your theme takes it from there.
---
{% codegroup %}
```ts title="transform.ts"
const ast = Markdoc.parse(content);
const tree = Markdoc.transform(ast, {
tags,
nodes
});
```
```html title="output.html"
<section class="rf-hero">
<header class="rf-hero__body">
<h1>Welcome</h1>
</header>
</section>
```
{% /codegroup %}
{% /feature %}<section data-field="content-section" data-rune="feature">
<meta content="split-reverse" data-field="layout">
<meta content="center" data-field="align">
<meta content="1 1" data-field="ratio">
<meta content="top" data-field="valign">
<div data-name="content">
<header>
<p data-name="eyebrow">pipeline</p>
<h2 id="how-it-works" data-name="headline">How it works</h2>
</header>
<dl>
<div data-name="feature-item">
<dt>
<span data-name="title">Parse</span>
</dt>
<dd data-name="description">Markdoc turns your Markdown into an AST.</dd>
</div>
<div data-name="feature-item">
<dt>
<span data-name="title">Transform</span>
</dt>
<dd data-name="description">Rune schemas reinterpret the AST nodes based on context.</dd>
</div>
<div data-name="feature-item">
<dt>
<span data-name="title">Render</span>
</dt>
<dd data-name="description">The identity transform adds BEM classes and structural elements. Your theme takes it from there.</dd>
</div>
</dl>
</div>
<div data-name="media">
<section data-rune="code-group">
<div role="tablist" data-name="tabs">
<button data-name="tab" role="tab">
<span>TypeScript</span>
</button>
<button data-name="tab" role="tab">
<span>HTML</span>
</button>
</div>
<div data-name="panels">
<div role="tabpanel" data-name="panel">
<div class="rf-codeblock">
<pre data-language="ts">
<code data-language="ts">const ast = Markdoc.parse(content);
const tree = Markdoc.transform(ast, {
tags,
nodes
});
</code>
</pre>
</div>
</div>
<div role="tabpanel" data-name="panel">
<div class="rf-codeblock">
<pre data-language="html">
<code data-language="html"><section class="rf-hero">
<header class="rf-hero__body">
<h1>Welcome</h1>
</header>
</section>
</code>
</pre>
</div>
</div>
</div>
</section>
</div>
</section>pipeline
How it works
- Parse
- Markdoc turns your Markdown into an AST.
- Transform
- Rune schemas reinterpret the AST nodes based on context.
- Render
- The identity transform adds BEM classes and structural elements. Your theme takes it from there.
const ast = Markdoc.parse(content);
const tree = Markdoc.transform(ast, {
tags,
nodes
});
<section class="rf-hero">
<header class="rf-hero__body">
<h1>Welcome</h1>
</header>
</section>
<section data-field="content-section" class="rf-feature rf-feature--split-reverse rf-feature--center rf-feature--full" data-layout="split-reverse" data-align="center" data-ratio="1 1" data-valign="top" data-gap="default" data-width="full" data-rune="feature" data-density="full" style="--split-ratio: 1fr 1fr; --split-valign: start; --split-gap: var(--rf-spacing-md)">
<div data-name="content" class="rf-feature__content">
<header data-name="preamble" class="rf-feature__preamble" data-section="preamble">
<p data-name="eyebrow" class="rf-feature__eyebrow">pipeline</p>
<h2 id="how-it-works" data-name="headline" class="rf-feature__headline" data-section="title">How it works</h2>
</header>
<dl data-name="definitions" class="rf-feature__definitions">
<div data-name="feature-item" class="rf-feature__feature-item">
<dt>
<span data-name="title">Parse</span>
</dt>
<dd data-name="description" class="rf-feature__description">Markdoc turns your Markdown into an AST.</dd>
</div>
<div data-name="feature-item" class="rf-feature__feature-item">
<dt>
<span data-name="title">Transform</span>
</dt>
<dd data-name="description" class="rf-feature__description">Rune schemas reinterpret the AST nodes based on context.</dd>
</div>
<div data-name="feature-item" class="rf-feature__feature-item">
<dt>
<span data-name="title">Render</span>
</dt>
<dd data-name="description" class="rf-feature__description">The identity transform adds BEM classes and structural elements. Your theme takes it from there.</dd>
</div>
</dl>
</div>
<div data-name="media" class="rf-feature__media" data-section="media" data-media="cover">
<section class="rf-codegroup rf-codegroup--scroll" data-overflow="scroll" data-rune="code-group" data-density="compact">
<div data-name="topbar" class="rf-codegroup__topbar" data-section="header">
<span data-name="dot" class="rf-codegroup__dot"></span>
<span data-name="dot" class="rf-codegroup__dot"></span>
<span data-name="dot" class="rf-codegroup__dot"></span>
</div>
<div role="tablist" data-name="tabs" class="rf-codegroup__tabs">
<button data-name="tab" role="tab" class="rf-codegroup__tab">
<span>TypeScript</span>
</button>
<button data-name="tab" role="tab" class="rf-codegroup__tab">
<span>HTML</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="ts"><code data-language="ts">const ast = Markdoc.parse(content);
const tree = Markdoc.transform(ast, {
tags,
nodes
});
</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"><section class="rf-hero">
<header class="rf-hero__body">
<h1>Welcome</h1>
</header>
</section>
</code></pre>
</div>
</div>
</div>
</section>
</div>
</section>Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
layout | string | stacked | Layout mode: stacked, split, or split-reverse |
align | string | center | Content alignment: left, center, or right |
ratio | string | 1 1 | Column width ratio in split layout (e.g., 2 1, 1 2) |
valign | string | top | Vertical alignment in split layout: top, center, or bottom |
gap | string | default | Gap between columns: none, tight, default, or loose |
collapse | string | — | Collapse to single column at breakpoint: sm, md, lg, or never |
Section header
Feature supports an optional eyebrow, headline, and blurb above the section above feature items. Place a short paragraph or heading before the main content to use them. See Page sections for the full syntax.
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 |