A single source of truth for content blocks
BlockSpec is an open standard for defining a content block as structured, machine-readable JSON. The HTML contract, content fields, layout variants, design tokens, and authoring guidance — all in one canonical file.
BlockSpec describes a single block as a complete unit: how it renders, what content it accepts, which design tokens it consumes, and how to fill it in well. One spec replaces the gap between design system, CMS schema, and authoring documentation.
The problem it solves
Component libraries describe how blocks look. CMS field schemas describe what blocks contain. Design tokens describe how they're styled. Authoring guidance lives in a Notion doc someone wrote a year ago.
Every layer is owned by a different tool, drifts from the others, and gets rebuilt from scratch on every project.
What it replaces
- Separate Figma component definitions
- ACF or Gutenberg field schemas drifting from design
- Verbal handoffs between designers and developers
- "What goes in this slot?" emails to clients
- Manual rebuilds of the same Hero in every project
How it works
ProcessA BlockSpec file is a single .json document describing one block. A library is a collection of BlockSpec files — one per block — composable into pages by StructureSpec.
-
Define the block
Create a
blockspec.jsonfile using the open schema. Specify the HTML contract —data-blockslug, allowed surfaces and layouts, structural pattern — alongside content fields, variants, token roles consumed, and authoring guidance. -
Compose pages with StructureSpec
StructureSpec references BlockSpec items by slug to compose page archetypes. A "Marketing landing page" type might list Hero, Feature grid, Testimonial, and CTA in sequence — each one a separate BlockSpec file in the library.
-
Render with brand tokens
A consumer reads the block's HTML contract from BlockSpec and the visual styling from a parent BrandSpec. The same block renders consistently across projects, themed by token swap rather than restyled by hand.
-
Author with guidance
Field-level prompts, character limits, and voice guidance turn a blank CMS form into a guided framework. The block tells the author what's expected, in what order, with what tone — and AI tools can use the same guidance to draft content directly.
For AI tools: BlockSpec gives content-generating models a structured slot specification. Instead of inferring what a Hero needs, the model receives a typed schema — required fields, character limits, voice guidance — and produces output that fits the block precisely.
Schema structure
v0.1.0A BlockSpec document is a JSON object with a root block key and seven top-level sections. All sections are optional at the object level but recommended for complete block definition. Identity and contract are the minimum viable spec.
block.identity
Block name, slug, category, description, and version. The slug is the canonical identifier used by StructureSpec and CMS integrations to reference this block.
block.contract
The DOM contract. The data-block attribute value, allowed data-surface values, allowed data-layout values, structural pattern, and breakpoint policy.
block.fields
Content fields the block accepts. Each field defines a name, type, label, help text, required flag, and validation constraints such as character limits or allowed values.
block.variants
Layout variants of the same block. A Hero might have centered, split, media-right variants — each declares which fields it uses and which layout attribute it maps to.
block.tokens
Design-token roles the block consumes. References roles (primary, heading, section-spacing) rather than values, so the block can be themed by any compatible BrandSpec.
block.guidance
Voice guidance and field-level prompts. Authoring help for humans; structured scaffolding for AI tools. The guidance layer turns a generic block into a guided form.
block.metadata
Administrative fields: block name, version string, last updated timestamp, and tags. Used for versioning, auditing, and tool compatibility checks.
Field definition fields
| Field | Type | Description | Example |
|---|---|---|---|
| name | string | Field slug — used as the key when content is stored | "headline" |
| label | string | Human-readable label shown in the authoring UI | "Headline" |
| type | string | Field type: text, rich-text, link, media, number, select, repeater | "text" |
| required | boolean | Whether the field must be populated for the block to render | true |
| characterLimit | integer | Maximum character count (text and rich-text fields) | 80 |
| help | string | Inline help text shown to the author beside the field | "The single sentence that earns attention." |
Contract fields
The contract object defines the HTML/DOM shape every instance of this block must conform to. It is the public interface that consuming tools render against.
| Field | Type | Description | Example |
|---|---|---|---|
| dataBlock | string | Value of the data-block attribute on the root element | "hero" |
| structure | string | The DOM nesting pattern (selector-style) | "section[data-block=hero] > .container > .inner" |
| surfaces | string[] | Allowed values for the data-surface attribute | ["default", "muted", "inverse"] |
| layouts | string[] | Allowed values for the data-layout attribute | ["centered", "split"] |
| scopedCss | boolean | Whether the block ships with scoped CSS (BEM or attribute-scoped) | true |
| breakpointPolicy | string | Responsive policy: fluid, single-breakpoint, multi-breakpoint | "fluid" |
Example file
JSONA complete BlockSpec document for a Hero block. All fields shown are part of the v0.1.0 schema.
{
"_export": {
"blockName": "Hero",
"blockSpecVersion": "0.1.0",
"exportedAt": "2026-05-02T10:00:00.000Z"
},
"block": {
/* ── Identity ────────────────────────────── */
"identity": {
"name": "Hero",
"slug": "hero",
"category": "lead",
"description": "Page-opening section with headline, supporting copy, and CTAs.",
"version": "1.0.0"
},
/* ── Contract ────────────────────────────── */
"contract": {
"dataBlock": "hero",
"structure": "section[data-block=hero][data-surface][data-layout] > .container > .inner",
"surfaces": ["default", "muted", "inverse", "accent"],
"layouts": ["centered", "split", "media-right", "media-left"],
"scopedCss": true,
"breakpointPolicy": "fluid"
},
/* ── Fields ──────────────────────────────── */
"fields": [
{
"name": "headline",
"label": "Headline",
"type": "text",
"required": true,
"characterLimit": 80,
"help": "The single sentence that earns attention."
},
{
"name": "subhead",
"label": "Supporting copy",
"type": "rich-text",
"required": false,
"characterLimit": 240,
"help": "One or two sentences expanding on the headline."
},
{
"name": "primaryCta",
"label": "Primary CTA",
"type": "link",
"required": false,
"help": "The primary action you want people to take."
},
{
"name": "media",
"label": "Hero image or video",
"type": "media",
"required": false,
"help": "Used in split, media-right, and media-left layouts."
}
],
/* ── Variants ────────────────────────────── */
"variants": [
{
"name": "centered",
"layout": "centered",
"description": "Headline and copy centered above CTAs. No media.",
"fieldsUsed": ["headline", "subhead", "primaryCta"]
},
{
"name": "split",
"layout": "split",
"description": "Headline left, media right, 50/50 split.",
"fieldsUsed": ["headline", "subhead", "primaryCta", "media"]
}
],
/* ── Tokens consumed ─────────────────────── */
"tokens": {
"colors": ["primary", "neutral", "accent"],
"typography": ["heading", "body"],
"spacing": ["section", "stack-md"]
},
/* ── Guidance ────────────────────────────── */
"guidance": {
"voice": "Clear and direct. Earn attention with specificity.",
"fieldPrompts": {
"headline": "The single sentence that captures what this page is about. Choose clear over clever.",
"subhead": "One or two sentences expanding on the headline with the why or the how.",
"primaryCta": "The verb is more important than the adjective. 'Start now' beats 'Get amazing access'."
}
},
/* ── Metadata ────────────────────────────── */
"metadata": {
"name": "Hero",
"version": "1.0.0",
"lastUpdated": "2026-05-02T00:00:00.000Z"
}
},
"$schema": "https://blockspec.org/schema/v0.1/blockspec.json",
"version": "0.1.0"
}
Using BlockSpec
OutputBlockSpec is designed to be consumed by any tool that needs a structured block definition — page renderers, CMS field generators, AI authoring tools, design-system documentation. Below are reference patterns for the most common outputs.
Render the HTML contract
Read the contract from BlockSpec and emit the canonical DOM structure. Surface and layout are passed in as attributes; field content fills the inner template.
/* Generated from blockspec.json → block.contract */
<section data-block="hero" data-surface="default" data-layout="centered">
<div class="container">
<div class="inner">
<h1>{{ headline }}</h1>
<p>{{ subhead }}</p>
<a href="{{ primaryCta.href }}">{{ primaryCta.label }}</a>
</div>
</div>
</section>
Generate a CMS field schema
Map BlockSpec fields directly to ACF field groups, Gutenberg block attributes, or any other CMS field schema. The block becomes a guided form, not a blank page.
// Generate ACF field group from BlockSpec
const spec = await fetch('/blocks/hero/blockspec.json').then(r => r.json());
const acfFields = spec.block.fields.map(field => ({
key: `field_${field.name}`,
label: field.label,
name: field.name,
type: mapType(field.type), // 'text' → 'text'; 'rich-text' → 'wysiwyg'
required: field.required,
maxlength: field.characterLimit,
instructions: field.help
}));
AI-assisted authoring
Combine BlockSpec field guidance with PersonSpec or BrandSpec voice to generate calibrated draft content. The block defines what's needed; the brand or person defines how it should sound.
// Draft Hero content from BlockSpec + BrandSpec voice
const block = await fetch('/blocks/hero/blockspec.json').then(r => r.json());
const brand = await fetch('/brandspec.json').then(r => r.json());
const draftPrompt = `
Block: ${block.block.identity.name}
Voice guidance: ${block.block.guidance.voice}
Brand tone: ${brand.brand.voice.tone.join(', ')}
Generate content for these fields:
${block.block.fields.map(f =>
`- ${f.label} (max ${f.characterLimit} chars): ${block.block.guidance.fieldPrompts[f.name]}`
).join('\n')}
`;
Composition with StructureSpec: StructureSpec page archetypes reference BlockSpec items by slug. A "Marketing landing page" archetype lists hero → feature-grid → testimonial → cta in sequence; the renderer pulls each BlockSpec to lay them out, then fills them with content from the CMS.
Standards & references
OpenBlockSpec is designed to be standards-aware. It builds on, references, and is interoperable with several established frameworks. It is not a replacement for any of them — it is a structured layer that makes them composable in a single, machine-readable file.
BrandSpec
The sibling open standard for organisational brand definitions. BlockSpec consumes BrandSpec tokens by role — when a block declares it consumes the primary colour role and heading typography role, those values come from the BrandSpec applied at render time.
StructureSpec
The sibling open standard for page structure. StructureSpec consumes BlockSpec — page archetypes reference blocks by slug to compose sequences. BlockSpec describes the parts; StructureSpec describes the wholes.
JSON Schema
The BlockSpec document format is defined using JSON Schema (Draft 2020-12). The $schema field in every BlockSpec file points to the published schema, enabling validation in any compatible tool. Editors such as VS Code will auto-complete and validate a BlockSpec file when the schema URI is present.
HTML5 & data attributes
The BlockSpec contract is HTML-native. Blocks render as standard semantic elements with data-* attributes for surface and layout — not custom elements, not framework-specific components. Any rendering environment that understands HTML can consume a BlockSpec.
BEM & scoped CSS
BlockSpec's scopedCss declaration follows BEM conventions or attribute-scoped equivalents. Styles attach to [data-block=name] selectors, ensuring blocks ship with their own CSS that doesn't leak into the surrounding cascade.
WAI-ARIA
BlockSpec contracts are accessibility-aware by default. Each block declares the semantic landmark it represents and inherits ARIA conventions from the underlying HTML elements rather than reimplementing them.
Relationship to StructureSpec: BlockSpec and StructureSpec are designed as a pair. BlockSpec defines individual blocks as complete units (contract + fields + guidance). StructureSpec composes them into page archetypes. Together they replace what design-system tools, CMS field schemas, and authoring documentation each cover separately.