The open-source marketing CMS
Menu

Component library: specification

Status: approved, 2026-11-10 (Kevin); phase 1 is planned after the site search and site agents work. A first version is described here, with how it fits the code that exists; the open questions at the end are still open.

The problem

Teams that care about their brand keep a component library: every component, every variant, what it looks like in the brand, how to use it. Most use Storybook for this, outside the CMS, maintained by developers. Editors never see it, and it drifts from what the CMS actually renders.

In Spillway the components are blocks: about 30 in core (Hero, Cards, Columns, Section, Buttons, Gallery, Form, Collection and the rest), plus the blocks modules add. Some of this is already in the admin:

  • Brand → Styles draws each styled block (Hero, Cards, Button, Call to action, Heading, Text and module blocks with styles) in every named style, in the draft's colors.
  • Brand → Style guide (and /brand once published) shows the tokens, the contrast grid, the voice and every block in every style, draft next to published.
  • Shared blocks are synced content reused across pages.
  • Every block has a summary, a description and a hint for the AI (packages/blocks/src/meta.ts).

What is missing:

  1. One place to see every block, not only the styled ones: each with its variants and states, on a phone and on a desktop, in light, dark and high contrast, with its fields and how to use it, and where it is used on the site. That is the Storybook view, built from what the site really renders.
  2. Choosing which blocks editors get. Every block is offered to every editor on every page. Teams want a short, approved set, like Builder.io and Storyblok give developers control over what marketers can use.
  3. Ready-made sections to start from. WordPress has the pattern directory and theme patterns, and section styles since 6.6. Drupal's new editor, Canvas, is built on Single Directory Components with Storybook alongside. Spillway has no patterns: every page starts from single blocks.

Goals and non-goals

Goals:

  1. A Components screen that shows every block the site has, rendered by the real renderer in the site's published or draft brand, with examples, controls, viewports, color modes, documentation and usage.
  2. A per-site block set: which blocks the page builder offers, and optionally which ones each content type allows.
  3. Patterns: sections made of blocks, with placeholder content and styles, that editors insert and then edit. A site keeps its own patterns, and component packs from the registry add more.
  4. A link with the outside: share the catalog as a page for designers, and let developers bring components from their own design system as module blocks.

Not in the first version: editing a block's code in the browser (Drupal Canvas's code components), a visual CSS editor beyond the Brand area, and running Storybook itself inside Spillway.

Terms

  • Block: a component editors place in the page builder. Unchanged.
  • Example: a named set of props for a block that shows one use of it ("Hero with image", "Hero, centered, no buttons"). Blocks declare examples; a site can add its own.
  • Block set: the blocks the page builder offers on a site.
  • Pattern: a saved arrangement of one or more blocks with content and styles, inserted as a copy. A pattern is not synced: editing a page never changes the pattern, and the other way round. Synced content stays a shared block.
  • Component pack: a registry package of patterns and block styles, and optionally a module that adds blocks.

What people see

Components screen

Structure → Components, between Content types and Menus, for managers and admins (site.settings); authors and editors can open it read-only (content.create) to look up how to use a block.

The left side lists every block grouped by the page builder's categories (Layout, Content, Marketing, Docs, Registry, Advanced, Modules), with a search box and a filter for In the block set / Hidden. Each row shows the block's name, its summary and a dot when it is hidden from editors.

Selecting a block opens it on the right with four tabs:

  1. Examples. Each example is drawn by the server renderer inside a frame that carries the site's CSS, so it looks exactly as on the public site. Above the frames:
    • Brand: Published, Draft, or Side by side, the same choice as the style guide;
    • Width: Phone (390), Tablet (768), Desktop (1280) or the frame's own width;
    • Mode: Light, Dark, High contrast (when the brand defines them);
    • Style: every named style of the block, or All styles in a grid.

    Controls under an example shows the block's fields as the page builder does (the same Puck field definitions) and redraws the frame as you change them, like Storybook's controls. Save as example keeps the current props as a site example; Copy as pattern turns it into a pattern.

  2. Docs. The block's description, a table of its fields (name, type, default, help), its styles and slots, the accessibility notes (heading level options, alt text, keyboard behavior) and the AI hint, all generated from the block's definition. Module blocks show their module and version.
  3. Checks. An automated accessibility run (axe, in the admin's browser) and the contrast of each style against the brand, per example and mode, with pass or fail.
  4. Usage. The pages and patterns that use the block, with counts per style, read from the site's page layouts. A block that nothing uses says so; hiding it is then safe.

The header has Show to editors / Hide from editors for the block and, for module blocks, a link to the module.

The block set

Components → Block set (or the switch on each block) chooses which blocks the page builder offers on this site:

  • All blocks are offered until someone changes it.
  • A hidden block is no longer in the blocks panel or the AI's list of blocks, but pages that already use it keep rendering it, and editors can still edit those instances. The pre-publish checks say "uses a hidden block" as a notice, not an error.
  • Favorites pins up to eight blocks at the top of the blocks panel.
  • Per content type: a layout field can list the blocks it allows (Content types → field → Allowed blocks: all of the block set, or chosen ones). A landing page type can offer everything while a news type offers only Heading, Text, Image, Gallery and Embed.

Patterns

The page builder's blocks panel gains a Patterns tab next to Blocks. Each pattern shows a thumbnail drawn in the site's brand, its name and its category (Hero sections, Features, Calls to action, Testimonials, FAQs, Pricing, Team, Contact, Footers). Dragging or clicking a pattern inserts a copy of its blocks with fresh ids, which the editor then edits like any blocks.

Patterns come from three places:

  • Saved on the site. Select one or more blocks in the page builder, then Save as pattern (name, category, description). Managers can share it with everyone or keep it to themselves.
  • Component packs installed from the registry (below).
  • Spillway's own packs, shipped in core so a new site has good sections from the first day.

Components → Patterns lists them with the same Brand, Width and Mode controls as the examples, plus Edit (opens the pattern in the page builder), Duplicate, Hide and Delete. A pattern that uses a hidden block is marked and cannot be inserted.

A pattern stores block styles by key. When the site has no style with that key, the block gets the default style, and the Components screen says which styles are missing.

Component packs from the registry

A new registry kind, components, joins modules, themes and kits. A pack holds:

  • patterns (JSON, the same shape as a page layout, with placeholder text and images shipped in the pack),
  • block styles (the same shape as Brand → Styles, built from tokens only, so they follow any brand),
  • optionally requires.modules: a module that adds blocks the patterns use.

Installing a pack (Kits → Install from registry, filtered to Components, or Components → Add components) shows what it brings: thumbnails of each pattern in this site's brand, the styles, the module it requires and that module's capability sheet. Patterns land in the site's library; styles land in the brand draft for review, as a theme's brand does today, never on the live brand. Uninstalling removes the pack's patterns from the library but not from pages, which hold copies.

Spillway publishes the first packs: Marketing sections (hero variants, feature grids, logo strip, stats, testimonials, FAQ, pricing, final call to action), Higher education (program finder hero, event list, faculty spotlight, apply call to action), Nonprofit (donate call to action, impact numbers, stories) and City (service finder, alerts, department contacts). The sector kits can then require their pack instead of carrying sections in sample pages.

Bringing your own components

Teams with a design system in code bring it in as module blocks, which exist today: a trusted module registers React blocks, and a sandboxed module returns escaped HTML. Two additions make that easier:

  • Examples in the manifest. A module block can declare examples, so it appears on the Components screen like a core block.
  • From Storybook. spillway-module create <name> --tier trusted --from-storybook <url> (the SDK's existing scaffolding command, with a new flag) reads a Storybook's index.json and each component's args, and scaffolds one block per component with its fields and examples, for a developer to finish. It is a starting point, not a live sync.

Design tokens already come in through Brand → Import brand (Figma variables, W3C design tokens, Tailwind, CSS custom properties).

Sharing the catalog

Publish the component library at /components (off by default, like /brand) renders the Components screen's examples as a public, read-only page in the published brand, for designers and agencies who do not have an admin account. spillway components export writes the same as static HTML.

How it works

  • Rendering. Examples and pattern thumbnails are rendered by the page renderer (@puckeditor/core/rsc on the server) into an HTML document with the site's theme CSS, the draft's when asked (the brand preview already does this), and shown in a sandboxed frame. Thumbnails are images made by the server's headless browser when it has one, otherwise the scaled live frame.
  • Examples. BlockMeta (packages/blocks/src/meta.ts) gains examples: { key, label, props }[]; core blocks ship two to four each. Module manifests gain the same per block. Site examples are stored per site.
  • Block set. A builder object in the site settings: { hidden: string[], favorites: string[] }. GET /api/builder/config (which already sends module blocks, styles and features) filters the blocks and orders the favorites, and the AI's block list (loadAiBlocks) uses the same filter. A layout field gains allowedBlocks?: string[].
  • Patterns. One migration adds patterns (id, site_id, key, label, category, description, data (Puck JSON), thumbnail_media_id, source (site or the pack's name and version), visibility, created_by, timestamps). Insertion copies the data and gives every block a new id.
  • Packs. The registry learns "kind": "components" in a kit manifest; the existing kit install path handles patterns and styles the way it handles a theme's brand, and requires.modules the way kits already require modules.
  • API. GET /api/components (blocks with meta, examples, styles, usage counts), GET /api/components/:block/render?example=&style=&brand=draft (HTML for the frame), GET/POST /api/patterns, PUT/DELETE /api/patterns/:id, PUT /api/site with builder. Validated with zod, permission-checked and audited like every route. MCP gains list_components and list_patterns, and draft_page_from_brief may use patterns.
  • Usage. Counts come from the published and draft layouts of the site's entities, computed on demand and cached with the entities tag.

Site agents

The site agent uses all of this. Asked for "a pricing section like the one on the home page", it finds the pattern; asked to "keep news posts simple", it proposes a block set for the news type; a weekly check task can report blocks that fail contrast in the current brand.

Phases

Phase 1: see everything. The Components screen with examples for every core block, Brand, Width and Mode controls, field controls, the Docs and Usage tabs, and the block set with favorites. Tests: every core block renders every example without errors in every style; hidden blocks leave existing pages unchanged; the AI's block list follows the block set.

Phase 2: patterns. Site patterns, Save as pattern, the Patterns tab in the page builder, per-content-type allowed blocks, the components registry kind and Spillway's four packs, with the sector kits requiring them.

Phase 3: share and connect. The Checks tab, /components and the static export, module examples in manifests, and --from-storybook.

Open questions

  1. Who chooses the block set? Managers (site.settings) as proposed, or a new permission so a design lead can own it without other settings?
  2. Should packs be free and first-party only at first, or open to every publisher like themes?
  3. Patterns per role. Is "shared with everyone or kept to myself" enough, or do teams need patterns for named roles, like shared views?
  4. Thumbnails. Require a headless browser in the image for real screenshots, or accept scaled live frames everywhere?

Spillway 0.1.0 · Edit this page on GitHub

Was this page helpful?