Grid
Flexible grid layout. Children separated by horizontal rules (---) become cells arranged in a responsive CSS grid. Supports explicit column spans, auto-fill mode, masonry, aspect ratio enforcement, and responsive collapse.
Basic usage
Equal columns created by separating content with horizontal rules.
{% grid %}
**First column.** This content sits in the first cell of the grid layout.
---
**Second column.** Grid cells are separated by horizontal rules in the Markdown source.
---
**Third column.** The grid automatically distributes available space across columns.
{% /grid %}<section data-rune="grid">
<div data-layout="grid">
<div data-colspan="1" data-name="cell">
<p>
<strong marker="**">First column.</strong>
This content sits in the first cell of the grid layout.
</p>
</div>
<div data-colspan="1" data-name="cell">
<p>
<strong marker="**">Second column.</strong>
Grid cells are separated by horizontal rules in the Markdown source.
</p>
</div>
<div data-colspan="1" data-name="cell">
<p>
<strong marker="**">Third column.</strong>
The grid automatically distributes available space across columns.
</p>
</div>
</div>
</section>First column. This content sits in the first cell of the grid layout.
Second column. Grid cells are separated by horizontal rules in the Markdown source.
Third column. The grid automatically distributes available space across columns.
<section class="rf-grid rf-grid--columns" data-mode="columns" data-rune="grid" data-density="full">
<div data-layout="grid">
<div data-colspan="1" data-name="cell">
<p>
<strong marker="**">First column.</strong>
This content sits in the first cell of the grid layout.
</p>
</div>
<div data-colspan="1" data-name="cell">
<p>
<strong marker="**">Second column.</strong>
Grid cells are separated by horizontal rules in the Markdown source.
</p>
</div>
<div data-colspan="1" data-name="cell">
<p>
<strong marker="**">Third column.</strong>
The grid automatically distributes available space across columns.
</p>
</div>
</div>
</section>Unequal columns
Use the spans attribute to control column widths. Values are space-separated span sizes.
{% grid spans="2 1" %}
**Wide column.** This column spans two units of the grid, taking up twice the space of the narrow column beside it.
---
**Narrow column.** This column takes one unit.
{% /grid %}<section data-rune="grid">
<div data-layout="grid">
<div data-colspan="2" data-name="cell">
<p>
<strong marker="**">Wide column.</strong>
This column spans two units of the grid, taking up twice the space of the narrow column beside it.
</p>
</div>
<div data-colspan="1" data-name="cell">
<p>
<strong marker="**">Narrow column.</strong>
This column takes one unit.
</p>
</div>
</div>
</section>Wide column. This column spans two units of the grid, taking up twice the space of the narrow column beside it.
Narrow column. This column takes one unit.
<section class="rf-grid rf-grid--columns" data-mode="columns" data-rune="grid" data-density="full">
<div data-layout="grid">
<div data-colspan="2" data-name="cell">
<p>
<strong marker="**">Wide column.</strong>
This column spans two units of the grid, taking up twice the space of the narrow column beside it.
</p>
</div>
<div data-colspan="1" data-name="cell">
<p>
<strong marker="**">Narrow column.</strong>
This column takes one unit.
</p>
</div>
</div>
</section>Gap
Control spacing between cells with the gap attribute.
{% grid gap="tight" %}
**Tight gap.** Less space between cells.
---
**Second cell.** The gap is smaller than default.
---
**Third cell.** Useful for compact layouts.
{% /grid %}<section data-rune="grid" data-rune-fields="{"gap":"tight"}">
<div data-layout="grid">
<div data-colspan="1" data-name="cell">
<p>
<strong marker="**">Tight gap.</strong>
Less space between cells.
</p>
</div>
<div data-colspan="1" data-name="cell">
<p>
<strong marker="**">Second cell.</strong>
The gap is smaller than default.
</p>
</div>
<div data-colspan="1" data-name="cell">
<p>
<strong marker="**">Third cell.</strong>
Useful for compact layouts.
</p>
</div>
</div>
</section>Tight gap. Less space between cells.
Second cell. The gap is smaller than default.
Third cell. Useful for compact layouts.
<section class="rf-grid rf-grid--columns" data-mode="columns" data-gap="tight" data-rune="grid" data-density="full" style="--grid-gap: var(--rf-spacing-sm)">
<div data-layout="grid">
<div data-colspan="1" data-name="cell">
<p>
<strong marker="**">Tight gap.</strong>
Less space between cells.
</p>
</div>
<div data-colspan="1" data-name="cell">
<p>
<strong marker="**">Second cell.</strong>
The gap is smaller than default.
</p>
</div>
<div data-colspan="1" data-name="cell">
<p>
<strong marker="**">Third cell.</strong>
Useful for compact layouts.
</p>
</div>
</div>
</section>Alignment
Vertically align cells within their row using the valign attribute. Useful when cells have different heights.
{% grid valign="center" spans="2 1" %}
**Tall cell.** This cell has more content and takes up more vertical space. The adjacent cell will be vertically centred relative to this one.
Additional paragraph to increase the height of this cell.
---
**Centred.** This shorter cell is vertically centred.
{% /grid %}<section data-rune="grid" data-rune-fields="{"valign":"center"}">
<div data-layout="grid">
<div data-colspan="2" data-name="cell">
<p>
<strong marker="**">Tall cell.</strong>
This cell has more content and takes up more vertical space. The adjacent cell will be vertically centred relative to this one.
</p>
<p>Additional paragraph to increase the height of this cell.</p>
</div>
<div data-colspan="1" data-name="cell">
<p>
<strong marker="**">Centred.</strong>
This shorter cell is vertically centred.
</p>
</div>
</div>
</section>Tall cell. This cell has more content and takes up more vertical space. The adjacent cell will be vertically centred relative to this one.
Additional paragraph to increase the height of this cell.
Centred. This shorter cell is vertically centred.
<section class="rf-grid rf-grid--columns" data-mode="columns" data-valign="center" data-rune="grid" data-density="full" style="--grid-valign: center">
<div data-layout="grid">
<div data-colspan="2" data-name="cell">
<p>
<strong marker="**">Tall cell.</strong>
This cell has more content and takes up more vertical space. The adjacent cell will be vertically centred relative to this one.
</p>
<p>Additional paragraph to increase the height of this cell.</p>
</div>
<div data-colspan="1" data-name="cell">
<p>
<strong marker="**">Centred.</strong>
This shorter cell is vertically centred.
</p>
</div>
</div>
</section>Responsive collapse
Force the grid to collapse to a single column at a given breakpoint.
{% grid collapse="md" %}
Sidebar content.
---
Main content.
{% /grid %}
Breakpoints: sm (640px), md (768px), lg (1024px), or never.
Auto mode
Responsive auto-fill layout. The grid creates as many columns as fit, each at least min wide. No explicit column count needed.
{% grid mode="auto" min="200px" %}
Card one.
---
Card two.
---
Card three.
---
Card four.
{% /grid %}<section data-rune="grid" data-rune-fields="{"mode":"auto","min":"200px"}">
<div data-layout="grid">
<div data-colspan="1" data-name="cell">
<p>Card one.</p>
</div>
<div data-colspan="1" data-name="cell">
<p>Card two.</p>
</div>
<div data-colspan="1" data-name="cell">
<p>Card three.</p>
</div>
<div data-colspan="1" data-name="cell">
<p>Card four.</p>
</div>
</div>
</section>Card one.
Card two.
Card three.
Card four.
<section class="rf-grid rf-grid--auto" data-mode="auto" data-min="200px" data-rune="grid" data-density="full" style="--grid-min: 200px">
<div data-layout="grid">
<div data-colspan="1" data-name="cell">
<p>Card one.</p>
</div>
<div data-colspan="1" data-name="cell">
<p>Card two.</p>
</div>
<div data-colspan="1" data-name="cell">
<p>Card three.</p>
</div>
<div data-colspan="1" data-name="cell">
<p>Card four.</p>
</div>
</div>
</section>Masonry mode
Progressive enhancement for masonry-style layouts. Falls back to a standard grid in browsers that don't support CSS masonry.
{% grid mode="masonry" columns=3 %}
Short card.
---
A taller card with more content that takes up more vertical space.
---
Medium card.
---
Another short one.
{% /grid %}
Aspect ratio
Enforce a uniform aspect ratio on all grid cells. Content is cropped with object-fit: cover — ideal for image galleries.
{% grid mode="auto" min="200px" aspect="1/1" %}

---

---

---

{% /grid %}<section data-rune="grid" data-rune-fields="{"mode":"auto","min":"200px","aspect":"1/1"}">
<div data-layout="grid">
<div data-colspan="1" data-name="cell">
<p>
<img src="https://picsum.photos/seed/grid1/400/400" alt="">
</p>
</div>
<div data-colspan="1" data-name="cell">
<p>
<img src="https://picsum.photos/seed/grid2/400/400" alt="">
</p>
</div>
<div data-colspan="1" data-name="cell">
<p>
<img src="https://picsum.photos/seed/grid3/400/400" alt="">
</p>
</div>
<div data-colspan="1" data-name="cell">
<p>
<img src="https://picsum.photos/seed/grid4/400/400" alt="">
</p>
</div>
</div>
</section><section class="rf-grid rf-grid--auto" data-mode="auto" data-aspect="1/1" data-min="200px" data-rune="grid" data-density="full" style="--grid-min: 200px; --grid-aspect: 1/1">
<div data-layout="grid">
<div data-colspan="1" data-name="cell">
<p>
<img src="https://picsum.photos/seed/grid1/400/400" alt="" />
</p>
</div>
<div data-colspan="1" data-name="cell">
<p>
<img src="https://picsum.photos/seed/grid2/400/400" alt="" />
</p>
</div>
<div data-colspan="1" data-name="cell">
<p>
<img src="https://picsum.photos/seed/grid3/400/400" alt="" />
</p>
</div>
<div data-colspan="1" data-name="cell">
<p>
<img src="https://picsum.photos/seed/grid4/400/400" alt="" />
</p>
</div>
</div>
</section>Stack order
Control the order of cells when the grid collapses to a single column on mobile. By default cells stack in source order; stack="reverse" places the last cell first.
{% grid spans="2 1" collapse="md" stack="reverse" %}
Main content (appears second on mobile).
---
Sidebar (appears first on mobile).
{% /grid %}
Attributes
| Attribute | Type | Required | Description |
|---|---|---|---|
columns | number | — | Number of grid columns |
rows | number | — | Number of grid rows |
flow | "row" | "column" | "dense" | "row dense" | "column dense" | — | Direction items fill the grid |
spans | SpaceSeparatedList | — | Column span widths for each cell (space-separated) |
ratio | string | — | Column width ratio (e.g. "2 1 1") |
gap | "none" | "tight" | "default" | "loose" | — | Space between grid cells |
valign | "top" | "center" | "bottom" | "stretch" | "baseline" | — | Vertical alignment of cell content |
collapse | "sm" | "md" | "lg" | "never" | — | Breakpoint at which grid collapses to a single column |
mode | "columns" | "auto" | "masonry" | — | Grid sizing: fixed columns, auto-fit, or masonry |
min | string | — | Minimum cell width for auto-fit mode |
aspect | string | — | Aspect ratio for grid cells |
stack | "natural" | "reverse" | — | Stacking order when collapsed |
layout | SpaceSeparatedList | — |
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) |
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 |
| prominence | this rune has no page-section header |