Block UI Renderer Contract
Phase 1 defines the intended public rendering contract between CMS layouts, slots, and block types and the shipped WebBlocks UI primitives already loaded by the public layout. This phase is documentation-only. It does not rewrite renderers yet.
Verified WebBlocks UI Primitives
Verified against the actual shipped assets used by CMS:
- CSS:
https://cdn.jsdelivr.net/gh/fklavyenet/webblocks-ui@v2.7.12/packages/webblocks/dist/webblocks-ui.css - Icons CSS:
https://cdn.jsdelivr.net/gh/fklavyenet/webblocks-ui@v2.7.12/packages/webblocks/dist/webblocks-icons.css - JS:
https://cdn.jsdelivr.net/gh/fklavyenet/webblocks-ui@v2.7.12/packages/webblocks/dist/webblocks-ui.js
Confirmed primitives and patterns:
wb-content-shellwb-content-headerwb-content-bodywb-content-footerwb-promowb-calloutwb-link-listwb-statwb-gallerywb-alertwb-rich-textwb-rich-text-readablewb-rich-text-compactwb-rich-text-loosewb-btnwb-gridwb-grid-2wb-grid-3wb-grid-4wb-stackwb-gap-1wb-gap-2wb-gap-3wb-gap-4wb-gap-6wb-gap-8
Missing primitives and UI gaps for now:
wb-prose— NOT FOUND in the shipped WebBlocks UI assets. Treat this as a UI gap and do not rely on it for Phase 2 layout or renderer alignment.wb-promo-muted— NOT FOUND in the shipped WebBlocks UI assets.wb-promo-accent— NOT FOUND in the shipped WebBlocks UI assets.wb-cluster-3— NOT FOUND in the shipped WebBlocks UI assets.
Verified shipped JS hooks relevant to current public rendering:
data-wb-toggle="dropdown"data-wb-toggle="modal"data-wb-gallery-target#wb-overlay-root
Phase 2B Layout Modes
Public pages now use explicit layout composition modes:
stackis the default mode.sidebaris used when the page has a populated sidebar slot.contentis reserved for future docs/editorial pages with explicit metadata and must not be guessed from URL or title.wb-content-shellis not a universal wrapper and should only appear when the page metadata explicitly supports a content-shell presentation.
Matrix
| <table><thead><tr><th>CMS Block</th><th>WebBlocks UI primitive/pattern</th><th>Status</th><th>Priority</th><th>Next action</th></tr></thead><tbody><tr><td>public layout</td><td><code>wb-public-body</code>, <code>wb-public-main</code>, <code>wb-container</code></td><td>acceptable</td><td>P0 shell/layout</td><td>keep the shell stable and move slot-specific chrome toward documented primitives</td></tr><tr><td>header slot</td><td><code>wb-section</code>, <code>wb-container</code>, nav/list primitives</td><td>weak</td><td>P0 shell/layout</td><td>align custom header chrome with documented shell rules</td></tr><tr><td>main slot</td><td><code>wb-public-main</code>, <code>wb-container</code>, <code>wb-content-shell</code></td><td>acceptable</td><td>P0 shell/layout</td><td>add <code>wb-content-shell</code> usage where content reads like article/docs content</td></tr><tr><td>sidebar slot</td><td><code>wb-grid</code>, <code>wb-stack</code>, optional <code>wb-content-shell</code> aside</td><td>weak</td><td>P0 shell/layout</td><td>stop treating generic sidebars as faux app sidebars</td></tr><tr><td>footer slot</td><td><code>wb-section</code>, <code>wb-container</code>, <code>wb-grid</code>, link-list/nav primitives</td><td>acceptable</td><td>P0 shell/layout</td><td>keep footer on shipped layout primitives and avoid extra custom classes</td></tr><tr><td><code>heading</code></td><td>legacy removed</td><td>retired</td><td>P0 removed</td><td>do not reintroduce; <code>header</code> is the canonical heading/title block</td></tr><tr><td><code>text</code></td><td>body copy in <code>wb-stack</code> rhythm</td><td>acceptable</td><td>P2 content quality</td><td>keep simple output and avoid bespoke typography wrappers</td></tr><tr><td><code>rich-text</code></td><td><code>wb-rich-text wb-rich-text-readable</code></td><td>acceptable</td><td>P2 content quality</td><td>keep safe body copy scoped to the shipped rich text primitive</td></tr><tr><td><code>html</code></td><td>trusted raw HTML in public block wrapper</td><td>acceptable</td><td>P3 later/custom</td><td>keep restricted to trusted editorial/admin usage and hoist any shipped overlay content into the shared page overlay root</td></tr><tr><td><code>section</code></td><td><code>wb-section</code>, optional <code>wb-promo</code></td><td>acceptable</td><td>P1 public marketing/docs</td><td>keep default section stable and reserve promo semantics for explicit variants</td></tr><tr><td><code>columns</code></td><td><code>wb-grid</code>, <code>wb-grid-2</code>, <code>wb-grid-3</code>, <code>wb-grid-4</code>, <code>wb-link-list</code></td><td>acceptable</td><td>P1 public marketing/docs</td><td>keep parent-driven variants explicit and avoid reintroducing forced wrapper cards</td></tr><tr><td><code>column_item</code></td><td>plain cell, <code>wb-card</code>, <code>wb-stat</code>, or link-list item</td><td>acceptable</td><td>P1 public marketing/docs</td><td>keep item rendering driven by the parent <code>columns.variant</code> mapping</td></tr><tr><td><code>callout</code></td><td><code>wb-alert</code>, optional <code>wb-callout</code></td><td>acceptable</td><td>P2 content quality</td><td>widen variant mapping if a shipped sidebar/help callout exists</td></tr><tr><td><code>quote</code></td><td>semantic <code>blockquote</code>, optional framed card</td><td>acceptable</td><td>P2 content quality</td><td>make framing conditional rather than always card-like</td></tr><tr><td><code>faq</code></td><td>simple Q/A card</td><td>acceptable</td><td>P3 custom/fallback</td><td>keep the stable single-card treatment and let grouped disclosure live in accordion blocks</td></tr><tr><td><code>tabs</code></td><td>interactive tabs pattern</td><td>weak</td><td>P2 content quality</td><td>defer until a real shipped tabs pattern exists</td></tr><tr><td><code>button</code></td><td><code>wb-btn</code> variants</td><td>weak</td><td>P1 public marketing/docs</td><td>map all supported CMS variants and add button-group direction</td></tr><tr><td><code>image</code></td><td>semantic <code>figure</code>, <code>img</code>, <code>figcaption</code></td><td>weak</td><td>P2 content quality</td><td>honor link behavior and only add media framing when needed</td></tr><tr><td><code>gallery</code></td><td><code>wb-gallery</code> plus overlay root modal</td><td>acceptable</td><td>P1 public marketing/docs</td><td>keep using shipped gallery hooks and central overlay root while keeping intro copy outside the Gallery block itself</td></tr><tr><td><code>download</code></td><td><code>wb-btn</code> or card-with-button CTA</td><td>acceptable</td><td>P2 content quality</td><td>add explicit card/download variants later</td></tr><tr><td><code>navigation-auto</code></td><td>nav/list primitives, optional <code>wb-link-list</code></td><td>acceptable</td><td>P1 public marketing/docs</td><td>keep simple menus simple and reserve docs sidebars for real docs shells</td></tr><tr><td><code>menu</code></td><td>legacy alias of <code>navigation-auto</code></td><td>acceptable</td><td>P3 later/custom</td><td>keep for migrated data only</td></tr><tr><td><code>contact_form</code></td><td>form primitives, <code>wb-btn</code>, <code>wb-alert</code></td><td>acceptable</td><td>P1 public marketing/docs</td><td>keep structured editor fields and avoid raw HTML forms</td></tr><tr><td><code>hero</code></td><td><code>wb-promo</code> marketing shell</td><td>acceptable</td><td>P1 public marketing/docs</td><td>keep hero first-class, translated, and action-driven through child button blocks</td></tr><tr><td><code>card-grid</code></td><td>card grid pattern</td><td>weak</td><td>P1 public marketing/docs</td><td>keep the current structured card grid stable and revisit first-class modeling later</td></tr><tr><td><code>showcase-list</code></td><td>showcase/gallery pattern</td><td>weak</td><td>P3 later/custom</td><td>either formalize as a first-class showcase block or keep custom</td></tr><tr><td><code>contact-info</code></td><td>link list / contact meta card</td><td>weak</td><td>P2 content quality</td><td>promote to first-class only if editors need it beyond seeded pages</td></tr><tr><td><code>code</code></td><td>code block pattern</td><td>missing</td><td>P2 content quality</td><td>add first-class renderer/admin support or keep fallback deliberately</td></tr><tr><td><code>list</code></td><td>list primitive</td><td>acceptable</td><td>P1 public marketing/docs</td><td>keep the dedicated line-based editor and preserve legacy fallback-style settings compatibility</td></tr><tr><td><code>table</code></td><td><code>wb-table</code> pattern</td><td>acceptable</td><td>P1 public marketing/docs</td><td>keep the dedicated line-based editor and preserve legacy fallback-style settings rows</td></tr><tr><td><code>accordion</code></td><td>semantic disclosure pattern</td><td>acceptable</td><td>P1 public marketing/docs</td><td>keep the first-class semantic <code><details></code> renderer with child blocks as items</td></tr><tr><td><code>feature-grid</code></td><td>delegated <code>columns.variant = cards</code> pattern</td><td>acceptable</td><td>P1 public marketing/docs</td><td>keep it published as a compatibility alias, but prefer <code>columns</code> for new structured content</td></tr><tr><td><code>stats</code> / <code>metric-card</code></td><td>delegated <code>columns.variant = stats</code> pattern</td><td>weak</td><td>P1 public marketing/docs</td><td>keep aliases honest and add real stat-specific contracts only if they become product-owned</td></tr><tr><td><code>logo-cloud</code></td><td>logo grid / brand strip</td><td>weak</td><td>P1 public marketing/docs</td><td>add structured media handling if it remains productized</td></tr><tr><td><code>testimonial</code></td><td>delegated quote testimonial pattern</td><td>weak</td><td>P2 content quality</td><td>keep alias behavior honest unless a standalone testimonial contract becomes product-owned</td></tr><tr><td><code>timeline</code></td><td>timeline pattern</td><td>weak</td><td>P2 content quality</td><td>promote only if timeline content is a real recurring use case</td></tr><tr><td><code>pricing</code></td><td>pricing card/grid pattern</td><td>weak</td><td>P1 public marketing/docs</td><td>make first-class only with structured plans/features</td></tr><tr><td><code>toc</code></td><td>table-of-contents navigation</td><td>acceptable</td><td>P1 public marketing/docs</td><td>keep same-page TOC collection on explicit anchored <code>header</code> blocks only</td></tr><tr><td><code>breadcrumb</code></td><td>breadcrumb navigation</td><td>missing</td><td>P2 content quality</td><td>defer until the public page shell truly needs it and a shipped pattern is confirmed</td></tr><tr><td><code>cookie-notice</code></td><td>shared privacy banner/modal pattern</td><td>missing</td><td>P3 later/custom</td><td>keep consent UI in the public layout rather than block renderers</td></tr></tbody></table> |
|---|
Page Layout And Slots
Public layout
- The public layout owns the page-wide shell and should keep the base
<body class="wb-public-body">contract. Page Layoutis the editor-facing name for the outer public shell mode.- The stored compatibility field remains
public_shellin this phase, but Page Layouts are now managed install-level records in the admin. - Page Layout may append validated body classes such as
layout-defaultorlayout-docsto that base body class. - Major page regions should be built from shipped WebBlocks UI layout primitives first:
wb-public-main,wb-container,wb-section,wb-stack,wb-grid. wb-content-shell,wb-content-header,wb-content-body, andwb-content-footerbelong inside the main content area when the page reads like article, guide, docs, or editorial content. They are not the site-wide header or footer chrome.#wb-overlay-rootis the single shared mount point for public overlays such as the gallery viewer, public search modal, and cookie preference modal.- Public layouts own that wrapper; blocks, partials, and trusted HTML may contribute overlay children but must not render competing overlay root containers.
- Trusted imported HTML that uses shipped WebBlocks UI trigger hooks must preserve the trigger-to-target contract. If a trigger inside imported main content points at a modal, drawer, popover, or gallery viewer outside
<main>, the extractor must hoist that referenced target into the shared#wb-overlay-rootinstead of dropping it. - When CMS pre-renders a shared dialog layer under
#wb-overlay-root, that layer must stay unhidden so WebBlocks UIv2.7.12can portal trusted modal and gallery targets into it without leaving them inside a hidden ancestor.
Card
cardownsarticle.wb-cardas the renderer root withdata-wb-public-block-type="card".card_headerownsdiv.wb-card-headerwithdata-wb-public-block-type="card-header".card_bodyownsdiv.wb-card-bodywithdata-wb-public-block-type="card-body".card_footerownsdiv.wb-card-footerwithdata-wb-public-block-type="card-footer".- Card regions should render their own roots directly and must not receive extra generic public wrappers.
- The normal Card contract is composable child content through those three regions, not a built-in promo or media renderer.
- Older saved Card rows may still use a minimal fallback renderer only when the Card has no Card region children.
wb-sidebaris reserved for a true docs/app navigation shell. Generic marketing or editorial sidebars should stay ordinaryasidecontent composed fromwb-grid,wb-stack, cards, callouts, and link lists.
Slot wrappers
- Slot wrappers are deterministic runtime behavior, not free-form page-level editorial settings.
- Page layout handle resolves to a managed Page Layout record first when available, and that record's managed Page Layout Slots are then used to resolve slot wrapper element, classes, ids, and trusted structural snippets.
- Edit Page comparison and
Add Missing Layout Slotschange page structure only by creating missing Page Slots; they do not change public wrapper ownership. - Page Layout Slot trusted HTML fields exist for wrapper-adjacent layout structure only. They are not a script surface, and blocks still own the content roots rendered inside the slot wrapper.
- When a default public header slot contains only a Navbar block, CMS promotes the slot wrapper to the
nav.wb-navbarroot so WebBlocks UI sticky behavior is not constrained by a short parent header box. wb-navbar--staticremains the opt-out class for non-sticky public Navbar blocks.defaultmapsheader,main,sidebar, andfooterto semantic wrappers and falls back todivfor unknown slots.docsmapsheaderto the docs navbar wrapper,sidebarto the docs sidebar wrapper, andmainto the docs main wrapper while keepingwb-dashboard-shellpage-owned.- Built-in
defaultanddocslayouts keep fallback managed definitions so rendering remains stable before or without relational slot rows. - Blocks render inside the resolved slot wrapper and do not own page-shell markup.
- Sticky navbar behavior belongs to
.wb-navbar. CMS should not add a second navbar-specific sticky class to the slot wrapper or to the navbar block output. - When a page slot uses
source_type = shared_slot, the Shared Slot contributes only the inner block tree. It must not render its own page shell or slot wrapper. - Shared Slot compatibility is checked before rendering: site scope must match, optional
public_shellmust exactly match the consuming page layout handle, and optionalslot_namemust match the consuming page slot. - Generic public block wrappers must stay non-semantic and must not be used for layout/root-owning blocks.
- The page shell owns the outer shell, slot wrappers own the region wrapper, and root-owning blocks own their own real public root element.
- Root-owning blocks must place
data-wb-public-block-typeon their own renderer root instead of receiving an extra outerwb-public-blockwrapper.
Header slot
- Intended mapping:
headerregion built fromwb-sectionandwb-containerwith shipped nav/list primitives inside. - Use
wb-stackorwb-gridfor internal rhythm, depending on whether the header reads as a stacked banner or a horizontal navigation bar. - Primary or legal navigation may render through
navigation-autoormenu, but the slot should not require block renderers to emit custom header-only HTML. - Current implementation is weak because the header slot owns several custom
wb-public-*chrome classes. Phase 2A keeps the existingwb-sectionandwb-containershell intact and defers any safe reduction of custom header chrome to a later pass.
Main slot
- Intended mapping:
<main class="wb-public-main">is the primary content region. - Default shell:
wb-public-main > .wb-container > .wb-stackfor ordinary block stacks. - Editorial/docs shell:
wb-public-main > .wb-container > .wb-content-shellwith optionalwb-content-header,wb-content-body, andwb-content-footersections. - Nested block layout should use
wb-stack,wb-stack-,wb-gap-,wb-grid, andwb-grid-*before any custom wrapper class. - The current implementation is acceptable because it already gives the main slot a stable public wrapper and block rhythm. Phase 2B adds explicit layout modes so
wb-content-shellis reserved rather than forced onto every page.
Sidebar slot
- Intended mapping:
asideadjacent to main content viawb-gridor another shipped layout primitive. - Default content should be a
wb-stackof supporting blocks, optionally grouped in awb-cardorwb-calloutif that matches the block content. wb-sidebarshould only be used when the page is explicitly rendering a docs/app navigation shell, not for generic supporting marketing content.- Current implementation is weak because it still uses a custom
wb-public-sidebarshell. Phase 2A removes the forced outerwb-cardwrapper so inner blocks control whether they render as cards or callouts.
Footer slot
- Intended mapping:
footerregion built fromwb-section,wb-container, andwb-gridorwb-stack. - Navigation lists should use simple nav/list primitives or
wb-link-listwhen the shipped UI pattern fits. - Supporting content blocks may render normally inside the footer; they should not need footer-specific block renderers.
- Current implementation is acceptable because it stays close to shipped layout primitives, with only minor CMS-specific footer chrome around the cookie settings control.
Core Content Blocks
`header`
- CMS block slug:
header - Admin fields:
text,level,anchor - Translatable fields:
titletext - Shared fields:
variant/level, alignment setting, anchor - Intended WebBlocks UI output: semantic
<h1>-<h6>based onlevel; optionalidfrom the shared anchor; no invented wrapper beyond the heading element. - Current implementation: acceptable
- TOC contract: explicit shared anchors are the only supported TOC source, and the public TOC collects anchored
headerblocks from the same rendered page tree, including nested layout descendants. - Notes for later renderer/admin improvements: keep anchor behavior explicit, preserve wrapper-free output, and keep heading semantics owned by
Headerrather than a parallel legacy block.
`text`
- CMS block slug:
text - Admin fields:
content - Translatable fields:
content - Shared fields: none
- Intended WebBlocks UI output: ordinary paragraph/body copy; use a shipped
wb-stackrhythm only when needed for multiple text nodes. - Current implementation: acceptable
- Import/sync mapping note: use
text/plain_textonly for simple plain body copy with no safe inline formatting markup. - Notes for later renderer/admin improvements: keep this block simple and avoid turning it into a pseudo-rich-text block.
`rich-text`
- CMS block slug:
rich-text - Admin fields:
content - Translatable fields:
content - Shared fields: none
- Intended WebBlocks UI output: sanitized body copy wrapped in
wb-rich-text wb-rich-text-readableusing the shipped WebBlocks UI rich text primitive. - Current implementation: acceptable
- Storage model: Rich Text stores a restricted safe HTML fragment, not Markdown markers. Allowed tags are
p,strong,em,code,a[href],ul,ol,li, andbrwhen needed. Classes, styles, event attributes, headings, media, tables, buttons, and unsupported HTML are stripped during sanitization. - Admin behavior: the admin editor is a dependency-free
contenteditablesurface synchronized to a hidden form field. It is intentionally limited to body-copy formatting and does not replace Header, Button, Media, Table, Layout, HTML, or other dedicated block types. - Import/sync mapping note: when imported body copy contains safe inline formatting, multiple paragraphs, or simple lists, it should become
rich-textrather thantext. - Notes for later renderer/admin improvements: keep Rich Text limited to safe editorial body copy.
wb-rich-textremains the public typography primitive. Headings, media, buttons, tables, layout, and raw HTML composition remain separate blocks or features.
`html`
- CMS block slug:
html - Admin fields:
content - Translatable fields:
content - Shared fields: none
- Intended WebBlocks UI output: raw trusted HTML inside the normal public block wrapper.
- Current implementation: acceptable
- Notes for later renderer/admin improvements: keep this block for trusted editorial/admin usage only, document the XSS risk clearly, and do not make ordinary editors rely on pasted WebBlocks HTML for normal content. When trusted HTML includes shipped WebBlocks UI overlay markup, the public page shell should hoist that inner overlay content into the shared page-owned
#wb-overlay-rootinstead of rendering duplicate overlay roots inline.
`section`
- CMS block slug:
section - Admin fields:
title,variant,content - Translatable fields:
title,content - Shared fields:
variant - Intended WebBlocks UI output: default wrapper uses
wb-section; explicitpromovariants may map towb-promowhen the shipped pattern fits; CTA actions should come from child blocks, not raw HTML. - Current implementation: acceptable
- Wrapper rule: the
Sectionblock owns the real<section class="wb-section ...">root and carriesdata-wb-public-block-type="section"on that element. Generic public block wrappers must not wrap it. - Notes for later renderer/admin improvements: keep default sections stable, treat
promoas an explicit marketing variant, and keep child buttons/button groups structured. - Promo CTA behavior: child
buttonblocks render inwb-promo-actions; non-button children continue rendering outside the CTA row.
Layout wrapper rule
header,section,container,grid,cluster,card, andcontent_headerare root-owning layout/content-shell blocks and should not receive a generic public wrapper from the public block loop.Sectionowns the semantic<section class="wb-section">root when needed.Container,Grid, andClusterown their own non-semantic layout roots unless a specific renderer intentionally chooses otherwise.Cardowns its<article class="wb-card">root.Card Header,Card Body, andCard Footerown their matching WebBlocks UI region roots and together define the normal Card public structure.Headerowns its semantic heading root such as<h1>or<h2>.Content Headerowns its semantic<header class="wb-content-header">root and always renders its title as<h1 class="wb-content-title">.- Layout + Card Phase 3 standardization keeps these root-owning blocks aligned across the renderer partials,
Block::ownsPublicRoot(), the contract registry, the read-only admin contract modal, and the contracts audit command.
`hero`
- CMS block slug:
hero - Admin fields:
subtitle,title,content,variant - Translatable fields:
subtitleas eyebrow,titleas headline,contentas supporting copy - Shared fields:
variant, child block structure - Intended WebBlocks UI output:
wb-promo > .wb-promo-copywith optionalwb-eyebrow,wb-promo-title,wb-promo-text, andwb-promo-actions - Current implementation: acceptable
- Notes for later renderer/admin improvements: hero CTA actions should come from child
buttonblocks, raw HTML should not be used for normal hero content, and legacy imported hero settings should only act as a fallback when canonical translated fields are empty. - Hero CTA behavior: child
buttonblocks render inwb-promo-actions; non-button children are ignored in the CTA row. - Wrapper rule:
heronow owns its renderer root and placesdata-wb-public-block-type="hero"on that root, so the slot loop must not add a generic outer public wrapper.
`columns`
- CMS block slug:
columns - Admin fields:
title,subtitle,content,variant, repeatablecolumn_items - Translatable fields:
title,subtitle,content, childcolumn_itemtext - Shared fields:
variant, child ordering, child links, structure - Intended WebBlocks UI output: layout container for repeatable children using
wb-grid,wb-grid-2,wb-grid-3,wb-grid-4, or a genericwb-gridwhen the count is dynamic. - Current implementation: acceptable
- Variant mapping:
cards-> grid container with each child rendered aswb-card > .wb-card-bodyplain-> grid container with unframed stacked child contentstats-> grid container with child items rendered aswb-statlinks->wb-link-listwith child items rendered as link-list rows- Notes for later renderer/admin improvements: keep the parent block responsible for presentation choice so existing
column_itemcontent can stay simple and reusable.
`column_item`
- CMS block slug:
column_item - Admin fields:
title,url,content - Translatable fields:
title,content - Shared fields:
url - Intended WebBlocks UI output: one grid cell that may render as plain content,
wb-card,wb-stat,wb-link-list-item, or other shipped cell treatment depending on parent or item variant. - Current implementation: acceptable
- Notes for later renderer/admin improvements:
column_itemremains a simple content unit and now defers public presentation to its parentcolumnsblock. The currentstatsmapping is intentionally conservative because there is no dedicated numeric value field yet.
`cta`
- CMS block slug:
cta - Admin fields:
subtitle,title,content, managed primary CTA, managed secondary CTA,variant - Translatable fields:
subtitleas eyebrow,titleas headline,contentas supporting copy, plus childbuttonlabels - Shared fields:
variant, child CTA URLs, child ordering - Intended WebBlocks UI output: promo-style
wb-card wb-promosection withwb-promo-copyandwb-promo-actions - Current implementation: acceptable
- Notes for later renderer/admin improvements: keep CTA labels locale-owned on child buttons, keep CTA URLs shared, and avoid reintroducing settings-driven CTA payloads.
- Wrapper rule:
ctanow owns its renderer root and placesdata-wb-public-block-type="cta"on that root, so the slot loop must not add a generic outer public wrapper.
`feature-grid`
- CMS block slug:
feature-grid - Admin fields:
title,subtitle,content, repeatablefeature_items - Translatable fields:
title,subtitle,content, childfeature-itemtext - Shared fields: child ordering, child links, structure
- Intended WebBlocks UI output: the same public card-grid structure used by
columns.variant = cards - Current implementation: acceptable as a compatibility alias
- Notes for later renderer/admin improvements: keep Feature Grid source-backed and documented, but be explicit that it currently delegates to the shared Columns cards renderer instead of owning distinct feature-grid markup.
`feature-item`
- CMS block slug:
feature-item - Admin fields:
title,url,content - Translatable fields:
title,content - Shared fields:
url - Intended WebBlocks UI output: the same card-style item shell used by
column_itemin the Columns cards presentation - Current implementation: acceptable as a compatibility alias
- Notes for later renderer/admin improvements: keep Feature Item documented as a source-backed supporting child contract while it still delegates to the shared Column Item cards presentation.
`callout`
- CMS block slug:
callout - Admin fields:
title,variant,content - Translatable fields:
title,content - Shared fields:
variant - Intended WebBlocks UI output: notice/alert behavior maps to
wb-alert-*; sidebar/help variants may map towb-calloutif that primitive exists in WebBlocks UI. - Current implementation: acceptable
- Notes for later renderer/admin improvements: keep tone mapping explicit and only introduce alternate callout shells when the shipped UI has them.
`quote`
- CMS block slug:
quote - Admin fields:
content,title,subtitle - Translatable fields:
content,title,subtitle - Shared fields: none
- Intended WebBlocks UI output: semantic
blockquote; use shipped card/callout framing only when the quote is intentionally presented as a framed testimonial/card. - Current implementation: acceptable
- Notes for later renderer/admin improvements: allow an unframed semantic quote variant and use
testimonialonly if that becomes a separate first-class pattern.
`faq`
- CMS block slug:
faq - Admin fields:
title,content - Translatable fields:
title,content - Shared fields: none
- Intended WebBlocks UI output: simple question/answer content in a stable
wb-cardandwb-stackshell. - Current implementation: acceptable
- Notes for later renderer/admin improvements: FAQ remains simple and backward-compatible. When used as a child of an accordion-family block, its
titleandcontentact as the disclosure summary/body.
`accordion`
- CMS block slug:
accordion - Admin fields:
title,content - Translatable fields:
title,content - Shared fields: child block structure and ordering
- Intended WebBlocks UI output: semantic grouped disclosure using
<details>and<summary>with no custom accordion JS or invented classes. - Current implementation: acceptable
- Notes for later renderer/admin improvements: child blocks supply the disclosure items. Blocks without usable
titleandcontentare skipped rather than rendered as empty wrappers.
`faq-list`
- CMS block slug:
faq-list - Admin fields: same editorial expectations as
accordion - Translatable fields: inherited from the parent block and child items
- Shared fields: child block structure and ordering
- Intended WebBlocks UI output: same as
accordion; this slug is now a transitional alias rather than a separate long-term pattern. - Current implementation: acceptable
- Notes for later renderer/admin improvements: keep old content working, but steer future grouped disclosure usage toward
accordion.
`tabs`
- CMS block slug:
tabs - Admin fields:
title,subtitle,content - Translatable fields:
title,subtitle,content - Shared fields: none
- Intended WebBlocks UI output: a true interactive tabset if tabs remain a first-class block.
- Current implementation: weak
- Notes for later renderer/admin improvements: tabs are explicitly deferred until WebBlocks UI ships a real tabs pattern. The current simple card treatment remains only as a compatibility fallback and is not promoted.
`button`
- CMS block slug:
button - Admin fields:
title,url,subtitle,variant - Translatable fields:
title - Shared fields:
url,subtitle/target,variant, optional attachment media relation - Intended WebBlocks UI output: explicit
wb-btnvariant mapping forprimary,secondary,outline,ghost, anddanger; unknown values must fall back towb-btn wb-btn-primary. - Current implementation: acceptable
- Variant mapping:
primary->wb-btn wb-btn-primarysecondary->wb-btn wb-btn-secondaryoutline->wb-btn wb-btn-outlineghost->wb-btn wb-btn-ghostdanger->wb-btn wb-btn-danger- unknown or empty ->
wb-btn wb-btn-primary - Notes for later renderer/admin improvements: keep attachment/download compatibility isolated, render
<button type="button">only when there is no URL, and formalize a first-class button-group block only when editors need it beyond child button rows.
CTA Rows
- Hero and promo-style section blocks should model CTAs with child
buttonblocks. - Promo CTA rows render in
wb-promo-actions. - Outside promo contexts, ordinary action rows can use shipped cluster utilities such as
wb-cluster wb-cluster-2. - A first-class
button-groupblock is deferred for now; the current child-button model already supports structured CTA rows without adding new block architecture.
`image`
- CMS block slug:
image - Admin fields:
media_id,subtitle,url,title - Translatable fields:
titleas caption,subtitleas alt text - Shared fields:
media_id,url - Intended WebBlocks UI output: semantic
figure,img, and optionalfigcaption; use shipped media/card classes only when the image is intentionally framed. - Current implementation: acceptable
- Notes for later renderer/admin improvements: Phase 3 now keeps caption and alt text on the image-translation path, preserves the optional shared link URL, and keeps wrapper-free semantic output when media exists.
`gallery`
- CMS block slug:
gallery - Admin fields: compact
Gallery Itemslist rows, shared Gallery presentation settings, and per-item metadata modals - Translatable fields: per-gallery-item
alt_text,caption,overlay_title, andoverlay_textviablock_gallery_item_translations - Shared fields: ordered gallery media selection and order, columns, gap, variant, aspect ratio, captions mode, overlay mode, and lightbox toggle
- Intended WebBlocks UI output: WebBlocks gallery pattern with the viewer mounted under
#wb-overlay-root; shipped WebBlocks UI gallery hooks should drive interaction first. - Current implementation: acceptable
- Public rendering contract: Gallery no longer emits a public intro heading or paragraph. Existing legacy stored Gallery title/description values may remain in older records but are ignored by the public renderer. Fixed-aspect Gallery media preserves the full image with centered contain fitting so screenshot-style images are not cropped inside fixed tiles.
- Editorial contract: when section headings or explanatory copy are needed, use
Content HeaderplusPlain TextorRich Textbefore the Gallery block. - Notes for later renderer/admin improvements: the current renderer writes canonical ordered gallery media through
block_media, resolves per-item locale-owned copy throughblock_gallery_item_translations, preserves legacy fallback items for older saved content, and keepsdata-wb-gallery-targetpaired with one shared viewer modal under#wb-overlay-root.
`download`
- CMS block slug:
download - Admin fields:
title,subtitle,media_id,variant - Translatable fields:
title,subtitle - Shared fields:
media_id,variant - Intended WebBlocks UI output: file CTA as
wb-btn, or a compactwb-cardpluswb-btnwhen the variant needs more context. - Current implementation: acceptable
- Notes for later renderer/admin improvements: Phase 3 now keeps visible label and helper text on text translations, preserves shared media and button variant ownership, and suppresses broken CTA output when no media source exists.
`contact_form`
- CMS block slug:
contact_form - Admin fields:
heading,intro_text,submit_label,success_message,recipient_email,send_email_notification,store_submissions - Translatable fields:
heading,intro_text,submit_label,success_message - Shared fields:
recipient_email,send_email_notification,store_submissions - Intended behavior: public submission stores the message first, then attempts synchronous email notification to the resolved recipient; admin views should show compact notification state and safe failure detail when delivery fails.
- Public submit endpoint: native browser form
POST /contact-messageswith CSRF, CMS-owned hidden generated anti-spam check field, requiredname,email, andmessagevalidation, optionalsubject, and generic success behavior for filled check-field submissions. - Notes: block recipient override wins first, then the current public site's
contact_recipient_email, thenCONTACT_RECIPIENT_EMAIL, thenMAIL_FROM_ADDRESSas a last safe local fallback when no explicit contact recipient is configured.
`video`
- CMS block slug:
video - Admin fields:
title,content,url,media_id - Translatable fields:
title,content - Shared fields:
url,media_id - Intended WebBlocks UI output: semantic
<video controls>for direct sources, or a safe provider<iframe>only for known YouTube/Vimeo URLs, inside a simplewb-cardshell. - Current implementation: acceptable
- Notes for later renderer/admin improvements: Phase 3 now gives Video a dedicated admin form and save path, keeps visible copy translated, preserves hosted-media-first rendering, and falls back to a simple external link for safe unknown URLs instead of unsafe embedding.
`audio`
- CMS block slug:
audio - Admin fields:
title,content,url,media_id - Translatable fields:
title,content - Shared fields:
url,media_id - Intended WebBlocks UI output: semantic
<audio controls>inside a simplewb-cardshell. - Current implementation: acceptable
- Notes for later renderer/admin improvements: Phase 3 now gives Audio a dedicated admin form and save path, keeps visible copy translated, and suppresses empty controls when no usable source exists.
`file`
- CMS block slug:
file - Admin fields:
title,content,url,media_id - Translatable fields:
title,content - Shared fields:
url,media_id - Intended WebBlocks UI output: compact file card with a
wb-btn wb-btn-secondarydownload/open action. - Current implementation: acceptable
- Notes for later renderer/admin improvements: Phase 3 now gives File a dedicated admin form and save path, keeps visible copy translated, and keeps the shared media-or-URL source contract explicit without rendering empty invalid anchors.
`map`
- CMS block slug:
map - Admin fields:
title,content,url - Translatable fields: none
- Shared fields:
title,content,url - Intended WebBlocks UI output: simple location summary with an external
Open mapbutton, not a custom map widget. - Current implementation: acceptable
- Notes for later renderer/admin improvements: keep the implementation link-first unless a real shipped map/embed pattern becomes available.
`slider`
- CMS block slug:
slider - Admin fields: ordered gallery assets and optional text
- Translatable fields: none
- Shared fields: assets and structure
- Intended WebBlocks UI output: no promoted Phase 4 renderer; use
galleryor other structured media blocks instead when possible. - Current implementation: weak
- Notes for later renderer/admin improvements: slider remains deferred because there is no confirmed shipped WebBlocks UI carousel pattern worth standardizing on.
`code`
- CMS block slug:
code - Admin fields:
title,content - Translatable fields:
title,content - Shared fields: optional
settings.language - Intended WebBlocks UI output: escaped source inside semantic
<pre><code>with no injected HTML and no syntax-highlighting dependency. - Current implementation: acceptable
- Import/sync mapping note: raw or multi-line snippets such as
<pre><code>examples and package include blocks should becomecode, notrich-text. - Notes for later renderer/admin improvements: keep code rendering safe and dependency-free. Optional language metadata may be exposed from settings, but a full code editor or syntax-highlighting stack is intentionally out of scope for Phase 3.
`toc`
- CMS block slug:
toc - Admin fields:
title - Translatable fields:
title - Shared fields: none
- Intended WebBlocks UI output:
wb-link-listbuilt from existing anchoredheaderblocks on the same page. - Current implementation: acceptable
- Notes for later renderer/admin improvements: the Phase 3 implementation stays intentionally minimal. It only renders when
Headerblocks already expose explicit anchor IDs and does not attempt complex heading parsing or auto-generated anchors.
`navigation-auto`
- CMS block slug:
navigation-auto - Admin fields:
navigation_menu_key - Translatable fields: none
- Shared fields:
settings.menu_key - Intended WebBlocks UI output: site navigation list using simple nav/list primitives or
wb-link-listwhen appropriate; do not fake a docs sidebar unless the page shell is truly a docs shell. - Current implementation: acceptable
- Notes for later renderer/admin improvements: keep the block focused on rendering navigation trees and let the slot/page shell decide whether the output is header nav, footer nav, or docs nav.
`menu`
- CMS block slug:
menu - Admin fields:
navigation_menu_key - Translatable fields: none
- Shared fields:
settings.menu_key - Intended WebBlocks UI output: same as
navigation-auto; this remains a legacy alias for migrated data. - Current implementation: acceptable
- Notes for later renderer/admin improvements: keep support for old content, but prefer
navigation-autoin admin UX and future seeds.
Legacy / Transitional Phase 3 Note
tabs,slider,menu, andfaq-listare still legacy draft-era slugs in the CMS catalog rather than published core contracts.showcase-listandcontact-infostill exist only as public-render compatibility blocks and are not promoted into the published core catalog in this phase.- This phase keeps those paths documented honestly, preserves compatibility rendering where it already ships, and adds safe link sanitization for settings-driven public links without introducing a new tabs or slider JavaScript system.
`contact_form`
- CMS block slug:
contact_form - Admin fields:
heading,intro_text,submit_label,success_message,recipient_email,send_email_notification,store_submissions - Translatable fields:
title/heading,content/intro,submit_label,success_message - Shared fields:
recipient_email,send_email_notification,store_submissions - Intended WebBlocks UI output: form fields use WebBlocks UI form primitives; submit action uses
wb-btn; transient success feedback renders once as a WebBlocks UI toast under the shared public#wb-overlay-root, while validation errors and user-correctable failures stay inline near the form withwb-alert. - Current implementation: acceptable
- Notes for later renderer/admin improvements: preserve structured form fields, keep operational settings out of editorial copy, keep site-level default recipients on the site record instead of arbitrary block JSON, and avoid making editors paste raw form markup or use
mailto:as a submission substitute.
Public-Only Or Weak Blocks
| <table><thead><tr><th>CMS Block</th><th>Current state</th><th>Desired direction</th><th>Notes</th></tr></thead><tbody><tr><td><code>card-grid</code></td><td>public-render-only</td><td>should stay transitional</td><td>The renderer now matches the same <code>wb-grid</code> and <code>wb-card</code> structure as <code>columns.variant = cards</code>, but it still depends on <code>settings.items</code>. Prefer Columns for new structured content.</td></tr><tr><td><code>showcase-list</code></td><td>public-render-only</td><td>should stay fallback/custom</td><td>This is currently showcase-specific seeded content and should not become core unless the pattern repeats across sites. Public image triggers should still follow the shipped gallery contract by emitting <code>data-wb-gallery-target</code> and registering one shared viewer modal under <code>#wb-overlay-root</code>.</td></tr><tr><td><code>contact-info</code></td><td>public-render-only</td><td>should become first-class</td><td>If editors keep using contact metadata cards, a small structured block is better than settings-driven custom content. Current compatibility rendering now ignores unsafe settings URLs instead of emitting invalid or dangerous anchors.</td></tr><tr><td><code>code</code></td><td>first-class public renderer</td><td>acceptable</td><td>Safe <code><pre><code></code> rendering is now in place; richer editor affordances remain optional future work.</td></tr><tr><td><code>list</code></td><td>first-class public renderer</td><td>acceptable</td><td>Dedicated line-based list rendering now exists; keep compatibility for legacy settings-driven content.</td></tr><tr><td><code>table</code></td><td>first-class public renderer</td><td>acceptable</td><td>Dedicated line-based table rendering now exists; keep compatibility for legacy settings rows.</td></tr><tr><td><code>accordion</code></td><td>first-class public renderer</td><td>acceptable</td><td>Grouped disclosure now uses semantic <code><details></code> and child blocks instead of fallback settings markup.</td></tr><tr><td><code>feature-grid</code></td><td>published compatibility alias</td><td>should stay transitional</td><td>The public renderer delegates to <code>columns.variant = cards</code>; prefer Columns for new content, but keep Feature Grid documented because it is source-backed and shipped.</td></tr><tr><td><code>stats</code></td><td>alias-only</td><td>should merge into Columns</td><td>The public renderer delegates to the existing <code>columns.variant = stats</code> path and should stay documented as alias behavior rather than a standalone published contract.</td></tr><tr><td><code>metric-card</code></td><td>first-class alias</td><td>should merge into stat primitives</td><td>The public renderer now uses the same <code>wb-stat</code> direction as the Columns stats variant.</td></tr><tr><td><code>logo-cloud</code></td><td>fallback-only</td><td>should become first-class</td><td>Only promote if there is a repeatable need for structured logo/media rows.</td></tr><tr><td><code>testimonial</code></td><td>alias-only</td><td>should merge into Quote</td><td>The public renderer delegates to the quote testimonial variant and should stay documented as alias behavior rather than a standalone published contract.</td></tr><tr><td><code>timeline</code></td><td>fallback-only</td><td>should become first-class</td><td>Promote only with structured milestones and a clear shipped UI pattern.</td></tr><tr><td><code>pricing</code></td><td>fallback-only</td><td>should become first-class</td><td>A pricing block needs structured plan, feature, and CTA fields to be worth promoting.</td></tr><tr><td><code>toc</code></td><td>first-class public renderer</td><td>acceptable</td><td>Minimal TOC rendering now uses existing <code>Header</code> anchors and <code>wb-link-list</code>; active-section behavior is still deferred.</td></tr><tr><td><code>breadcrumb</code></td><td>fallback-only</td><td>should become first-class</td><td>Only add when the public shell truly requires breadcrumb navigation.</td></tr><tr><td><code>cookie-notice</code></td><td>fallback-only</td><td>should stay fallback/custom</td><td>Public consent UI already lives in the layout shell, so this block should not compete with the shared privacy pattern.</td></tr></tbody></table> |
|---|
Renderer Rules
- Blade renderers must prefer shipped
wb-*classes. - No new one-off public CSS classes unless documented as a WebBlocks UI gap.
- No inline scripts in block renderers.
- Interactive blocks must use shipped WebBlocks UI data hooks first.
- CMS-specific JS belongs in
public/cms/jsonly when necessary. - Overlay, dialog, and modal content must use
#wb-overlay-root. - Admin fields must not require editors to paste raw WebBlocks UI HTML for normal blocks.
- Blocks should expose structured fields and variants, not raw HTML, whenever possible.
README.mdmust be updated after meaningful renderer or admin behavior changes.
Phase Plan
Phase 1
- Documentation only.
- Establish renderer contract.
- No renderer rewrite yet.
Phase 2
- Align public layout and slot wrappers.
- Ensure main content can render with
wb-content-shellwhere appropriate. - Add tests for shell and slot class output.
Phase 3
- Make card-grid, button-group, and any future standalone stats or testimonial patterns first-class only when they become real product-owned contracts rather than aliases.
- Add admin forms and translation registry support where needed.
Phase 3 completed: core public blocks now align with WebBlocks UI primitives.
Phase 4
- Migrate existing seeded/demo docs content to first-class blocks.
- Remove reliance on raw HTML or
settings.itemswhere a structured block exists.