SpectreSkills
← All skills

cms-section-catalog

v1.0.0

Declare every first-party CMS section as four artifacts — a manifest, a React component, a registry entry, and a preset — with settings, blocks, CTAs, and spacing generated from the manifest. Trigger when adding or editing a section in a page builder, or when mapping a design onto a section contract.

CMS & Page Builderscmssectionscatalogregistry
Install
npx @spectre-apps/skills add cms-section-catalog

Writes .claude/skills/cms-section-catalog/SKILL.md. Add --user to install globally.

What it does

The section catalog

A first-party section is four artifacts, and missing one fails silently (no registry entry → renders null; no preset → empty library card). Adding a type means adding all four and satisfying the compiler everywhere they are read — never editing six lists that can drift.

  1. Manifest — settings, blocks, CTAs, spacingDefaults, a three-letter tag, a preset seeded with the design's real copy.
  2. Component — reads settings with typed helpers; fallbacks equal the preset, so the site renders identically with no published theme.
  3. Registry entry — one flat Record<type, Component>. Both the public renderer and the preview canvas dispatch through this same map.
  4. Preset — real copy, not lorem. The library page and the Figma/reference capture render from it.

The picker, the section library, and the all-sections sandbox are all manifest-driven. A new manifest appears in every one of them for free. Do not add a fifth list.

Field-kind mapping

Map a design onto the SettingField union. Colors are global theme tokens, not per-section settings, unless the value is genuinely local.

Design elementField
Editable line / paragraphtext (multiline: true for paragraphs)
Formatted proserichtext
A link targeturl
An image / video slotimage / video
On/offcheckbox
A fixed set of choicesselect (segmented when ≤ 3 options)
A numeric knobrange (min/max/step/unit)
A visual group dividerheader (label only, not an input)
A named menumenu_picker (stores a handle)
A flat link listlinks (JSON-encoded string)

Repeating groups become blocks: a BlockManifest plus seeded preset instances. Buttons become CTAs — a homogeneous sibling repeater to blocks (every CTA shares one field set: label, link, style). Do not hand-roll a primary/secondary pair in settings.

Anchor groups

A page anchor is a fixed section the page cannot do without (a hero, a product buy box). When a page should offer a choice of layouts for the same slot, the alternatives share an anchorGroup string. The reconciler's rule becomes exactly one member of the group: extras drop (keeping the first), and a page with none gets the group's default.

A layout swap starts the new anchor from its own preset. It deliberately does not carry the old anchor's blocks — they are different layouts, not skins.

Spacing is generated from the manifest

spacingDefaults on the manifest must mirror what the component actually renders. The CSS fallbacks are generated from those numbers (emitCatalogSpacingCss) and injected by the site's root layout. There is one copy of each number.

Never hand-write a padding-block rule for a .cms-section--* class. Change the manifest. If the two copies drift, every spacing control opens at a value the page never had, and Reset breaks the design.

Two mechanisms, deliberately different:

  • Sections emit custom properties with CSS-side fallbacks.
  • Blocks emit concrete values with !important, only for edges the operator actually set.

Adding a section: the whole list

Work top to bottom. A missed touchpoint is the silent failure.

  1. Manifesttype (kebab-case, globally unique), name, description, tag, scope, settings/blocks/CTAs, spacingDefaults, a preset whose copy is distinct (two settings that render the same string break content-keyed locators).
  2. Component — fallback defaults ≡ preset. Read with str / num / bool helpers; render CTAs through the shared CTA helper, not ad-hoc buttons.
  3. Registry — add the type to the flat map. This is the #1 silent miss.
  4. CSS — a root class plus nested kebab-case classes; theme tokens, never a raw hex where a token exists. Section padding comes from the generated custom properties.
  5. Default theme — add the type to a template only if it should ship in the unpublished fallback theme.
  6. Verify — typecheck the contract package, regenerate any lock the catalog is gated on, open the section library / all-sections sandbox, and compare the card against the source design.

A human reviews the rendered result. This skill never publishes a theme.

Three sections that carry decisions

  • Operator HTML (custom-html) strips <script>, inline on* handlers, and javascript: URLs. That covers an embed container hydrated by a separately-loaded app script. Do not loosen the strip to support raw script embeds — add a vendor allowlist or a CSP nonce, so the audit trail survives.
  • Lead form POSTs to an operator-supplied action. With none set, submit is disabled. The CMS stores no submissions of its own; wiring it to a forms product needs no manifest change.
  • Countdown stores an ISO instant, never a duration, and its digits are a client component. Under ISR a server-rendered clock freezes at generation time and shows every visitor the same wrong number.

Pair with cms-imported-sections when the section is arriving as customer data rather than as four artifacts in the repo.