Block reference
Every block's fields and canonical YAML examples
Blocks are entries in a YAML frontmatter blocks: array. This reference is generated from the full field schemas and canonical BLOCK_EXAMPLES, in deliberate registry order. Regenerate with npm run generate-references, or npm run generate-blocks-reference for this file alone.
See Layouts for rendering architecture, column matching, sidebar behavior, and styling. All source paths below are repository-root-relative.
Reading the schemas
Schema types retain authoring semantics: markdown, image, and reference store strings; array<T> is a list of T. Dotted paths describe nested objects, and [] describes each list item. A required child is required only when its optional parent object is supplied (or for each supplied list object). Required fields must be supplied even when a default is shown.
The Default column shows the value the build fills in when a block omits the field, after validation and before rendering, so templates and authors can rely on it. Defaults of nested fields apply within each supplied parent object. Derived defaults depend on another field, as the description explains; None means an omitted field stays absent. Descriptions and usage notes may describe contextual/template behavior beyond schema validation.
CMS collection availability describes the schema's editor allowlist, not a runtime restriction. Site CMS customization can hide collections or blocks. Contextual blocks may still need page data to render usefully. CMS label identifies editor-exposed fields; Not exposed fields can still be authored in YAML.
Common fields
type is a required string selecting a registered block. The common wrapper fields below come from CONTAINER_FIELDS. dark selects a dark section palette; compact reduces section padding. They affect full-width section wrappers, not direct column/sidebar rendering; transparent snippet blocks do not acquire their own wrapper.
| Field | Schema type | Presence | Default | CMS label | Description |
|---|---|---|---|---|---|
dark |
boolean |
optional | false |
Dark | |
compact |
boolean |
optional | None | Compact |
Column and sidebar compatibility
This table covers registered types only. No types are rejected inside columns and the right-content sidebar, but may appear in full-width before slots. Yes means the type passes the placement check, not that it is context-free. Snippets are reusable compositions: keep their contents appropriate for their destination too.
| Block | Container width | Columns / sidebar |
|---|---|---|
section-header |
wide |
Yes |
features |
wide |
Yes |
image-cards |
wide |
Yes |
stats |
wide |
Yes |
code-block |
wide |
Yes |
hero |
full |
No |
split-image |
wide |
No |
split-video |
wide |
No |
split-code |
wide |
No |
split-icon-links |
wide |
No |
split-html |
wide |
No |
split-callout |
wide |
No |
split-full |
full |
No |
cta |
wide |
Yes |
callout |
narrow |
Yes |
image-background |
full |
No |
video-background |
full |
No |
items |
wide |
Yes |
items-array |
wide |
Yes |
items-text-list |
wide |
Yes |
link-columns |
wide |
Yes |
markdown |
wide |
Yes |
table-of-contents |
narrow |
Yes |
html |
wide |
Yes |
iframe-embed |
wide |
Yes |
include |
wide |
Yes |
news-meta |
wide |
Yes |
faqs |
wide |
Yes |
guide-categories |
wide |
Yes |
guide-header |
full |
Yes |
guide-navigation |
full |
Yes |
guide-pages-list |
full |
Yes |
link-button |
wide |
Yes |
gallery |
wide |
Yes |
marquee-images |
full |
No |
video-cards |
wide |
Yes |
reviews |
wide |
Yes |
contact-form |
wide |
No |
quote-form |
wide |
No |
add-to-quote |
wide |
Yes |
icon-links |
narrow |
Yes |
downloads |
narrow |
Yes |
snippet |
wide |
Yes |
section-header
Standalone section header with rich text intro.
Schema: src/_lib/utils/block-schema/section-header.js
Component: block_section_header
Template: src/_includes/design-system/blocks/section-header.html
SCSS: src/css/design-system/_base.scss
HTML root: <div class="section-header prose">
Container width: wide
CMS collection availability: All collections with a block editor
Columns / sidebar: Yes
| Field | Schema type | Presence | Default | CMS label | Description |
|---|---|---|---|---|---|
intro |
markdown |
required | None | Section Header Intro | Rich text content rendered as markdown. Use headings and body text together. |
align |
string |
optional | "center" |
Not exposed | Text alignment. `"center"` adds `.text-center`. |
class |
string |
optional | None | Not exposed | Extra CSS classes. |
Canonical example
blocks:
- type: section-header
intro: >-
## Section headers introduce a page section
Use one wherever a group of blocks needs a heading and a sentence of
context.
features
Grid of feature cards with optional icons, names, and descriptions.
Schema: src/_lib/utils/block-schema/features.js
Component: block_features
Template: src/_includes/design-system/blocks/features.html
SCSS: src/css/design-system/_feature.scss
HTML root: <ul class="features" role="list"> containing <li><article class="feature"> items
Container width: wide
CMS collection availability: All collections with a block editor
Columns / sidebar: Yes
| Field | Schema type | Presence | Default | CMS label | Description |
|---|---|---|---|---|---|
items |
array<object> |
required | None | Features | Feature objects. Each: `{icon, icon_label, name, description, style}`. Icon can be an Iconify ID (`"prefix:name"`), image path (`"/images/foo.svg"`), or raw HTML/emoji. |
items[].icon |
string |
optional | None | Icon (Iconify ID or HTML entity) | |
items[].icon_label |
string |
optional | None | Icon Accessible Label | |
items[].name |
string |
required | None | Name | |
items[].description |
markdown |
optional | None | Description | |
items[].style |
string |
optional | None | Custom Style | |
intro_content |
markdown |
optional | None | Intro Content (Markdown) | Markdown content rendered above the block in `.prose`. |
reveal |
boolean |
optional | true |
Not exposed | Adds `data-reveal` to each feature. |
center |
boolean |
optional | false |
Centered | If true, centers feature text. |
Canonical example
blocks:
- type: features
intro_content: "## A grid of features"
items:
- icon: hugeicons:cube
name: Composable
description: Pages are YAML lists of typed blocks.
- icon: hugeicons:shield-01
name: Validated
description: Unknown types or keys fail the build loudly.
- icon: hugeicons:rocket
name: Static
description: Everything renders to plain HTML at build time.
image-cards
Grid of cards featuring images with names and optional descriptions.
Schema: src/_lib/utils/block-schema/image-cards.js
Component: block_image_cards
Template: src/_includes/design-system/blocks/image-cards.html
SCSS: src/css/design-system/_items.scss
HTML root: <ul class="items" role="list">
Container width: wide
CMS collection availability: All collections with a block editor
Columns / sidebar: Yes
| Field | Schema type | Presence | Default | CMS label | Description |
|---|---|---|---|---|---|
items |
array<object> |
required | None | Cards | Card objects. Each: `{image, name, description, link}`. Images processed by `{% image %}` shortcode for responsive srcset + LQIP. |
items[].image |
image |
required | None | Image | |
items[].name |
string |
required | None | Name | |
items[].description |
string |
optional | None | Description | |
items[].link |
string |
optional | None | Link URL | |
reveal |
boolean |
optional | true |
Not exposed | Adds `data-reveal` to each card. |
image_aspect_ratio |
string |
optional | None | Image Aspect Ratio | Aspect ratio for images, e.g. `"16/9"`, `"1/1"`, `"4/3"`. |
intro_content |
markdown |
optional | None | Intro Content (Markdown) | Markdown content rendered above the block in `.prose`. |
Canonical example
blocks:
- type: image-cards
items:
- image: breakfast.jpg
name: Breakfast
description: Cards pair an image with a name and description.
link: /how-it-works/
- image: lunch.jpg
name: Lunch
description: Images get responsive srcset and LQIP placeholders.
- image: dinner.jpg
name: Dinner
description: An optional link makes the whole card clickable.
stats
Key metrics displayed as large numbers with labels.
Schema: src/_lib/utils/block-schema/stats.js
Component: block_stats
Template: src/_includes/design-system/blocks/stats.html
SCSS: src/css/design-system/_stats.scss
HTML root: <dl class="stats">
Container width: wide
CMS collection availability: All collections with a block editor
Columns / sidebar: Yes
| Field | Schema type | Presence | Default | CMS label | Description |
|---|---|---|---|---|---|
items |
array<object> |
required | None | Statistics | Stat objects: `{value, label}` or pipe-delimited strings `"value|label"`. Also accepts "value|label" strings. |
items[].value |
string |
required | None | Value | |
items[].label |
string |
required | None | Label | |
intro_content |
markdown |
optional | None | Intro Content (Markdown) | Markdown content rendered above the block in `.prose`. |
reveal |
boolean |
optional | true |
Not exposed | Adds `data-reveal` to each stat. |
Canonical example
blocks:
- type: stats
items:
- value: 35+
label: Block types
- value: 100%
label: Line coverage
- value: "0"
label: Servers to run
code-block
Terminal-style code display with macOS-like toolbar header.
Schema: src/_lib/utils/block-schema/code-block.js
Component: block_code_block
Template: src/_includes/design-system/blocks/code-block.html
SCSS: src/css/design-system/_code-block.scss
HTML root: <div class="code-block">
Container width: wide
CMS collection availability: All collections with a block editor
Columns / sidebar: Yes
| Field | Schema type | Presence | Default | CMS label | Description |
|---|---|---|---|---|---|
intro_content |
markdown |
optional | None | Intro Content (Markdown) | Markdown content rendered above the block in `.prose`. |
filename |
string |
required | None | Filename | Displayed in the toolbar header. |
code |
string |
required | None | Code | Code content. Rendered in `<pre><code>`. |
language |
string |
optional | None | Language | Sets `data-language` attribute (for future syntax highlighting). |
reveal |
boolean |
optional | true |
Not exposed | Adds `data-reveal` to the code block. |
Canonical example
blocks:
- type: code-block
intro_content: "## Commands to get started"
filename: quick-start.sh
language: bash
code: |-
npm install
npm run serve
hero
Full-width hero banner with optional badge, markdown content, and action buttons.
Schema: src/_lib/utils/block-schema/hero.js
Component: block_hero
Template: src/_includes/design-system/blocks/hero.html
SCSS: src/css/design-system/_hero.scss
HTML root: <header class="hero">
Container width: full
CMS collection availability: All collections with a block editor
Columns / sidebar: No
| Field | Schema type | Presence | Default | CMS label | Description |
|---|---|---|---|---|---|
badge |
string |
optional | None | Badge Text | Small pill label above the content. Renders as `<span class="badge">`. |
content |
markdown |
required | None | Content | Markdown content rendered in `.prose`. Start with a `# Heading`; paragraphs get `body-lg` size and muted color in a centered, guttered measure. |
buttons |
array<object> |
optional | None | Buttons | Action buttons below the content. Each: `{text, href, variant, size}`. Variants: `"primary"` (filled), `"secondary"` (outlined), `"ghost"` (transparent). Sizes: `"sm"`, `"lg"`, or omit for default. |
buttons[].text |
string |
required | None | Button Text | |
buttons[].href |
string |
required | None | URL | |
buttons[].variant |
string |
optional | "primary" |
Variant | |
buttons[].size |
string |
optional | None | Size | |
reveal |
string |
optional | None | Not exposed | `data-reveal` value. |
class |
string |
optional | None | CSS Class | Extra CSS classes on the `<header>`. Use `"gradient"` for gradient bg. |
Canonical example
blocks:
- type: hero
badge: Hero block
content: >-
# A full-width opening banner
Start most pages with one of these: a heading, a sentence of context, and
the actions that matter.
buttons:
- text: Primary action
href: /how-it-works/
variant: primary
size: lg
- text: Secondary
href: /blocks/
variant: secondary
size: lg
split-image
Two-column layout with text content and a responsive image.
Schema: src/_lib/utils/block-schema/split-image.js
Component: block_split_image
Template: src/_includes/design-system/split.html
SCSS: src/css/design-system/_split.scss
HTML root: <div class="split">
Container width: wide
CMS collection availability: All collections with a block editor
Columns / sidebar: No
| Field | Schema type | Presence | Default | CMS label | Description |
|---|---|---|---|---|---|
subtitle |
string |
optional | None | Subtitle | Subtitle with `.text-muted` styling. |
content |
markdown |
optional | None | Content | Main content with markdown headings (e.g. `## Heading`). Rendered through `renderContent: "md"` filter, wrapped in `.prose`. |
reverse |
boolean |
optional | false |
Reverse Layout | Reverses column order (content right, figure left) on desktop. |
reveal_content |
string |
optional | Derived | Reveal Content Animation | `data-reveal` for the text side. Defaults to `"left"`, or `"right"` when `reverse` is true. |
reveal_figure |
string |
optional | "scale" |
Reveal Figure Animation | `data-reveal` for the figure side. |
button |
object |
optional | None | Button | `{text, href, variant, size}`. Rendered below content. |
button.text |
string |
required | None | Button Text | |
button.href |
string |
required | None | URL | |
button.variant |
string |
optional | "secondary" |
Variant | |
button.size |
string |
optional | None | Size | |
figure_src |
image |
required | None | Figure Image | Image path. |
figure_alt |
string |
optional | None | Figure Alt Text | Alt text for the image. |
figure_caption |
string |
optional | None | Figure Caption | Visible caption below the image. |
Canonical example
blocks:
- type: split-image
subtitle: Split layouts
content: >-
## Text beside an image
The figure sits on the right by default; set `reverse: true` to flip the
columns.
figure_src: fireworks.jpg
figure_alt: Fireworks over a city at night
figure_caption: Captions are optional.
split-video
Two-column layout with text content and an embedded video.
Schema: src/_lib/utils/block-schema/split-video.js
Component: block_split_video
Template: src/_includes/design-system/split.html
SCSS: src/css/design-system/_split.scss
HTML root: <div class="split">
Container width: wide
CMS collection availability: All collections with a block editor
Columns / sidebar: No
| Field | Schema type | Presence | Default | CMS label | Description |
|---|---|---|---|---|---|
subtitle |
string |
optional | None | Subtitle | Subtitle with `.text-muted` styling. |
content |
markdown |
optional | None | Content | Main content with markdown headings (e.g. `## Heading`). Rendered through `renderContent: "md"` filter, wrapped in `.prose`. |
reverse |
boolean |
optional | false |
Reverse Layout | Reverses column order (content right, figure left) on desktop. |
reveal_content |
string |
optional | Derived | Reveal Content Animation | `data-reveal` for the text side. Defaults to `"left"`, or `"right"` when `reverse` is true. |
reveal_figure |
string |
optional | "scale" |
Reveal Figure Animation | `data-reveal` for the figure side. |
button |
object |
optional | None | Button | `{text, href, variant, size}`. Rendered below content. |
button.text |
string |
required | None | Button Text | |
button.href |
string |
required | None | URL | |
button.variant |
string |
optional | "secondary" |
Variant | |
button.size |
string |
optional | None | Size | |
figure_video_id |
string |
required | None | Video ID or embed URL | Bare YouTube video ID (e.g. `dQw4w9WgXcQ`), or the full embed URL of another provider (Vimeo, Bunny Stream). Never an `<iframe>` snippet. |
figure_thumbnail_url |
string |
optional | None | Thumbnail | Image path or URL shown behind the play button. Defaults to the provider's own thumbnail for a YouTube ID or a Vimeo URL; other providers show a plain panel. |
figure_alt |
string |
optional | "Video" |
Video Name | Name of the video, used for the play button's label and the iframe's title. |
figure_caption |
string |
optional | None | Video Caption | Visible caption below the video. |
figure_autoplay |
boolean |
optional | false |
Autoplay | If true, skips the play button and starts the video muted, with its controls showing, when it scrolls into view. |
Usage notes: Without `figure_autoplay` the video loads only when its play button is pressed.
Canonical example
blocks:
- type: split-video
subtitle: Split layouts
content: |-
## Text beside a video
The video loads when a visitor presses play.
figure_video_id: dQw4w9WgXcQ
figure_thumbnail_url: dinner.jpg
figure_alt: A YouTube video
figure_caption: Captions are optional.
split-code
Two-column layout with text content and a code block.
Schema: src/_lib/utils/block-schema/split-code.js
Component: block_split_code
Template: src/_includes/design-system/split.html
SCSS: src/css/design-system/_split.scss
HTML root: <div class="split">
Container width: wide
CMS collection availability: All collections with a block editor
Columns / sidebar: No
| Field | Schema type | Presence | Default | CMS label | Description |
|---|---|---|---|---|---|
subtitle |
string |
optional | None | Subtitle | Subtitle with `.text-muted` styling. |
content |
markdown |
optional | None | Content | Main content with markdown headings (e.g. `## Heading`). Rendered through `renderContent: "md"` filter, wrapped in `.prose`. |
reverse |
boolean |
optional | false |
Reverse Layout | Reverses column order (content right, figure left) on desktop. |
reveal_content |
string |
optional | Derived | Reveal Content Animation | `data-reveal` for the text side. Defaults to `"left"`, or `"right"` when `reverse` is true. |
reveal_figure |
string |
optional | "scale" |
Reveal Figure Animation | `data-reveal` for the figure side. |
button |
object |
optional | None | Button | `{text, href, variant, size}`. Rendered below content. |
button.text |
string |
required | None | Button Text | |
button.href |
string |
required | None | URL | |
button.variant |
string |
optional | "secondary" |
Variant | |
button.size |
string |
optional | None | Size | |
figure_filename |
string |
optional | None | Code Filename | Displayed filename in the code block header. |
figure_code |
string |
required | None | Code Content | Code content. |
figure_language |
string |
optional | None | Code Language | Syntax highlighting language. |
Canonical example
blocks:
- type: split-code
subtitle: Split layouts
content: >-
## Text beside a code block
Use this to walk through configuration or commands next to the
explanation.
figure_filename: site.json
figure_language: json
figure_code: |-
{
"name": "My Site",
"url": "https://example.com"
}
split-icon-links
Two-column layout with text content and an icon-links list.
Schema: src/_lib/utils/block-schema/split-icon-links.js
Component: block_split_icon_links
Template: src/_includes/design-system/split.html
SCSS: src/css/design-system/_split.scss
HTML root: <div class="split">
Container width: wide
CMS collection availability: All collections with a block editor
Columns / sidebar: No
| Field | Schema type | Presence | Default | CMS label | Description |
|---|---|---|---|---|---|
subtitle |
string |
optional | None | Subtitle | Subtitle with `.text-muted` styling. |
content |
markdown |
optional | None | Content | Main content with markdown headings (e.g. `## Heading`). Rendered through `renderContent: "md"` filter, wrapped in `.prose`. |
reverse |
boolean |
optional | false |
Reverse Layout | Reverses column order (content right, figure left) on desktop. |
reveal_content |
string |
optional | Derived | Reveal Content Animation | `data-reveal` for the text side. Defaults to `"left"`, or `"right"` when `reverse` is true. |
reveal_figure |
string |
optional | "scale" |
Reveal Figure Animation | `data-reveal` for the figure side. |
button |
object |
optional | None | Button | `{text, href, variant, size}`. Rendered below content. |
button.text |
string |
required | None | Button Text | |
button.href |
string |
required | None | URL | |
button.variant |
string |
optional | "secondary" |
Variant | |
button.size |
string |
optional | None | Size | |
figure_items |
array<object> |
required | None | Links | Icon-link objects. Each: `{icon, text, url}`. `url` is optional. Icon can be an Iconify ID (`"prefix:name"`), image path, or raw HTML/emoji. |
figure_items[].icon |
string |
required | None | Icon (Iconify ID or HTML entity) | |
figure_items[].text |
string |
required | None | Link Text | |
figure_items[].url |
string |
optional | None | URL |
Canonical example
blocks:
- type: split-icon-links
subtitle: Split layouts
content: |-
## Text beside a list of links
Each link gets an icon.
figure_items:
- icon: hugeicons:book-02
text: How it works
url: /how-it-works/
- icon: hugeicons:cube
text: Blocks
url: /blocks/
- icon: hugeicons:search-01
text: Search
url: /search/
split-html
Two-column layout with text content and custom HTML.
Schema: src/_lib/utils/block-schema/split-html.js
Component: block_split_html
Template: src/_includes/design-system/split.html
SCSS: src/css/design-system/_split.scss
HTML root: <div class="split">
Container width: wide
CMS collection availability: All collections with a block editor
Columns / sidebar: No
| Field | Schema type | Presence | Default | CMS label | Description |
|---|---|---|---|---|---|
subtitle |
string |
optional | None | Subtitle | Subtitle with `.text-muted` styling. |
content |
markdown |
optional | None | Content | Main content with markdown headings (e.g. `## Heading`). Rendered through `renderContent: "md"` filter, wrapped in `.prose`. |
reverse |
boolean |
optional | false |
Reverse Layout | Reverses column order (content right, figure left) on desktop. |
reveal_content |
string |
optional | Derived | Reveal Content Animation | `data-reveal` for the text side. Defaults to `"left"`, or `"right"` when `reverse` is true. |
reveal_figure |
string |
optional | "scale" |
Reveal Figure Animation | `data-reveal` for the figure side. |
button |
object |
optional | None | Button | `{text, href, variant, size}`. Rendered below content. |
button.text |
string |
required | None | Button Text | |
button.href |
string |
required | None | URL | |
button.variant |
string |
optional | "secondary" |
Variant | |
button.size |
string |
optional | None | Size | |
figure_html |
markdown |
required | None | Figure HTML Content | Raw HTML content for the figure side. |
Canonical example
blocks:
- type: split-html
subtitle: Split layouts
content: |-
## Text beside custom HTML
The figure column renders whatever HTML you give it.
figure_html: <p style="font-size:3rem;text-align:center">🎉</p>
split-callout
Two-column layout with text content and a styled callout box with icon, name, and subtitle.
Schema: src/_lib/utils/block-schema/split-callout.js
Component: block_split_callout
Template: src/_includes/design-system/blocks/split-callout.html
SCSS: src/css/design-system/_split-callout.scss
HTML root: <div class="split-callout">
Container width: wide
CMS collection availability: All collections with a block editor
Columns / sidebar: No
| Field | Schema type | Presence | Default | CMS label | Description |
|---|---|---|---|---|---|
subtitle |
string |
optional | None | Subtitle | Subtitle with `.text-muted` styling. |
content |
markdown |
optional | None | Content | Main content with markdown headings (e.g. `## Heading`). Rendered through `renderContent: "md"` filter, wrapped in `.prose`. |
reverse |
boolean |
optional | false |
Reverse Layout | Reverses column order (content right, figure left) on desktop. |
reveal_content |
string |
optional | Derived | Reveal Content Animation | `data-reveal` for the text side. Defaults to `"left"`, or `"right"` when `reverse` is true. |
reveal_figure |
string |
optional | "scale" |
Reveal Figure Animation | `data-reveal` for the figure side. |
button |
object |
optional | None | Button | `{text, href, variant, size}`. Rendered below content. |
button.text |
string |
required | None | Button Text | |
button.href |
string |
required | None | URL | |
button.variant |
string |
optional | "secondary" |
Variant | |
button.size |
string |
optional | None | Size | |
figure_icon |
string |
optional | None | Icon (Iconify ID, emoji, or path) | Icon content: Iconify ID (`prefix:name`), emoji, or image path. |
figure_name |
string |
required | None | Callout Name | Bold heading text in the callout box. |
figure_subtitle |
string |
optional | None | Callout Subtitle | Supporting text below the name. |
figure_variant |
string |
optional | "primary" |
Callout Color Variant | Color scheme: `"primary"`, `"secondary"`, `"gradient"`, or a custom CSS gradient string. |
Canonical example
blocks:
- type: split-callout
subtitle: Split layouts
content: |-
## Text beside a callout card
Good for highlighting one key fact or contact route.
figure_icon: hugeicons:call
figure_name: Talk to a person
figure_subtitle: Phone lines open 9-5, Monday to Friday.
split-full
Full-width two-panel layout with distinct background colors per side.
Schema: src/_lib/utils/block-schema/split-full.js
Component: block_split_full
Template: src/_includes/design-system/blocks/split-full.html
SCSS: src/css/design-system/_split.scss
HTML root: <div class="split-full">
Container width: full
CMS collection availability: All collections with a block editor
Columns / sidebar: No
| Field | Schema type | Presence | Default | CMS label | Description |
|---|---|---|---|---|---|
variant |
string |
optional | None | Variant | Color scheme: `"dark-left"`, `"dark-right"`, `"primary-left"`, `"primary-right"`. |
left_content |
markdown |
optional | None | Left Content | Left panel content with markdown headings (e.g. `## Heading`). Rendered as markdown via `.prose`. |
left_button |
object |
optional | None | Left Button | `{text, href, variant, size}`. |
left_button.text |
string |
required | None | Button Text | |
left_button.href |
string |
required | None | URL | |
left_button.variant |
string |
optional | "secondary" |
Variant | |
left_button.size |
string |
optional | None | Size | |
right_content |
markdown |
optional | None | Right Content | Right panel content with markdown headings (e.g. `## Heading`). Rendered as markdown via `.prose`. |
right_button |
object |
optional | None | Right Button | `{text, href, variant, size}`. |
right_button.text |
string |
required | None | Button Text | |
right_button.href |
string |
required | None | URL | |
right_button.variant |
string |
optional | "secondary" |
Variant | |
right_button.size |
string |
optional | None | Size | |
reveal_left |
string |
optional | None | Reveal Left Animation | `data-reveal` for left panel. |
reveal_right |
string |
optional | None | Reveal Right Animation | `data-reveal` for right panel. |
Usage notes: Variants: `"dark-left"` / `"dark-right"` (dark bg + light text), `"primary-left"` / `"primary-right"` (`--color-link` bg + contrast text). Button colors automatically invert in dark/primary panels. The parent `<section>` has zero padding — panels handle their own padding.
Canonical example
blocks:
- type: split-full
variant: dark-left
left_content: |-
## Two panels
Each side takes its own content and button.
left_button:
text: Left action
href: /how-it-works/
variant: secondary
right_content: |-
## Side by side
Variants control which side is dark.
right_button:
text: Right action
href: /blocks/
cta
Call-to-action banner with gradient background.
Schema: src/_lib/utils/block-schema/cta.js
Component: block_cta
Template: src/_includes/design-system/blocks/cta.html
SCSS: src/css/design-system/_cta.scss
HTML root: <aside class="cta">
Container width: wide
CMS collection availability: All collections with a block editor
Columns / sidebar: Yes
| Field | Schema type | Presence | Default | CMS label | Description |
|---|---|---|---|---|---|
content |
markdown |
required | None | Content | Markdown content with optional heading (e.g. `## Heading`). `body-lg`, 0.9 opacity, max-width `$width-narrow`. |
button |
object |
optional | None | Button | `{text, href, variant, size}`. |
button.text |
string |
required | None | Button Text | |
button.href |
string |
required | None | URL | |
button.variant |
string |
optional | "secondary" |
Variant | |
button.size |
string |
optional | "lg" |
Size | |
reveal |
string |
optional | None | Not exposed | `data-reveal` value. |
Canonical example
blocks:
- type: cta
content: |-
## Ready to try it?
A call-to-action closes the page with one clear next step.
button:
text: How it works
href: /how-it-works/
size: lg
callout
One-column callout/note with icon, name, and short content — for content warnings, advisories, tips, etc.
Schema: src/_lib/utils/block-schema/callout.js
Component: block_callout
Template: src/_includes/design-system/blocks/callout.html
SCSS: src/css/design-system/_callout.scss
HTML root: <aside class="callout">
Container width: narrow
CMS collection availability: All collections with a block editor
Columns / sidebar: Yes
| Field | Schema type | Presence | Default | CMS label | Description |
|---|---|---|---|---|---|
variant |
string |
optional | "info" |
Variant (info | warning | success | danger) | Color scheme: `"info"`, `"warning"`, `"success"`, or `"danger"`. |
icon |
string |
optional | None | Icon (Iconify ID, emoji, or path) | Icon content: Iconify ID (`prefix:name`), emoji, or image path. |
name |
string |
optional | None | Name | Bold heading text. |
content |
markdown |
required | None | Content | Markdown content rendered via `renderContent: "md"` inside `.prose`. |
Canonical example
blocks:
- type: callout
variant: info
icon: hugeicons:information-circle
name: Good to know
content: Callouts hold short advisories, tips, and warnings. Four color variants
are available.
image-background
Full-width image background with hero-style overlay content (badge, markdown content, buttons) and optional parallax.
Schema: src/_lib/utils/block-schema/image-background.js
Component: block_image_background
Template: src/_includes/design-system/blocks/image-background.html
SCSS: src/css/design-system/_image-background.scss
HTML root: <div class="image-background">
Container width: full
CMS collection availability: All collections with a block editor
Columns / sidebar: No
| Field | Schema type | Presence | Default | CMS label | Description |
|---|---|---|---|---|---|
image |
image |
required | None | Background Image | Image path. |
image_alt |
string |
optional | "Background image" |
Image Alt Text | Alt text. |
class |
string |
optional | None | CSS Class | Extra CSS classes. |
badge |
string |
optional | None | Badge Text | Small pill label above the content. Renders as `<span class="badge">`. |
content |
markdown |
optional | None | Content | Markdown overlay content rendered in `.prose` inside the `<figcaption>`. |
buttons |
array<object> |
optional | None | Buttons | Action buttons below the content. Each: `{text, href, variant, size}`. Variants: `"primary"` (filled), `"secondary"` (outlined), `"ghost"` (transparent). Sizes: `"sm"`, `"lg"`, or omit for default. |
buttons[].text |
string |
required | None | Button Text | |
buttons[].href |
string |
required | None | URL | |
buttons[].variant |
string |
optional | "primary" |
Variant | |
buttons[].size |
string |
optional | None | Size | |
reveal |
string |
optional | None | Not exposed | `data-reveal` value. |
parallax |
boolean |
optional | false |
Parallax | Enables CSS `animation-timeline: scroll()` parallax effect. |
tint |
boolean |
optional | false |
Tint | Applies a dark gradient overlay for text legibility over the background image. |
Usage notes: Image processed via `{% image %}` at widths 2560/1920/1280/960/640, cropped to 16/9. Parallax uses `animation-timeline: scroll()` for native CSS scroll-driven translation.
Canonical example
blocks:
- type: image-background
image: city-traffic-night.jpg
image_alt: City traffic at night
tint: true
badge: Image background
content: |-
# Content over a full-width image
A tint keeps the text readable.
video-background
Full-width video playing silently behind hero-style overlay content (badge, markdown content, buttons).
Schema: src/_lib/utils/block-schema/video-background.js
Component: block_video_background
Template: src/_includes/design-system/blocks/video-background.html
SCSS: src/css/design-system/_video-background.scss
Container width: full
CMS collection availability: All collections with a block editor
Columns / sidebar: No
| Field | Schema type | Presence | Default | CMS label | Description |
|---|---|---|---|---|---|
video_id |
string |
required | None | Video ID or embed URL | Bare YouTube video ID (e.g. `dQw4w9WgXcQ`); the block builds the embed itself. For other providers (Vimeo, Bunny Stream), the full embed URL. Never an `<iframe>` snippet. |
video_title |
string |
optional | "Background video" |
Video Title | Accessible `title` on the iframe. |
thumbnail_url |
string |
optional | None | Thumbnail | Image path or URL shown over the iframe while the video loads. Defaults to the provider's own thumbnail for a YouTube ID or a Vimeo URL; other providers show nothing until the video plays. |
class |
string |
optional | None | CSS Class | Extra CSS classes. |
badge |
string |
optional | None | Badge Text | Small pill label above the content. Renders as `<span class="badge">`. |
content |
markdown |
optional | None | Content | Markdown overlay content rendered in `.prose` inside the `<figcaption>`. |
buttons |
array<object> |
optional | None | Buttons | Action buttons below the content. Each: `{text, href, variant, size}`. Variants: `"primary"` (filled), `"secondary"` (outlined), `"ghost"` (transparent). Sizes: `"sm"`, `"lg"`, or omit for default. |
buttons[].text |
string |
required | None | Button Text | |
buttons[].href |
string |
required | None | URL | |
buttons[].variant |
string |
optional | "primary" |
Variant | |
buttons[].size |
string |
optional | None | Size | |
reveal |
string |
optional | None | Not exposed | `data-reveal` value. |
Usage notes: YouTube IDs become `youtube-nocookie.com` embeds that autoplay muted, loop, and hide their controls; the thumbnail fades out once the player reports it is playing. Vimeo URLs gain autoplay, loop, and mute. A Bunny Stream embed URL is used as written, so set its `autoplay`, `loop`, and `muted` parameters yourself; its thumbnail fades out when the player starts, through a small script loaded only on pages with a Bunny background. The video is decorative: put anything a visitor must know in the overlay content.
Canonical example
blocks:
- type: video-background
video_id: dQw4w9WgXcQ
thumbnail_url: city-traffic-night.jpg
badge: Video background
content: |-
# Content over a full-width video
The thumbnail shows until the video is playing.
items
Displays an Eleventy collection as a card grid or horizontal slider.
Schema: src/_lib/utils/block-schema/items.js
Component: block_items
Template: src/_includes/design-system/blocks/items.html
SCSS: src/css/design-system/_items.scss
Container width: wide
CMS collection availability: All collections with a block editor
Columns / sidebar: Yes
| Field | Schema type | Presence | Default | CMS label | Description |
|---|---|---|---|---|---|
collection |
string |
required | None | Collection Name | Name of an Eleventy collection (e.g. `"news"`, `"guideCategories"`). |
intro_content |
markdown |
optional | None | Intro Content (Markdown) | Markdown content rendered above the block in `.prose`. |
horizontal |
boolean |
optional | false |
Horizontal Slider | If true, renders as a horizontal slider instead of a wrapping grid. |
masonry |
boolean |
optional | false |
Masonry Grid | If true, renders as a masonry grid using uWrap for zero-reflow height prediction. |
filter |
object |
optional | None | Filter | Filter object: `{property, includes, equals}`. `property` is a dot-notation path (e.g. `"url"`, `"data.name"`). When the resolved value is an array, the operator runs against each element (per-element exact match for `equals`, per-element substring for `includes`). `includes` matches substring; `equals` matches exact value. |
filter.property |
string |
optional | None | Property (e.g. url, data.name) | |
filter.includes |
string |
optional | None | Contains | |
filter.equals |
string |
optional | None | Equals | |
image_aspect_ratio |
string |
optional | None | Image Aspect Ratio | Aspect ratio for images, e.g. `"16/9"`, `"1/1"`, `"4/3"`. |
Canonical example
blocks:
- type: items
collection: news
intro_content: |-
## A collection as cards
This example renders the `news` collection.
items-array
Renders items from an explicit list of paths. The collection is inferred dynamically from each item's path. Directory paths (ending in `/` or with no `.md` extension) expand to every item in that directory.
Schema: src/_lib/utils/block-schema/items-array.js
Component: block_items_array
Template: src/_includes/design-system/blocks/items-array.html
SCSS: src/css/design-system/_items.scss
Container width: wide
CMS collection availability: All collections with a block editor
Columns / sidebar: Yes
| Field | Schema type | Presence | Default | CMS label | Description |
|---|---|---|---|---|---|
items |
array<string> |
optional | None | Items | Array of path strings. Each entry may be a file path (e.g. `src/news/example.md`) or a directory path (e.g. `src/news` or `src/news/`), in which case every item in that directory is included in place. |
intro_content |
markdown |
optional | None | Intro Content (Markdown) | Markdown content rendered above the block in `.prose`. |
horizontal |
boolean |
optional | false |
Horizontal Slider | If true, renders as a horizontal slider instead of a wrapping grid. |
masonry |
boolean |
optional | false |
Masonry Grid | If true, renders as a masonry grid using uWrap for zero-reflow height prediction. |
filter |
object |
optional | None | Filter | Filter object: `{property, includes, equals}`. `property` is a dot-notation path (e.g. `"url"`, `"data.name"`). When the resolved value is an array, the operator runs against each element (per-element exact match for `equals`, per-element substring for `includes`). `includes` matches substring; `equals` matches exact value. |
filter.property |
string |
optional | None | Property (e.g. url, data.name) | |
filter.includes |
string |
optional | None | Contains | |
filter.equals |
string |
optional | None | Equals | |
image_aspect_ratio |
string |
optional | None | Image Aspect Ratio | Aspect ratio for images, e.g. `"16/9"`, `"1/1"`, `"4/3"`. |
Canonical example
blocks:
- type: items-array
items:
- news
intro_content: |-
## Hand-picked items
List file paths, or a directory to include everything in it.
items-text-list
Renders a collection as a comma-separated inline list of links, with optional introductory markdown text prepended. Excludes the current page from the list.
Schema: src/_lib/utils/block-schema/items-text-list.js
Component: block_items_text_list
Template: src/_includes/design-system/blocks/items-text-list.html
Container width: wide
CMS collection availability: All collections with a block editor
Columns / sidebar: Yes
| Field | Schema type | Presence | Default | CMS label | Description |
|---|---|---|---|---|---|
collection |
string |
required | None | Collection Name | Name of an Eleventy collection (e.g. `"services"`, `"events"`). |
intro_content |
markdown |
optional | None | Intro Content (Markdown) | Markdown content rendered above the block in `.prose`. |
Canonical example
blocks:
- type: items-text-list
collection: news
intro_content: "Read our latest news posts:"
link-columns
Renders a collection as a plain-text unordered list of links arranged in responsive CSS columns. Optionally strips matching text via a regex so repetitive prefixes/suffixes can be removed.
Schema: src/_lib/utils/block-schema/link-columns.js
Component: block_link_columns
Template: src/_includes/design-system/blocks/link-columns.html
SCSS: src/css/design-system/_link-columns.scss
Container width: wide
CMS collection availability: All collections with a block editor
Columns / sidebar: Yes
| Field | Schema type | Presence | Default | CMS label | Description |
|---|---|---|---|---|---|
collection |
string |
required | None | Collection Name | Name of an Eleventy collection (e.g. `"locations"`, `"services"`). |
intro_content |
markdown |
optional | None | Intro Content (Markdown) | Markdown content rendered above the block in `.prose`. |
filter |
object |
optional | None | Filter | Filter object: `{property, includes, equals}`. `property` is a dot-notation path (e.g. `"url"`, `"data.name"`). When the resolved value is an array, the operator runs against each element (per-element exact match for `equals`, per-element substring for `includes`). `includes` matches substring; `equals` matches exact value. |
filter.property |
string |
optional | None | Property (e.g. url, data.name) | |
filter.includes |
string |
optional | None | Contains | |
filter.equals |
string |
optional | None | Equals | |
remove_text |
string |
optional | None | Remove Text (Regex) | Regex pattern (JavaScript syntax, global flag implied). Each match is removed from every link's display text and the result is trimmed. Useful for stripping repetitive prefixes like `"Service in "` so links render tidier. |
Canonical example
blocks:
- type: link-columns
collection: news
intro_content: "## A collection as link columns"
markdown
Renders markdown content as rich text.
Schema: src/_lib/utils/block-schema/markdown.js
Component: block_markdown
Template: src/_includes/design-system/blocks/markdown.html
SCSS: src/css/design-system/_prose.scss
HTML root: <div class="prose">
Container width: wide
CMS collection availability: All collections with a block editor
Columns / sidebar: Yes
| Field | Schema type | Presence | Default | CMS label | Description |
|---|---|---|---|---|---|
content |
markdown |
required | None | Markdown | Markdown content. Passed through `renderContent: "md"` filter. |
Canonical example
blocks:
- type: markdown
content: >-
## Plain markdown
The simplest block: write markdown, get rich text. Lists, links, and
**emphasis** all work.
- One
- Two
- Three
table-of-contents
In-page contents built from the headings the page actually renders.
Schema: src/_lib/utils/block-schema/table-of-contents.js
Component: block_table_of_contents
Template: src/_includes/design-system/blocks/table-of-contents.html
SCSS: src/css/design-system/_table-of-contents.scss
HTML root: <nav class="table-of-contents">
Container width: narrow
CMS collection availability: All collections with a block editor
Columns / sidebar: Yes
| Field | Schema type | Presence | Default | CMS label | Description |
|---|---|---|---|---|---|
title |
string |
optional | "On this page" |
Title | Heading above the list, and the accessible name of the navigation landmark. |
levels |
string |
optional | "2,3" |
Heading Levels | Comma-separated heading levels to list, from `2` to `6`. Every other level is left out of the list, so `"2"` gives a top-level-only contents. An unlistable level fails the build. |
Usage notes: The list is filled in after the page renders, from the headings in the page's main content at the levels this block names — no matter which blocks wrote them. Each listed heading gets an `id` (its own, when it already has one) so the links are stable, and the entries nest the way the headings do. A page whose headings none of the named levels match fails the build rather than publishing an empty contents.
Canonical example
blocks:
- type: table-of-contents
title: On this page
levels: 2,3
html
Outputs raw HTML without processing.
Schema: src/_lib/utils/block-schema/html.js
Component: block_html
Template: src/_includes/design-system/blocks/html.html
Container width: wide
CMS collection availability: All collections with a block editor
Columns / sidebar: Yes
| Field | Schema type | Presence | Default | CMS label | Description |
|---|---|---|---|---|---|
content |
string |
required | None | Raw HTML | Raw HTML. Output directly with `{{ block.content }}`. |
Usage notes: No wrapping element. Useful for custom embeds, iframes, or one-off HTML.
Canonical example
blocks:
- type: html
content: <p><strong>Raw HTML</strong> passes straight through - the escape hatch
for one-off markup.</p>
iframe-embed
Third-party iframe embed (itch.io widgets, Buttondown, Bandcamp, Stripe buttons, etc).
Schema: src/_lib/utils/block-schema/iframe-embed.js
Component: block_iframe_embed
Template: src/_includes/design-system/blocks/iframe-embed.html
SCSS: src/css/design-system/_iframe-embed.scss
HTML root: <div class="iframe-embed">
Container width: wide
CMS collection availability: All collections with a block editor
Columns / sidebar: Yes
| Field | Schema type | Presence | Default | CMS label | Description |
|---|---|---|---|---|---|
src |
string |
required | None | Iframe URL | Full URL of the iframe to embed. |
name |
string |
required | None | Accessible Name | Accessible name (rendered as the iframe's `title` attribute). |
width |
number |
optional | None | Width (px) | Fixed pixel width. Omit to fill the container. |
height |
number |
optional | None | Height (px) | Fixed pixel height. Required for non-responsive embeds unless `aspect_ratio` is set. |
aspect_ratio |
string |
optional | None | Aspect Ratio (e.g. 16/9) | CSS `aspect-ratio` for responsive height, e.g. `"16/9"`. Alternative to `height`. |
max_width |
string |
optional | None | Max Width (CSS, e.g. 560px) | CSS max-width on the wrapper, e.g. `"560px"`. |
sandbox |
string |
optional | None | Sandbox | Space-separated sandbox tokens, e.g. `"allow-scripts allow-same-origin allow-forms"`. |
allow |
string |
optional | None | Allow (permissions policy) | `allow` attribute for iframe permissions policy. |
scrolling |
string |
optional | None | Scrolling | Legacy `scrolling` attribute, e.g. `"no"`. |
intro_content |
markdown |
optional | None | Intro Content (Markdown) | Markdown content rendered above the block in `.prose`. |
Usage notes: Provide either `height` for a fixed-height embed or `aspect_ratio` (e.g. `16/9`) for a responsive one. Use `max_width` to cap the embed width within the container.
Canonical example
blocks:
- type: iframe-embed
src: /search/
name: The search page, embedded
aspect_ratio: 16/9
max_width: 560px
include
Includes an arbitrary template file.
Schema: src/_lib/utils/block-schema/include.js
Component: block_include
Template: src/_includes/design-system/blocks/include.html
Container width: wide
CMS collection availability: All collections with a block editor
Columns / sidebar: Yes
| Field | Schema type | Presence | Default | CMS label | Description |
|---|---|---|---|---|---|
file |
string |
required | None | Template File Path | Path to the template file to include. |
Usage notes: Escape hatch for custom content that doesn't fit the block system. The `file` value is passed straight to `{% include %}`.
Canonical example
blocks:
- type: include
file: demo-include.html
news-meta
Renders a news post's metadata: author name plus the post date.
Schema: src/_lib/utils/block-schema/news-meta.js
Component: block_news_meta
Template: src/_includes/design-system/blocks/news-meta.html
Container width: wide
CMS collection availability: news
Columns / sidebar: Yes
No block-specific fields. Common fields still apply.
Usage notes: News-only block. No parameters. Reads `author` from the page data and renders it as plain text, falling back to a date-only block when there is no author.
Canonical example
blocks:
- type: news-meta
faqs
Renders question/answer pairs as a definition list, or as collapsed accordions with `collapsible: true`. Available on all page types.
Schema: src/_lib/utils/block-schema/faqs.js
Component: block_faqs
Template: src/_includes/design-system/blocks/faqs.html
Container width: wide
CMS collection availability: All collections with a block editor
Columns / sidebar: Yes
| Field | Schema type | Presence | Default | CMS label | Description |
|---|---|---|---|---|---|
items |
array<object> |
optional | None | FAQs | FAQ question/answer pairs. Answers support markdown formatting. Falls back to page-level `faqs` array if omitted. |
items[].question |
string |
required | None | Question | |
items[].answer |
markdown |
optional | None | Answer (Markdown) | |
collapsible |
boolean |
optional | false |
Collapsible | Collapse each answer under its question, opened by selecting the question (native details/summary, no JavaScript). |
intro_content |
markdown |
optional | None | Intro Content (Markdown) | Markdown content rendered above the block in `.prose`. |
Usage notes: Define FAQs inline via `items`, or omit to fall back to the page-level `faqs` array (useful for pages and guide pages that declare FAQs in frontmatter). Answers are rendered as markdown.
Canonical example
blocks:
- type: faqs
intro_content: "## Frequently asked questions"
items:
- question: How are FAQ answers formatted?
answer: Answers support **markdown**, including links and lists.
- question: Where else can FAQs come from?
answer: Omit `items` and the block falls back to the page-level `faqs` array.
guide-categories
Displays the site-wide guide categories.
Schema: src/_lib/utils/block-schema/guide-categories.js
Component: block_guide_categories
Template: src/_includes/design-system/blocks/guide-categories.html
Container width: wide
CMS collection availability: All collections with a block editor
Columns / sidebar: Yes
No block-specific fields. Common fields still apply.
Usage notes: No block-level parameters. Uses the global `collections.guide-categories`, minus any category with a `property`, which belongs to a single property's guide.
Canonical example
blocks:
- type: guide-categories
guide-header
Renders a guide page's heading: title and optional subtitle.
Schema: src/_lib/utils/block-schema/guide-header.js
Component: block_guide_header
Template: src/_includes/design-system/blocks/guide-header.html
Container width: full
CMS collection availability: guide-pages, guide-categories
Columns / sidebar: Yes
No block-specific fields. Common fields still apply.
Usage notes: Guide-only block. No parameters. Reads `title` and `subtitle` from the page data.
Canonical example
blocks:
- type: guide-header
guide-navigation
Renders a 'Back to <category>' breadcrumb link for a guide page.
Schema: src/_lib/utils/block-schema/guide-navigation.js
Component: block_guide_navigation
Template: src/_includes/design-system/blocks/guide-navigation.html
Container width: full
CMS collection availability: guide-pages
Columns / sidebar: Yes
No block-specific fields. Common fields still apply.
Usage notes: Guide-page-only block. No parameters. Renders nothing when the page has no `guide-category` field.
Canonical example
blocks:
- type: guide-navigation
guide-pages-list
Lists the guide pages that belong to the current guide category (filtered via `guidesByCategory`).
Schema: src/_lib/utils/block-schema/guide-pages-list.js
Component: block_guide_pages_list
Template: src/_includes/design-system/blocks/guide-pages-list.html
Container width: full
CMS collection availability: guide-categories
Columns / sidebar: Yes
No block-specific fields. Common fields still apply.
Usage notes: Guide-category-only block. No parameters. A guide page with a `property` is only listed when the category carries the same `property`. Renders nothing when there are no pages left to show.
Canonical example
blocks:
- type: guide-pages-list
link-button
Standalone centered button linking to an anchor or URL.
Schema: src/_lib/utils/block-schema/link-button.js
Component: block_link_button
Template: src/_includes/design-system/blocks/link-button.html
SCSS: src/css/design-system/_link-button.scss
HTML root: <div class="link-button">
Container width: wide
CMS collection availability: All collections with a block editor
Columns / sidebar: Yes
| Field | Schema type | Presence | Default | CMS label | Description |
|---|---|---|---|---|---|
text |
string |
required | None | Button Text | Button label. |
href |
string |
required | None | URL | Link URL or anchor (e.g. `"#contact"`, `"/about"`). |
variant |
string |
optional | "primary" |
Variant | `"primary"`, `"secondary"`, or `"ghost"`. |
size |
string |
optional | None | Size | `"sm"`, `"lg"`, or omit for default. |
reveal |
string |
optional | None | Reveal Animation | `data-reveal` value. |
Canonical example
blocks:
- type: link-button
text: A standalone button
href: /search/
variant: primary
gallery
Image grid with optional aspect ratio cropping and captions.
Schema: src/_lib/utils/block-schema/gallery.js
Component: block_gallery
Template: src/_includes/design-system/blocks/gallery.html
SCSS: src/css/design-system/_items.scss
HTML root: <ul class="items" role="list">
Container width: wide
CMS collection availability: All collections with a block editor
Columns / sidebar: Yes
| Field | Schema type | Presence | Default | CMS label | Description |
|---|---|---|---|---|---|
items |
array<object> |
required | None | Gallery Images | Image objects. Each: `{image, caption}`. Images processed by `{% image %}` shortcode. |
items[].image |
image |
required | None | Image | |
items[].caption |
string |
optional | None | Caption | |
aspect_ratio |
string |
optional | None | Aspect Ratio | Aspect ratio for images (e.g. `"16/9"`, `"1/1"`, `"4/3"`). Default: no cropping. |
intro_content |
markdown |
optional | None | Intro Content (Markdown) | Markdown content rendered above the block in `.prose`. |
masonry |
boolean |
optional | false |
Masonry Grid | If true, renders as a masonry grid using uWrap for zero-reflow height prediction. |
horizontal |
boolean |
optional | false |
Horizontal Slider | If true, renders as a horizontal slider instead of a wrapping grid. |
Canonical example
blocks:
- type: gallery
intro_content: "## An image grid"
aspect_ratio: 4/3
items:
- image: breakfast.jpg
caption: Breakfast
- image: lunch.jpg
caption: Lunch
- image: dinner.jpg
caption: Dinner
- image: fireworks.jpg
caption: Fireworks
marquee-images
Continuously scrolling marquee of images (e.g. brand logos, partner badges).
Schema: src/_lib/utils/block-schema/marquee-images.js
Component: block_marquee_images
Template: src/_includes/design-system/blocks/marquee-images.html
SCSS: src/css/design-system/_marquee-images.scss
HTML root: <div class="marquee-images">
Container width: full
CMS collection availability: All collections with a block editor
Columns / sidebar: No
| Field | Schema type | Presence | Default | CMS label | Description |
|---|---|---|---|---|---|
items |
array<object> |
required | None | Images | Image objects. Each: `{image, alt, link_url}`. `image` is a path; `alt` is optional alt text; `link_url` is an optional URL to wrap the image in a link. Images are processed via the `{% image %}` shortcode for responsive formats and proper URL normalization. |
items[].image |
string |
required | None | Image Path | |
items[].alt |
string |
optional | None | Alt Text | |
items[].link_url |
string |
optional | None | Link URL | |
speed |
string |
optional | "30s" |
Scroll Speed (e.g. 30s) | CSS animation duration for one full scroll cycle (e.g. `"20s"`, `"45s"`). Slower = longer duration. |
height |
string |
optional | "50px" |
Image Height (e.g. 50px) | CSS height for the images (e.g. `"60px"`, `"80px"`). Width scales proportionally. |
intro_content |
markdown |
optional | None | Intro Content (Markdown) | Markdown content rendered above the block in `.prose`. |
Canonical example
blocks:
- type: marquee-images
height: 60px
items:
- image: breakfast.jpg
alt: Breakfast
- image: lunch.jpg
alt: Lunch
- image: dinner.jpg
alt: Dinner
- image: fireworks.jpg
alt: Fireworks
video-cards
Grid of click-to-play video thumbnails. Takes YouTube IDs and the embed URLs of other providers (Vimeo, Bunny Stream).
Schema: src/_lib/utils/block-schema/video-cards.js
Component: block_video_cards
Template: src/_includes/design-system/blocks/video-cards.html
SCSS: src/css/design-system/_items.scss
HTML root: <ul class="items" role="list">
Container width: wide
CMS collection availability: All collections with a block editor
Columns / sidebar: Yes
| Field | Schema type | Presence | Default | CMS label | Description |
|---|---|---|---|---|---|
videos |
array<object> |
required | None | Videos | Video objects. Each: `{id, name, thumbnail_url}`. `id` is a bare YouTube video ID (e.g. `dQw4w9WgXcQ`) or the full embed URL of another provider (Vimeo, Bunny Stream); never an `<iframe>` snippet. `thumbnail_url` is an image path or URL, and defaults to the provider's own thumbnail for a YouTube ID or a Vimeo URL. |
videos[].id |
string |
required | None | Video ID or embed URL | |
videos[].name |
string |
required | None | Name | |
videos[].thumbnail_url |
string |
optional | None | Thumbnail | |
reveal |
boolean |
optional | true |
Not exposed | Adds `data-reveal` to each video. |
expand |
boolean |
optional | false |
Expand to Full Width | If true, videos fill the available width (1 = 100%, 2 = 50%, 3+ = 33.3%) instead of the standard card grid. |
intro_content |
markdown |
optional | None | Intro Content (Markdown) | Markdown content rendered above the block in `.prose`. |
Usage notes: A video loads only when its thumbnail is pressed, so the grid costs nothing until a visitor plays one. A YouTube or Vimeo thumbnail with no `thumbnail_url` is downloaded at build time, so a build with one needs the network; a video from another provider with no `thumbnail_url` shows a plain panel behind the play button.
Canonical example
blocks:
- type: video-cards
videos:
- id: dQw4w9WgXcQ
name: A YouTube video
thumbnail_url: breakfast.jpg
- id: https://player.vimeo.com/video/76979871
name: A Vimeo video
thumbnail_url: lunch.jpg
- id: https://iframe.mediadelivery.net/embed/1/example
name: A video with no thumbnail
reviews
Customer reviews as cards, newest first: rating, date, the reviewer's words, and a link to where they wrote it.
Schema: src/_lib/utils/block-schema/reviews.js
Component: block_reviews
Template: src/_includes/design-system/blocks/reviews.html
SCSS: src/css/design-system/_reviews.scss
Container width: wide
CMS collection availability: All collections with a block editor
Columns / sidebar: Yes
| Field | Schema type | Presence | Default | CMS label | Description |
|---|---|---|---|---|---|
minimum_rating |
number |
optional | 1 |
Minimum Rating | Only reviews rated at least this are shown, from `1` (every review) to `5`. |
limit |
number |
optional | None | Limit | Show at most this many reviews, newest first. |
horizontal |
boolean |
optional | false |
Horizontal Slider | If true, renders as a horizontal slider instead of a wrapping grid. |
masonry |
boolean |
optional | false |
Masonry Grid | If true, renders as a masonry grid using uWrap for zero-reflow height prediction. |
intro_content |
markdown |
optional | None | Intro Content (Markdown) | Markdown content rendered above the block in `.prose`. |
Usage notes: Renders the `reviews` collection: one markdown file per review in `src/reviews/`, named `YYYY-MM-DD-reviewer.md`, with `name`, `rating`, and optional `source`, `url`, and `avatar` (the reviewer's photo; their initial shows without one) in the frontmatter and the review as the body. `npm run fetch-reviews` writes these files from Google, Facebook and Trustpilot. A review with `hidden: true` is left out. The block renders nothing when no review qualifies.
Canonical example
blocks:
- type: reviews
minimum_rating: 4
limit: 6
intro_content: "## What customers say"
contact-form
A contact form, optionally beside text, that posts to a form service.
Schema: src/_lib/utils/block-schema/contact-form.js
Component: block_contact_form
Template: src/_includes/design-system/blocks/contact-form.html
SCSS: src/css/design-system/_contact-form.scss
Container width: wide
CMS collection availability: All collections with a block editor
Columns / sidebar: No
| Field | Schema type | Presence | Default | CMS label | Description |
|---|---|---|---|---|---|
content |
markdown |
optional | None | Content | Text shown beside the form, rendered as markdown. Without it the form takes the full width. |
fields |
array<object> |
optional | [{"name":"name","label":"Name","type":"text","required":true},{"name":"email","label":"Email","type":"email","required":true},{"name":"phone","label":"Phone","type":"tel","required":false},{"name":"message","label":"Message","type":"textarea","required":true,"rows":6}] |
Form Fields | The form's fields, in order. Defaults to name, email, phone, and message. |
fields[].label |
string |
required | None | Label | |
fields[].name |
string |
optional | Derived | Name | Name the answer is submitted under. Defaults to the label. |
fields[].type |
string |
optional | "text" |
Type | `textarea`, `select`, `radio`, `heading` (a sub-heading between fields, with `note` as its text), or any HTML input type such as `text`, `email`, `tel`, `date`, or `number`. |
fields[].required |
boolean |
optional | false |
Required | |
fields[].placeholder |
string |
optional | None | Placeholder | |
fields[].note |
string |
optional | None | Help Note | |
fields[].options |
array<string> |
optional | None | Options | Choices of a `select` or `radio` field. |
fields[].rows |
number |
optional | 6 |
Rows | Height of a `textarea`, in lines of text. |
submit_text |
string |
optional | "Send Enquiry" |
Submit Button Text | |
redirect |
string |
optional | "/thank-you/" |
Redirect | Page a visitor lands on after sending the form. |
intro_content |
markdown |
optional | None | Intro Content (Markdown) | Markdown content rendered above the block in `.prose`. |
Usage notes: A static site cannot receive a form, so the block posts to the URL in the `contact_form_target` setting, or to [Formspark](formspark.io) when `formspark_id` is set; with neither it shows a notice in place of the form. Setting `botpoison_public_key` adds a [Botpoison](botpoison.com) spam challenge, which needs JavaScript. The submission emails are named after the site, and a field named `email` becomes their reply address on Formspark.
Canonical example
blocks:
- type: contact-form
content: |-
## Get in touch
Tell us what you need and we will get back to you.
fields:
- label: Name
name: name
required: true
- label: Email
name: email
type: email
required: true
- label: What do you need?
name: service
type: select
options:
- A new website
- Help with an existing site
- Something else
- label: Message
name: message
type: textarea
required: true
quote-form
The visitor's quote: the products they added, an estimated price, and a form that sends the request to a form service.
Schema: src/_lib/utils/block-schema/quote-form.js
Component: block_quote_form
Template: src/_includes/design-system/blocks/quote-form.html
SCSS: src/css/design-system/_quote.scss
Container width: wide
CMS collection availability: All collections with a block editor
Columns / sidebar: No
| Field | Schema type | Presence | Default | CMS label | Description |
|---|---|---|---|---|---|
content |
markdown |
optional | None | Content | Text shown beside the form, rendered as markdown. Without it the form takes the full width. |
fields |
array<object> |
optional | [{"name":"name","label":"Name","type":"text","required":true},{"name":"email","label":"Email","type":"email","required":true},{"name":"phone","label":"Phone","type":"tel","required":false},{"name":"start_date","label":"Start date","type":"date","required":true},{"name":"end_date","label":"End date","type":"date","required":true},{"name":"message","label":"Anything else we should know?","type":"textarea","required":false,"rows":4}] |
Form Fields | The form's fields, in order. Defaults to name, email, phone, start and end dates, and a message. |
fields[].label |
string |
required | None | Label | |
fields[].name |
string |
optional | Derived | Name | Name the answer is submitted under. Defaults to the label. |
fields[].type |
string |
optional | "text" |
Type | `textarea`, `select`, `radio`, `heading` (a sub-heading between fields, with `note` as its text), or any HTML input type such as `text`, `email`, `tel`, `date`, or `number`. |
fields[].required |
boolean |
optional | false |
Required | |
fields[].placeholder |
string |
optional | None | Placeholder | |
fields[].note |
string |
optional | None | Help Note | |
fields[].options |
array<string> |
optional | None | Options | Choices of a `select` or `radio` field. |
fields[].rows |
number |
optional | 6 |
Rows | Height of a `textarea`, in lines of text. |
submit_text |
string |
optional | "Request a Quote" |
Submit Button Text | |
redirect |
string |
optional | "/quote-sent/" |
Redirect | Page a visitor lands on after sending the form. |
intro_content |
markdown |
optional | None | Intro Content (Markdown) | Markdown content rendered above the block in `.prose`. |
Usage notes: Products carry an add-to-quote button; this block lists what was added, kept in the visitor's browser, with a quantity for each and an estimated total. It posts like the `contact-form` block, to `contact_form_target` or Formspark, adding the items as `cart_items`, the total as `estimated_total`, and the number of hire days as `hire_days`. Hire products are priced by the days between the `start_date` and `end_date` fields, counting both ends; a stay with no matching option is priced TBC. The quote is cleared once it is sent. Prices are shown in the `currency` setting.
Canonical example
blocks:
- type: quote-form
content: |-
## Your quote
Add products to see them here.
add-to-quote
An add-to-quote button for the product the page describes.
Schema: src/_lib/utils/block-schema/add-to-quote.js
Component: block_add_to_quote
Template: src/_includes/design-system/blocks/add-to-quote.html
SCSS: src/css/design-system/_quote.scss
Container width: wide
CMS collection availability: products
Columns / sidebar: Yes
No block-specific fields. Common fields still apply.
Usage notes: Product-only block. No parameters. A product with more than one option gets a choice of option beside the button; a hire product is added once and priced by the days the visitor asks for. Renders nothing on a page without `options`.
Canonical example
blocks:
- type: add-to-quote
icon-links
Vertical list of links with icons, rendered as a flex column stack.
Schema: src/_lib/utils/block-schema/icon-links.js
Component: block_icon_links
Template: src/_includes/design-system/blocks/icon-links.html
SCSS: src/css/design-system/_icon-links.scss
HTML root: <ul class="icon-links" role="list">
Container width: narrow
CMS collection availability: All collections with a block editor
Columns / sidebar: Yes
| Field | Schema type | Presence | Default | CMS label | Description |
|---|---|---|---|---|---|
intro_content |
markdown |
optional | None | Intro Content (Markdown) | Markdown content rendered above the block in `.prose`. |
items |
array<object> |
required | None | Links | Link objects. Each: `{icon, text, url}`. `url` is optional — items without it render as plain text. Icon can be an Iconify ID (`"prefix:name"`), image path, or raw HTML/emoji. |
items[].icon |
string |
required | None | Icon (Iconify ID or HTML entity) | |
items[].text |
string |
required | None | Link Text | |
items[].url |
string |
optional | None | URL | |
reveal |
boolean |
optional | true |
Not exposed | Adds `data-reveal` to each link item. |
Canonical example
blocks:
- type: icon-links
intro_content: "## Links with icons"
items:
- icon: hugeicons:book-02
text: How it works
url: /how-it-works/
- icon: hugeicons:cube
text: Browse the blocks
url: /blocks/
- icon: hugeicons:search-01
text: Search the site
url: /search/
downloads
List of downloadable files. Each item auto-detects its icon from the file extension and its size from the filesystem at build time.
Schema: src/_lib/utils/block-schema/downloads.js
Component: block_downloads
Template: src/_includes/design-system/blocks/downloads.html
SCSS: src/css/design-system/_downloads.scss
HTML root: <ul class="downloads" role="list">
Container width: narrow
CMS collection availability: All collections with a block editor
Columns / sidebar: Yes
| Field | Schema type | Presence | Default | CMS label | Description |
|---|---|---|---|---|---|
intro_content |
markdown |
optional | None | Intro Content (Markdown) | Markdown content rendered above the block in `.prose`. |
items |
array<object> |
required | None | Downloads | Download objects. Each: `{file, label}`. `file` is a site-relative URL path; `label` is the visible text. |
items[].file |
string |
required | None | File Path (e.g. /files/guide.pdf) | |
items[].label |
string |
required | None | Label | |
reveal |
boolean |
optional | true |
Not exposed | Adds `data-reveal` to each download item. |
Usage notes: The `file` path is resolved against `src/` (e.g. `/files/guide.pdf` reads from `src/files/guide.pdf`). Missing files cause a build error. Ensure the containing directory is configured as a passthrough-copy target so the file is also served to the browser.
Canonical example
blocks:
- type: downloads
intro_content: "## Files to download"
items:
- file: /files/template-overview.txt
label: Template overview (TXT)
snippet
Renders blocks from a named snippet file, enabling reusable block compositions.
Schema: src/_lib/utils/block-schema/snippet.js
Component: block_snippet
Template: src/_includes/design-system/blocks/snippet.html
Container width: wide
CMS collection availability: All collections with a block editor
Columns / sidebar: Yes
| Field | Schema type | Presence | Default | CMS label | Description |
|---|---|---|---|---|---|
reference |
reference |
required | None | Snippet | Filename of the snippet (without `.md` extension) from `src/snippets/`. CMS options: {"collection":"snippets"} |
Usage notes: The referenced snippet must exist in `src/snippets/` and have a `blocks` frontmatter array. The snippet block is transparent — it renders no wrapping section element, so each inner block renders its own section directly.
Canonical example
blocks:
- type: snippet
reference: demo