This rune is part of @refrakt-md/marketing. Install with npm install @refrakt-md/marketing and add "@refrakt-md/marketing" to the plugins 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" data-rune-fields="{"media-position":"bottom","layout":"grid","align":"center"}">
<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-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--center rf-feature--full" data-media-position="bottom" data-layout="grid" data-align="center" data-width="full" data-content-measure="anchored" data-elevation="flush" data-rune="feature" data-density="full">
<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-columns="3" data-name="items" class="rf-feature__items">
<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" data-rune-fields="{"media-position":"bottom","layout":"grid","align":"center"}">
<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-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--center rf-feature--full" data-media-position="bottom" data-layout="grid" data-align="center" data-width="full" data-content-measure="anchored" data-elevation="flush" data-rune="feature" data-density="full">
<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-columns="2" data-name="items" class="rf-feature__items">
<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>Media on the right (end)
Use media-position="end" to place definitions alongside a media column — an image, code block, or any other content. A horizontal rule (---) separates the media block (first) from the content block (second).
{% feature media-position="end" %}
{% 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 %}
---
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.
{% /feature %}<section data-field="content-section" data-rune="feature" data-rune-fields="{"media-position":"end","layout":"list","align":"center"}">
<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 data-columns="2">
<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--center rf-feature--full" data-media-position="end" data-layout="list" data-align="center" data-width="full" data-content-measure="anchored" data-elevation="flush" data-rune="feature" data-density="full">
<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-columns="2" data-name="items" class="rf-feature__items">
<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" data-guest-fit="bleed">
<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>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>Media on the left (start)
Use media-position="start" to swap the column order — media on the left, definitions on the right.
{% feature media-position="start" %}
{% 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 %}
---
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.
{% /feature %}<section data-field="content-section" data-rune="feature" data-rune-fields="{"media-position":"start","layout":"list","align":"center"}">
<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 data-columns="3">
<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--center rf-feature--full" data-media-position="start" data-layout="list" data-align="center" data-width="full" data-content-measure="anchored" data-elevation="flush" data-rune="feature" data-density="full">
<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-columns="3" data-name="items" class="rf-feature__items">
<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" data-guest-fit="bleed">
<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>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>Item arrangement (layout)
The layout axis controls how feature-items are arranged, independently of where the media sits:
layout="grid"— items tile as a grid.layout="list"— items stack in a single column.layout="carousel"— items lay out on a horizontal scroll-snap track with prev/next nav (the shared carousel layout mode), at every viewport.
When layout is unset it derives from media-position (stacked media → grid, beside media → list), so existing content is unchanged. Set it explicitly to override. For example, layout="carousel" lays the feature-items out on a horizontal scroll-snap track with prev/next nav, at every viewport:
{% feature layout="carousel" %}
why Refrakt
## Built for versatility
- **Zero config**
Drop Markdown files into your content directory.
- **Framework agnostic**
The identity transform is pure data — render with anything.
- **Type-safe output**
Every rune produces validated content.
- **Incremental adoption**
Add runes to existing Markdown one block at a time.
{% /feature %}<section data-field="content-section" data-rune="feature" data-rune-fields="{"media-position":"bottom","layout":"carousel","align":"center"}">
<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 data-columns="4">
<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.</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 anything.</dd>
</div>
<div data-name="feature-item">
<dt>
<span data-name="title">Type-safe output</span>
</dt>
<dd data-name="description">Every rune produces validated content.</dd>
</div>
<div data-name="feature-item">
<dt>
<span data-name="title">Incremental adoption</span>
</dt>
<dd data-name="description">Add runes to existing Markdown one block at a time.</dd>
</div>
</dl>
</div>
</section>why Refrakt
Built for versatility
- Zero config
- Drop Markdown files into your content directory.
- Framework agnostic
- The identity transform is pure data — render with anything.
- Type-safe output
- Every rune produces validated content.
- Incremental adoption
- Add runes to existing Markdown one block at a time.
<section data-field="content-section" class="rf-feature rf-feature--center rf-feature--full" data-media-position="bottom" data-layout="carousel" data-align="center" data-width="full" data-content-measure="anchored" data-elevation="flush" data-rune="feature" data-density="full">
<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-columns="4" data-name="items" class="rf-feature__items">
<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.</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 anything.</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 validated content.</dd>
</div>
<div data-name="feature-item" class="rf-feature__feature-item">
<dt>
<span data-name="title">Incremental adoption</span>
</dt>
<dd data-name="description" class="rf-feature__description">Add runes to existing Markdown one block at a time.</dd>
</div>
</dl>
</div>
</section>Carousel on mobile only (collapse-to)
A grid is great when the feature has room, but cramped on a phone. Add collapse-to="carousel" to make the collapsed form a swipeable scroll-snap row instead of a stack — a grid on desktop, a carousel on mobile. The breakpoint is the feature's own width (a container query), so it also flips when the feature sits in a narrow column. Switch the viewport selector to mobile to see it flip:
{% feature layout="grid" collapse-to="carousel" %}
why Refrakt
## Built for versatility
- **Zero config**
Drop Markdown files into your content directory.
- **Framework agnostic**
The identity transform is pure data — render with anything.
- **Type-safe output**
Every rune produces validated content.
- **Incremental adoption**
Add runes to existing Markdown one block at a time.
{% /feature %}<section data-field="content-section" data-rune="feature" data-rune-fields="{"media-position":"bottom","layout":"grid","collapse-to":"carousel","align":"center"}">
<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 data-columns="4">
<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.</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 anything.</dd>
</div>
<div data-name="feature-item">
<dt>
<span data-name="title">Type-safe output</span>
</dt>
<dd data-name="description">Every rune produces validated content.</dd>
</div>
<div data-name="feature-item">
<dt>
<span data-name="title">Incremental adoption</span>
</dt>
<dd data-name="description">Add runes to existing Markdown one block at a time.</dd>
</div>
</dl>
</div>
</section>why Refrakt
Built for versatility
- Zero config
- Drop Markdown files into your content directory.
- Framework agnostic
- The identity transform is pure data — render with anything.
- Type-safe output
- Every rune produces validated content.
- Incremental adoption
- Add runes to existing Markdown one block at a time.
<section data-field="content-section" class="rf-feature rf-feature--center rf-feature--full" data-media-position="bottom" data-layout="grid" data-collapse-to="carousel" data-align="center" data-width="full" data-content-measure="anchored" data-elevation="flush" data-rune="feature" data-density="full">
<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-columns="4" data-name="items" class="rf-feature__items">
<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.</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 anything.</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 validated content.</dd>
</div>
<div data-name="feature-item" class="rf-feature__feature-item">
<dt>
<span data-name="title">Incremental adoption</span>
</dt>
<dd data-name="description" class="rf-feature__description">Add runes to existing Markdown one block at a time.</dd>
</div>
</dl>
</div>
</section>Attributes
| Attribute | Type | Required | Description |
|---|---|---|---|
align | "left" | "center" | "right" | — | Horizontal alignment of headline and body text |
layout | "grid" | "list" | "carousel" | — | Arrangement of feature-items: grid (tiled), list (single column), or carousel (scroll-snap track). Defaults from media-position when unset. |
collapse-to | "stack" | "carousel" | — | Collapsed (mobile) form below the `collapse` breakpoint: stack (default) or carousel (a scroll-snap row). |
justify | string | — |
Inherited from the split layout preset
| Attribute | Type | Required | Description |
|---|---|---|---|
media-position | "top" | "bottom" | "start" | "end" | "cover" | — | Where the media zone sits relative to the content: above (top), below (bottom), or beside (start/end) |
media-ratio | "1/3" | "2/5" | "1/2" | "3/5" | "2/3" | — | Media zone’s share of the row width when media is beside content (start/end) |
valign | "top" | "center" | "bottom" | "stretch" | — | Cross-axis alignment when media is beside content (start/end); applies to the shorter zone |
collapse | "sm" | "md" | "lg" | "never" | — | Breakpoint at which side-by-side layouts collapse to a single stacked column |
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 |
frame
Surface chrome (SPEC-086): a named preset from the theme registry with inline `frame-*` overrides layered on top.
| Attribute | Type | Required | Description |
|---|---|---|---|
frame | string | — | Named frame preset presenting this block's media surface |
frame-anchor | string | — | Crop focal point when the guest is cut (object-position) |
frame-aspect | string | — | Aspect ratio of the framed media, e.g. "16/9" |
frame-displace | "top" | "bottom" | "end" | "bottom-end" | "top-end" | — | Edge/corner the framed guest moves toward |
frame-displace-mode | "peek" | "bleed" | — | How displacement renders: `peek` (default) translates the guest visually inside its frame target; `bleed` uses negative margin on the media zone so following layout pulls up — extends past a section like a hero without leaving a gap above |
frame-offset | "none" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "4xl" | — | Displacement distance (non-linear named scale: sm…xl ride the block-spacing tokens, 2xl/3xl/4xl step up to section-spacing for bleed-mode displacements that clear a section edge) |
frame-overflow | "clip" | "bleed" | — | How the frame handles a guest whose content is wider than it: `clip` (default) keeps it inside the rounded frame; `bleed` runs an overflowing guest out to the layout edge on a narrow viewport so it reads as cropped by the screen (a bleed host only — hero/feature) |
frame-oversize | string | — | How far the guest exceeds its slot (scale factor) |
frame-place | string | — | Guest-box alignment in the slot (e.g. "left top") |
frame-shadow | "none" | "sm" | "md" | "lg" | — | Silhouette drop-shadow strength for the framed media |
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 |
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.
Layout attributes
The body splits on --- into media → content zones (media-first in source). media-position controls visual placement independently of source order. Item arrangement (grid vs. single column) is the separate layout axis — when unset it derives from media-position (stacked → grid, beside → list), but the two are independently controllable.
| Attribute | Type | Default | Description |
|---|---|---|---|
media-position | string | bottom | Where the media sits: top, bottom (the default — media beneath the text), start (left), end (right) |
media-ratio | string | — | Media's share of the row when beside content (start/end): 1/3, 2/5, 1/2, 3/5, 2/3 |
valign | string | — | Cross-axis alignment when media is beside content: top, center, bottom, stretch |
collapse | string | — | Breakpoint at which beside layouts collapse to a stack and a grid of items reflows to one column: sm, md, lg, never |