The contract under every site block, in three layers. Layer 1: server-rendered primitives (Section, Container, SectionHeader, BlockHeading, BlockImage, BlockLink, RichText, createBlocks) that work in any React site with plain props. Layer 2: one zod schema per block (f.* fields, Localized<T>, defineBlock, registry, migrations, lintPage) for CMSs, editors and AI tools. Layer 3: BlockRenderer turns a stored VamosDoc into the page.
Meyhane · Cevizli
Dinner, slowly.
Twenty-two cold meze, fish from the morning boats and a raki list that runs to the back page.
We open at 6 pm, Tuesday to Sunday. The bar takes walk-ins; tables are booked online.
Installation
The whole kit as one package — get updates by bumping the version. Add the styles to globals.css: @import "@vamos-ui/react/styles.css";
Usage
// Layer 1 — any React / Next.js site, plain props
import { Section, Container, SectionHeader, BlockImage, createBlocks } from "@vamos-ui/react/blocks";
<Section theme={{ mode: "dark" }} headingId="intro">
<Container>
<SectionHeader title="Dinner, *slowly*." headingLevel={1} headingId="intro" />
</Container>
</Section>
// Bind Next.js once (no context needed in Server Components)
export const { BlockImage, BlockLink, RichText, bind } = createBlocks({ Image: NextImage, Link: NextLink });
// Layer 2 — define a block once for site, editor and AI tooling
import { z, f, defineBlock, createRegistry } from "@vamos-ui/react/blocks/schema";
const faqBlock = defineBlock({ type: "faq", version: 1, title: "FAQ", category: "content",
schema: z.object({ title: f.heading(), items: f.list(f.group({ q: f.text(), a: f.rich() })) }), component: Faq });
// Layer 3 — render stored JSON
import { BlockRenderer } from "@vamos-ui/react/render";
<BlockRenderer doc={page} registry={createRegistry([faqBlock])} locale="tr" fallbackLocales={["en"]} />Formats are frozen in docs/plan/BLOCK-FORMAT.md (BlockNode, VamosDoc, RichDoc). Changing them requires a doc migration.
zod stays on the server: components import only types from @vamos-ui/react/blocks/schema.
Sections are container-query contexts: blocks lay out by their own width (@2xl, @4xl, @6xl), so the same block works full width, in a column or in a builder iframe.
The blocks title component is BlockHeading (the panel's Heading lives in Typography).
Examples
The same page as JSON
A VamosDoc through the async BlockRenderer: migrate, validate (bad fields are salvaged, not blanked), localize, resolve queries, render. The first page-title block gets the h1 automatically.
Meyhane · Cevizli
Dinner, slowly.
Twenty-two cold meze, fish from the morning boats and a raki list that runs to the back page.
Find us
Rıhtım Caddesi 14, Cevizli. Five minutes from Cevizli metro, valet from 6 pm.
Groups of eight or more: write to us a day ahead.
- Tue – Thu
- 18:00–00:00
- Fri – Sat
- 18:00–02:00
- Sun
- 13:00–23:00
Section theming
theme maps to subtree axes: mode, surface (scene · surface · surface-2 · accent · media), accent (built-in or oklch), material, density, rhythm. Media surfaces are forced dark under a scrim; accent surfaces invert the primary action.
Media surface
Fish from the morning boats
A photo under a scrim; the section is forced dark so text stays readable.
Light · surface-2
Lunch on weekends
A light section keeps its own accent ink, even inside a dark page.
Dark · custom oklch accent
The raki list
Custom accents derive their ink and foreground automatically.
Localized content (tr with en fallback)
Fields store "text" or { en, tr }. localize() picks locale → base language → fallbacks → first, so components only ever see plain strings. Kit strings come from blockLabelsTr.
Meyhane · Cevizli
Akşam yemeği, ağır ağır.
Yirmi iki soğuk meze, sabah teknelerinden balık ve arka sayfaya uzanan bir rakı listesi.
Bize ulaşın
Rıhtım Caddesi 14, Cevizli. Cevizli metrosuna beş dakika, 18.00’den sonra vale.
Sekiz kişi ve üzeri gruplar bir gün önceden bize yazsın.
- Sal – Per
- 18:00–00:00
- Cum – Cmt
- 18:00–02:00
- Paz
- 13:00–23:00
Define a block
One definition for site, editor inspector and JSON tooling. Components receive plain, validated, localized props plus headingLevel, theme, anchor and adapters.
Three rituals, no rush
- 0150 min
Hammam & kese
Warm marble, a black-soap scrub and a cold rinse to close.
- 0275 min
Deep-tissue massage
For desk shoulders. Tell us where it hurts; we skip the rest.
- 0345 min
Facial with clay mask
Gentle enough for the day before an event.
Heading *emphasis* × font presets
One starred span becomes <em>: italic serif in editorial, accent ink in sans presets. The schema rejects a second one; \* is a literal star.
Treatments that take their time
Treatments that take their time
Treatments that take their time
Treatments that take their time
RichText (server renderer)
Renders the RichDoc format: paragraphs, h2–h4, lists, quotes, code, rules; bold, italic, underline, strike, code, highlight, sub/sup and links. Unsafe links render as text; runs sharing a link share one <a>.
Cancellations
Cancel up to 24 hours before your treatment at no charge. Later than that, we keep half the deposit.
Reschedule once for free, by phone or online.
Gift vouchers stay valid for 12 months.
Running late? Call us — we would rather shorten the massage than lose the slot.
Questions: +90 216 000 00 00
lintPage
Warnings, not locks: duplicate ids, a second h1, long or stock-phrase headings, two primary actions, violet custom accents, card-heavy pages, and per-locale translation completeness.
- warningviolet-accentPurple/violet custom accents read as generic; prefer a hue outside 265–320°.
- infomissing-translationMissing tr translation. · props.title
- warningheading-too-longHeadings read best under 10 words. · props.title
- warningcliche-copy“unlock” is stock copy; say what the business actually does. · props.title
- warningmultiple-primary-ctaUse one primary action per block; make the others secondary.
Registry as JSON Schema
registry.toJSONSchema() — a tool definition for AI page drafts or an external builder. Field metadata travels under the vamos key.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"anyOf": [
{
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^[A-Za-z0-9_-]{4,64}$"
},
"type": {
"type": "string",
"const": "intro"
},
"v": {
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
},
"variant": {
"type": "string"
},
"content": {
"type": "array",
"items": {
"anyOf": [
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "br"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"text": {
"type": "string"
},
"marks": {
"type": "array",
"items": {
"oneOf": [
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "bold"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "italic"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "underline"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "strike"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "code"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "highlight"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "sub"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "sup"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "link"
},
"href": {
"type": "string"
},
"newTab": {
"type": "boolean"
},
"title": {
"type": "string"
}
},
"required": [
"type",
"href"
]
}
]
}
}
},
"required": [
"text"
]
}
]
}
},
"theme": {
"type": "object",
"properties": {
"mode": {
"type": "string",
"enum": [
"inherit",
"light",
"dark"
]
},
"surface": {
"type": "string",
"enum": [
"scene",
"surface",
"surface-2",
"accent",
"media"
]
},
"accent": {
"anyOf": [
{
"type": "string",
"enum": [
"lime",
"ocean",
"ember",
"rose",
"graphite"
]
},
{
"type": "string",
"pattern": "^oklch\\(\\s*[\\d.]+%?\\s+[\\d.]+\\s+[\\d.]+(deg)?\\s*(\\/\\s*[\\d.]+%?\\s*)?\\)$"
}
]
},
"material": {
"type": "string",
"enum": [
"inherit",
"glass",
"solid"
]
},
"density": {
"type": "string",
"enum": [
"inherit",
"comfortable",
"compact"
]
},
"rhythm": {
"type": "string",
"enum": [
"tight",
"normal",
"loose"
]
},
"background": {
"type": "object",
"properties": {
"src": {
"type": "string",
"minLength": 1
},
"alt": {
"anyOf": [
{
"type": "string"
},
{
"type": "object",
"propertyNames": {
"type": "string",
"pattern": "^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$"
},
"additionalProperties": {
"type": "string"
}
}
]
},
"width": {
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
},
"height": {
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
},
"blurDataURL": {
"type": "string",
"format": "starts_with",
"pattern": "^data:image\\/.*"
},
"focal": {
"type": "object",
"properties": {
"x": {
"type": "number",
"minimum": 0,
"maximum": 1
},
"y": {
"type": "number",
"minimum": 0,
"maximum": 1
}
},
"required": [
"x",
"y"
]
},
"decorative": {
"type": "boolean"
},
"assetId": {
"type": "string"
}
},
"required": [
"src",
"width",
"height"
]
}
}
},
"anchor": {
"type": "string",
"pattern": "^[a-z0-9-]+$"
},
"hidden": {
"type": "boolean"
},
"schedule": {
"type": "object",
"properties": {
"from": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
},
"until": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
}
}
},
"props": {
"type": "object",
"properties": {
"eyebrow": {
"anyOf": [
{
"type": "string",
"maxLength": 40
},
{
"type": "object",
"propertyNames": {
"type": "string",
"pattern": "^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$"
},
"additionalProperties": {
"type": "string",
"maxLength": 40
}
}
],
"vamos": {
"field": "text",
"localized": true,
"max": 40,
"label": "Eyebrow"
},
"title": "Eyebrow"
},
"title": {
"anyOf": [
{
"type": "string",
"maxLength": 90
},
{
"type": "object",
"propertyNames": {
"type": "string",
"pattern": "^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$"
},
"additionalProperties": {
"type": "string",
"maxLength": 90
}
}
],
"vamos": {
"field": "heading",
"localized": true,
"max": 90,
"label": "Title"
},
"title": "Title"
},
"lead": {
"anyOf": [
{
"type": "string",
"maxLength": 280
},
{
"type": "object",
"propertyNames": {
"type": "string",
"pattern": "^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$"
},
"additionalProperties": {
"type": "string",
"maxLength": 280
}
}
],
"vamos": {
"field": "text",
"localized": true,
"max": 280,
"multiline": true,
"label": "Lead"
},
"title": "Lead"
},
"image": {
"type": "object",
"properties": {
"src": {
"type": "string",
"minLength": 1
},
"alt": {
"anyOf": [
{
"type": "string"
},
{
"type": "object",
"propertyNames": {
"type": "string",
"pattern": "^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$"
},
"additionalProperties": {
"type": "string"
}
}
]
},
"width": {
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
},
"height": {
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
},
"blurDataURL": {
"type": "string",
"format": "starts_with",
"pattern": "^data:image\\/.*"
},
"focal": {
"type": "object",
"properties": {
"x": {
"type": "number",
"minimum": 0,
"maximum": 1
},
"y": {
"type": "number",
"minimum": 0,
"maximum": 1
}
},
"required": [
"x",
"y"
]
},
"decorative": {
"type": "boolean"
},
"assetId": {
"type": "string"
}
},
"required": [
"src",
"width",
"height"
],
"vamos": {
"field": "image",
"aspect": "4:5",
"label": "Photo"
},
"title": "Photo"
},
"actions": {
"maxItems": 2,
"type": "array",
"items": {
"type": "object",
"properties": {
"link": {
"type": "object",
"properties": {
"href": {
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"type": "object",
"propertyNames": {
"type": "string",
"pattern": "^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$"
},
"additionalProperties": {
"type": "string",
"minLength": 1
}
}
]
},
"label": {
"anyOf": [
{
"type": "string",
"maxLength": 80
},
{
"type": "object",
"propertyNames": {
"type": "string",
"pattern": "^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$"
},
"additionalProperties": {
"type": "string",
"maxLength": 80
}
}
]
},
"kind": {
"type": "string",
"enum": [
"url",
"anchor",
"tel",
"mailto",
"whatsapp",
"file"
]
},
"newTab": {
"type": "boolean"
}
},
"required": [
"href",
"label"
]
},
"style": {
"type": "string",
"enum": [
"primary",
"secondary",
"link"
]
}
},
"required": [
"link",
"style"
],
"vamos": {
"field": "cta"
}
},
"vamos": {
"field": "list",
"max": 2,
"label": "Actions"
},
"title": "Actions"
}
},
"required": [
"title"
]
},
"slots": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "array",
"items": {
"$ref": "#/$defs/__schema0"
}
}
}
},
"required": [
"id",
"type",
"props"
]
},
{
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^[A-Za-z0-9_-]{4,64}$"
},
"type": {
"type": "string",
"const": "visit"
},
"v": {
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
},
"variant": {
"type": "string"
},
"content": {
"type": "array",
"items": {
"anyOf": [
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "br"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"text": {
"type": "string"
},
"marks": {
"type": "array",
"items": {
"oneOf": [
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "bold"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "italic"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "underline"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "strike"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "code"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "highlight"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "sub"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "sup"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "link"
},
"href": {
"type": "string"
},
"newTab": {
"type": "boolean"
},
"title": {
"type": "string"
}
},
"required": [
"type",
"href"
]
}
]
}
}
},
"required": [
"text"
]
}
]
}
},
"theme": {
"type": "object",
"properties": {
"mode": {
"type": "string",
"enum": [
"inherit",
"light",
"dark"
]
},
"surface": {
"type": "string",
"enum": [
"scene",
"surface",
"surface-2",
"accent",
"media"
]
},
"accent": {
"anyOf": [
{
"type": "string",
"enum": [
"lime",
"ocean",
"ember",
"rose",
"graphite"
]
},
{
"type": "string",
"pattern": "^oklch\\(\\s*[\\d.]+%?\\s+[\\d.]+\\s+[\\d.]+(deg)?\\s*(\\/\\s*[\\d.]+%?\\s*)?\\)$"
}
]
},
"material": {
"type": "string",
"enum": [
"inherit",
"glass",
"solid"
]
},
"density": {
"type": "string",
"enum": [
"inherit",
"comfortable",
"compact"
]
},
"rhythm": {
"type": "string",
"enum": [
"tight",
"normal",
"loose"
]
},
"background": {
"type": "object",
"properties": {
"src": {
"type": "string",
"minLength": 1
},
"alt": {
"anyOf": [
{
"type": "string"
},
{
"type": "object",
"propertyNames": {
"type": "string",
"pattern": "^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$"
},
"additionalProperties": {
"type": "string"
}
}
]
},
"width": {
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
},
"height": {
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
},
"blurDataURL": {
"type": "string",
"format": "starts_with",
"pattern": "^data:image\\/.*"
},
"focal": {
"type": "object",
"properties": {
"x": {
"type": "number",
"minimum": 0,
"maximum": 1
},
"y": {
"type": "number",
"minimum": 0,
"maximum": 1
}
},
"required": [
"x",
"y"
]
},
"decorative": {
"type": "boolean"
},
"assetId": {
"type": "string"
}
},
"required": [
"src",
"width",
"height"
]
}
}
},
"anchor": {
"type": "string",
"pattern": "^[a-z0-9-]+$"
},
"hidden": {
"type": "boolean"
},
"schedule": {
"type": "object",
"properties": {
"from": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
},
"until": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
}
}
},
"props": {
"type": "object",
"properties": {
"title": {
"anyOf": [
{
"type": "string",
"maxLength": 60
},
{
"type": "object",
"propertyNames": {
"type": "string",
"pattern": "^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$"
},
"additionalProperties": {
"type": "string",
"maxLength": 60
}
}
],
"vamos": {
"field": "heading",
"localized": true,
"max": 60
}
},
"body": {
"anyOf": [
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "doc"
},
"content": {
"type": "array",
"items": {
"$ref": "#/$defs/__schema1"
}
}
},
"required": [
"type",
"content"
]
},
{
"type": "object",
"propertyNames": {
"type": "string",
"pattern": "^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$"
},
"additionalProperties": {
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "doc"
},
"content": {
"type": "array",
"items": {
"$ref": "#/$defs/__schema1"
}
}
},
"required": [
"type",
"content"
]
}
}
],
"vamos": {
"field": "rich",
"localized": true,
"nodes": [
"paragraph",
"list"
],
"marks": [
"bold",
"italic",
"link"
]
}
},
"hours": {
"minItems": 1,
"maxItems": 7,
"type": "array",
"items": {
"type": "object",
"properties": {
"days": {
"anyOf": [
{
"type": "string",
"maxLength": 24
},
{
"type": "object",
"propertyNames": {
"type": "string",
"pattern": "^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$"
},
"additionalProperties": {
"type": "string",
"maxLength": 24
}
}
],
"vamos": {
"field": "text",
"localized": true,
"max": 24
}
},
"time": {
"type": "string",
"maxLength": 24,
"vamos": {
"field": "text",
"localized": false,
"max": 24
}
}
},
"required": [
"days",
"time"
],
"vamos": {
"field": "group"
}
},
"vamos": {
"field": "list",
"min": 1,
"max": 7
}
}
},
"required": [
"title",
"body",
"hours"
]
},
"slots": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "array",
"items": {
"$ref": "#/$defs/__schema0"
}
}
}
},
"required": [
"id",
"type",
"props"
]
}
],
"$defs": {
"__schema0": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^[A-Za-z0-9_-]{4,64}$"
},
"type": {
"type": "string",
"pattern": "^[a-z][a-z0-9]*(-[a-z0-9]+)*$"
},
"v": {
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
},
"variant": {
"type": "string"
},
"content": {
"type": "array",
"items": {
"anyOf": [
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "br"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"text": {
"type": "string"
},
"marks": {
"type": "array",
"items": {
"oneOf": [
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "bold"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "italic"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "underline"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "strike"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "code"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "highlight"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "sub"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "sup"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "link"
},
"href": {
"type": "string"
},
"newTab": {
"type": "boolean"
},
"title": {
"type": "string"
}
},
"required": [
"type",
"href"
]
}
]
}
}
},
"required": [
"text"
]
}
]
}
},
"theme": {
"type": "object",
"properties": {
"mode": {
"type": "string",
"enum": [
"inherit",
"light",
"dark"
]
},
"surface": {
"type": "string",
"enum": [
"scene",
"surface",
"surface-2",
"accent",
"media"
]
},
"accent": {
"anyOf": [
{
"type": "string",
"enum": [
"lime",
"ocean",
"ember",
"rose",
"graphite"
]
},
{
"type": "string",
"pattern": "^oklch\\(\\s*[\\d.]+%?\\s+[\\d.]+\\s+[\\d.]+(deg)?\\s*(\\/\\s*[\\d.]+%?\\s*)?\\)$"
}
]
},
"material": {
"type": "string",
"enum": [
"inherit",
"glass",
"solid"
]
},
"density": {
"type": "string",
"enum": [
"inherit",
"comfortable",
"compact"
]
},
"rhythm": {
"type": "string",
"enum": [
"tight",
"normal",
"loose"
]
},
"background": {
"type": "object",
"properties": {
"src": {
"type": "string",
"minLength": 1
},
"alt": {
"anyOf": [
{
"type": "string"
},
{
"type": "object",
"propertyNames": {
"type": "string",
"pattern": "^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$"
},
"additionalProperties": {
"type": "string"
}
}
]
},
"width": {
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
},
"height": {
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
},
"blurDataURL": {
"type": "string",
"format": "starts_with",
"pattern": "^data:image\\/.*"
},
"focal": {
"type": "object",
"properties": {
"x": {
"type": "number",
"minimum": 0,
"maximum": 1
},
"y": {
"type": "number",
"minimum": 0,
"maximum": 1
}
},
"required": [
"x",
"y"
]
},
"decorative": {
"type": "boolean"
},
"assetId": {
"type": "string"
}
},
"required": [
"src",
"width",
"height"
]
}
}
},
"anchor": {
"type": "string",
"pattern": "^[a-z0-9-]+$"
},
"hidden": {
"type": "boolean"
},
"schedule": {
"type": "object",
"properties": {
"from": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
},
"until": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
}
}
},
"props": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"slots": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "array",
"items": {
"$ref": "#/$defs/__schema0"
}
}
}
},
"required": [
"id",
"type"
]
},
"__schema1": {
"oneOf": [
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "paragraph"
},
"content": {
"type": "array",
"items": {
"anyOf": [
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "br"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"text": {
"type": "string"
},
"marks": {
"type": "array",
"items": {
"oneOf": [
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "bold"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "italic"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "underline"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "strike"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "code"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "highlight"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "sub"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "sup"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "link"
},
"href": {
"type": "string"
},
"newTab": {
"type": "boolean"
},
"title": {
"type": "string"
}
},
"required": [
"type",
"href"
]
}
]
}
}
},
"required": [
"text"
]
}
]
}
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "heading"
},
"level": {
"anyOf": [
{
"type": "number",
"const": 2
},
{
"type": "number",
"const": 3
},
{
"type": "number",
"const": 4
}
]
},
"content": {
"type": "array",
"items": {
"anyOf": [
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "br"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"text": {
"type": "string"
},
"marks": {
"type": "array",
"items": {
"oneOf": [
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "bold"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "italic"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "underline"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "strike"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "code"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "highlight"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "sub"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "sup"
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "link"
},
"href": {
"type": "string"
},
"newTab": {
"type": "boolean"
},
"title": {
"type": "string"
}
},
"required": [
"type",
"href"
]
}
]
}
}
},
"required": [
"text"
]
}
]
}
},
"anchor": {
"type": "string",
"pattern": "^[a-z0-9-]+$"
}
},
"required": [
"type",
"level"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "list"
},
"ordered": {
"type": "boolean"
},
"start": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"content": {
"type": "array",
"items": {
"$ref": "#/$defs/__schema1"
}
}
},
"required": [
"content"
]
}
}
},
"required": [
"type",
"items"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "blockquote"
},
"content": {
"type": "array",
"items": {
"$ref": "#/$defs/__schema1"
}
},
"cite": {
"type": "string"
}
},
"required": [
"type",
"content"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "code"
},
"text": {
"type": "string"
},
"language": {
"type": "string"
}
},
"required": [
"type",
"text"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "hr"
}
},
"required": [
"type"
]
}
]
}
}
}API
Section
| Prop | Type | Default |
|---|---|---|
theme{ mode, surface, accent, material, density, rhythm, background } — mapped onto class/data-* so tokens cascade into the subtree. | BlockTheme | — |
asLandmark element. | "section" | "div" | "header" | "footer" | "aside" | "article" | "nav" | "section" |
anchorid / URL anchor (#menu). | string | — |
headingIdSets aria-labelledby so the section is a named region. | string | — |
blockId / blockTypeEmitted as data-block-id / data-block-type in edit mode. | string | — |
editModeBuilder canvas attributes. | boolean | false |
priorityEager-load the media background (above the fold). | boolean | false |
components{ Image, Link } for the media background. | BlockAdapters | — |
Container
| Prop | Type | Default |
|---|---|---|
size76rem / 90rem / 42rem / edge to edge; fluid --gutter padding. | "content" | "wide" | "prose" | "full" | "content" |
SectionHeader
| Prop | Type | Default |
|---|---|---|
eyebrowSmall mono line above the title. | ReactNode | — |
indexIndex before the eyebrow ("01"). | string | — |
titleA string supports one *emphasis*. | ReactNode | — |
leadIntro paragraph, capped at 64ch. | ReactNode | — |
actionsLinks/buttons; one primary at most. | ReactNode | — |
alignsplit: title 5–6 cols beside lead 6 cols on wide containers. center only for statement heroes / minimal CTAs. | "start" | "split" | "center" | "start" |
sizedisplay-m (section h2) or display-l. | "m" | "l" | "m" |
headingLevelOutline level (the renderer passes 1 to the page's first intro). | 1 | 2 | 3 | 2 |
headingIdId for the title — pass the same to Section headingId. | string | — |
editModedata-field attributes and an "Add a title" placeholder. | boolean | false |
BlockHeading
| Prop | Type | Default |
|---|---|---|
textCopy; strings parse one *emphasis* (\* = literal star). | ReactNode | — |
levelOutline level. | 1–6 | 2 |
sizeFluid --text-* scale, multiplied by the preset's --display-scale. | "display-2xl" | "display-xl" | "display-l" | "display-m" | "h3" | "display-m" |
editMode / fieldPlaceholder + data-field in edit mode. | boolean / string | — |
BlockImage
| Prop | Type | Default |
|---|---|---|
src / alt / width / heightIntrinsic size prevents layout shift. alt is required unless decorative. | string / string / number / number | — |
decorativeRenders alt="". | boolean | false |
aspectFixed-ratio crop (cover). | "1:1" | "4:5" | "3:4" | "4:3" | "3:2" | "2:3" | "16:9" | "21:9" | number | — |
focal0..1 focal point → object-position. | { x: number; y: number } | — |
fitobject-fit. | "cover" | "contain" | "cover" |
blurDataURLPlaceholder painted under the image. | string | — |
sizesResponsive sizes hint. | string | — |
priorityEager + fetchpriority=high for the LCP image. | boolean | false |
captionWraps in figure + figcaption. | ReactNode | — |
imgClassNameClasses for the <img> (className goes on the frame). | string | — |
componentsUses components.Image (Next Image gets fill when cropped). | BlockAdapters | — |
BlockLink
| Prop | Type | Default |
|---|---|---|
hrefSanitised: javascript:, data: and unknown schemes render as plain text. | string | — |
newTabtarget=_blank, rel=noopener noreferrer and an announced "(opens in a new tab)". | boolean | false |
appearanceCTA styles. Primary inverts on accent surfaces. | "primary" | "secondary" | "link" | — |
sizeButton size when appearance is set. | "md" | "lg" | "xl" | "lg" |
componentsInternal paths use components.Link; external, mailto and tel stay <a>. | BlockAdapters | — |
RichText
| Prop | Type | Default |
|---|---|---|
docStrings: blank line = paragraph, newline = <br>. | RichDoc | RichBlock[] | RichInline[] | string | — |
size14 / 16 / 18 px base. | "sm" | "md" | "lg" | "md" |
headingOffsetShift stored h2 → h3 inside a block whose title is h2. | 0 | 1 | 2 | 0 |
fullWidthRemove the 64ch measure. | boolean | false |
components / labels / localeLink adapter and the new-tab string. | BlockAdapters / Partial<BlockLabels> / string | — |
createBlocks(options)
| Prop | Type | Default |
|---|---|---|
Image / LinkFramework bindings (Next Image / Link are compatible). | VamosImageComponent / VamosLinkComponent | — |
labelse.g. blockLabelsTr. | Partial<BlockLabels> | — |
localeDefault Intl locale. | string | — |
→ returnsbind(Component) injects components/labels/locale into any block. | { Section, SectionHeader, BlockHeading, BlockImage, BlockLink, RichText, SkipLink, bind, adapters } | — |
defineBlock(def)
| Prop | Type | Default |
|---|---|---|
type / versionKebab-case, never renamed. Every version below the current needs migrate[n] (v n → n+1). | string / number | — |
title / category / region / keywords / iconInserter metadata. region default "body". | string / BlockCategory / BlockRegion / string[] / string | — |
schemaProps, built from f.* fields. | z.ZodObject | — |
variants / defaultVariant / variantMetaStored on node.variant. | readonly string[] / string / Record | — |
fields / slotsInspector hints; child-block slots { allow, min, max }. | FieldHints / Record<string, SlotSpec> | — |
migrateRuns on read; content is never dropped. | Record<number, (props, node) => unknown> | — |
supportspageTitle: the first such block gets h1. | { anchor, theme, spacing, align, pageTitle } | — |
preview / examplesRealistic sample content; also the salvage source for invalid fields. | { props, variant, image } / (variant, locale) => props | — |
jsonLd / lintMerged into one @graph; block-specific lint rules. | (props, ctx) => object | null / (props, node) => issues | — |
componentThe same component on the site and in the editor canvas. | ComponentType<BlockComponentProps<P>> | — |
f.* fields
| Prop | Type | Default |
|---|---|---|
f.heading({ max })One *emphasis*, no emoji. | Localized<string> | — |
f.text({ max, multiline, localized })localized: false stores a plain string. | Localized<string> | — |
f.rich({ nodes, marks })Allow-listed nodes and marks. | Localized<RichDoc> | — |
f.image({ aspect }) / f.link() / f.cta()alt or decorative required; unsafe hrefs rejected. | ImageRef / LinkRef / { link, style } | — |
f.list(item, { min, max, itemLabel }) / f.group(shape)Repeaters and nested groups. | T[] / object | — |
f.select / f.toggle / f.number / f.icon / f.datef.icon uses a 150-name allow-list. | enum / boolean / number / lucide name / YYYY-MM-DD | — |
f.money / f.phone / f.address / f.hoursStructural, never localized. | { amount: decimal string, currency } / E.164 / AddressValue / OpeningHours | — |
f.query(resource, { into }) / f.embed({ allow }) / f.social()Queries are resolved by BlockRenderer resolvers into the prop `into`. | QueryValue / EmbedValue / SocialLink[] | — |
BlockRenderer / renderDoc
| Prop | Type | Default |
|---|---|---|
docStored page; { v: 1 } is accepted as an alias of { version: 1 }. | VamosDoc | — |
registrycreateRegistry([...]) — pick only the blocks a site uses. | BlockRegistry | — |
locale / fallbackLocalesDefault doc.lang ?? doc.locales[0]; fallback to the doc's other locales, then en. | string / string[] | — |
components / labelsPassed to every block. | BlockAdapters / Partial<BlockLabels> | — |
resolversBlockRenderer only; run in parallel. A failure leaves the block's empty state. | Record<string, (query, ctx) => data> | — |
editModeKeeps hidden/scheduled blocks, wraps blocks in display:contents data-block-id, shows placeholders. | boolean | false |
devUnknown/invalid notices; production skips unknown blocks silently. | boolean | not production |
regions / mainId / jsonLdWhich layout regions, a <main id> skip target, merged JSON-LD (default on). | BlockRegion[] / string / boolean | — |
onIssue / nowLogging hook; clock for schedules. | (issue) => void / Date | — |