@bettercms-ai/mcp 0.83.0 → 0.83.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +86 -86
  2. package/SKILL.md +160 -160
  3. package/dist/index.js +38 -9
  4. package/dist/index.js.map +1 -1
  5. package/dist/kit-catalog.json +1509 -1509
  6. package/dist/skills/bettercms/SKILL.md +206 -206
  7. package/dist/skills/bettercms/references/accessibility.md +171 -171
  8. package/dist/skills/bettercms/references/animation.md +296 -296
  9. package/dist/skills/bettercms/references/astro.md +148 -148
  10. package/dist/skills/bettercms/references/bindings.md +366 -366
  11. package/dist/skills/bettercms/references/componentize.md +185 -185
  12. package/dist/skills/bettercms/references/components.md +415 -415
  13. package/dist/skills/bettercms/references/content-experiments.md +62 -62
  14. package/dist/skills/bettercms/references/content-modeling.md +382 -382
  15. package/dist/skills/bettercms/references/detection.md +465 -465
  16. package/dist/skills/bettercms/references/dock-input.md +121 -121
  17. package/dist/skills/bettercms/references/editor-chrome.md +92 -92
  18. package/dist/skills/bettercms/references/external-content.md +78 -78
  19. package/dist/skills/bettercms/references/finish-site.md +377 -377
  20. package/dist/skills/bettercms/references/forms-and-interactions.md +245 -245
  21. package/dist/skills/bettercms/references/frameworks.md +159 -159
  22. package/dist/skills/bettercms/references/getting-started.md +156 -156
  23. package/dist/skills/bettercms/references/integrations.md +364 -364
  24. package/dist/skills/bettercms/references/kit-catalog.md +3259 -3259
  25. package/dist/skills/bettercms/references/kit-scenarios.md +712 -712
  26. package/dist/skills/bettercms/references/localization.md +85 -85
  27. package/dist/skills/bettercms/references/media-and-brand.md +181 -181
  28. package/dist/skills/bettercms/references/next.md +189 -189
  29. package/dist/skills/bettercms/references/other-frameworks.md +85 -85
  30. package/dist/skills/bettercms/references/portable-text.md +213 -213
  31. package/dist/skills/bettercms/references/programmatic-seo.md +98 -98
  32. package/dist/skills/bettercms/references/publishing.md +289 -289
  33. package/dist/skills/bettercms/references/seo.md +357 -357
  34. package/dist/skills/bettercms/references/shopify-headless.md +52 -52
  35. package/dist/skills/bettercms/references/site-blueprints.md +895 -895
  36. package/dist/skills/bettercms/references/site-review.md +74 -74
  37. package/dist/skills/bettercms/references/worked-examples-advanced.md +509 -509
  38. package/dist/skills/bettercms/references/worked-examples.md +885 -885
  39. package/dist/skills/bettercms/references/write-limits.md +217 -217
  40. package/package.json +1 -1
@@ -1,712 +1,712 @@
1
- ---
2
- title: "Kit scenarios"
3
- description: "Which component-kit entry fits a section and what to do for it: all 14 kit categories and all 20 edge tags, each with when to pick it, the field shape, the pitfalls and real kit entry ids, plus what a stored component cannot carry."
4
- ---
5
-
6
- # Kit scenarios: pick the closest kit entry, then model it
7
-
8
- The component kit is 149 worked section patterns (142 kit entries in 14 categories, plus 7 core
9
- examples). Each entry carries notes written for a converting agent: what the section is, the HTML
10
- that gives it away, the mapping to fields, and the mistakes to avoid. The full list is the MCP
11
- resource `bettercms://kit` (an index: categories, tags and one line per entry, each with the URI
12
- of its category and of the entry itself) and the generated reference
13
- [kit-catalog.md](kit-catalog.md). This file is the hand-written chooser that points into it.
14
-
15
- ## Contents
16
-
17
- - [How to use the kit](#how-to-use-the-kit)
18
- - [What a stored component cannot carry](#what-a-stored-component-cannot-carry)
19
- - [Categories](#categories)
20
- - [Edge tags](#edge-tags)
21
- - [Gaps the kit names](#gaps-the-kit-names)
22
-
23
- ## How to use the kit
24
-
25
- 1. **Name the band's category** from the chooser below (or detection.md §7). The category decides
26
- where the section lives: chrome goes in the Global Layout, a record type becomes a collection,
27
- everything else is a section component or page fields.
28
- 2. **Pick the closest entry.** Inside the category, match the strongest HTML signal; the chooser
29
- lists what separates each entry from its neighbours. Then read that entry's own resource from
30
- the index: its `mcp` note is the mapping, its `pitfalls` are the review list, its `htmlSignals`
31
- are what to look for, and its `component` is the props list the kit converts to (step 4).
32
- 3. **Check its edge tags** against the [edge tag rules](#edge-tags). A band that matches no entry
33
- still has tags (a list of cards is `repeater`; a price is `commerce`), and the tag rules hold
34
- for it.
35
- 4. **Never send an entry's props to `create_component` as they are.** The catalogue's props have
36
- no `target`; every prop you declare needs one, aimed at the leaf your own `blockJson` renders
37
- for it (schema playbook §3, [components.md](components.md) §1). Write the `blockJson` first.
38
- 5. **Copy the shape, never the sample copy.** The entries use a fictional cookware brand
39
- (Ember, Hearth & Ash, Sierra camp). Write the customer's words.
40
- 6. **Respect the entry's limits.** A pitfall that starts "SCHEMA GAP" is something the platform
41
- cannot check; do the check yourself at import time and flag what you cannot.
42
-
43
- ## What a stored component cannot carry
44
-
45
- Each catalogue entry lists `notCarried`: kit features the stored component drops. Do the
46
- production equivalent instead of losing the intent.
47
-
48
- | notCarried | What to do instead |
49
- |---|---|
50
- | `variants` | Each layout variant is its own component sharing one `sectionType`, same prop keys ([components.md](components.md)) |
51
- | `kind:global` | The section is chrome: the Global Layout (`get_layout`, `update_layout`), never a page section |
52
- | `kind:entry (props)` | The entry is a record: a collection model and entries, not a component |
53
- | `slot allow` | Name the allowed child components with the slot prop's `config.componentIds` |
54
- | `reference target` | Point the reference at a collection with `config.contentModelId` |
55
- | `ai`, `category`, `item openMode`, `item settings`, `group (nested)`, `fieldset (derived)`, `document preview path`, `segmented>4`, `validation.items`, `itemNoun (derived)` | Editor presentation only; arrange the dock with [editor-chrome.md](editor-chrome.md) where it matters |
56
-
57
- ## Categories
58
-
59
- Each category: when it applies, the shape, the traps, and a chooser of every entry in it.
60
-
61
- ### Navigation & chrome
62
-
63
- **When:** the band is the same on every page (header, footer, announcement bar, cookie banner,
64
- docs sidebar), or it is a per-template bar such as a sticky mobile buy bar.
65
-
66
- **Shape:** one global, created once and placed by the layout. Links are rows of `{label, link}`;
67
- a call to action is one button; design (sticky, transparent, tone) is a setting, never copy.
68
- Breadcrumbs and the sticky mobile bar are not globals: the first is per page and mostly automatic,
69
- the second sits on the template it appears on.
70
-
71
- **Traps:** a link field for the logo (it always goes home); copying a cart count, prices or the
72
- customer name (session state, `nav-ecommerce-header`); free-text opening hours
73
- (`footer-mega-locations`); a countdown stored as text (`announcement-countdown`); pre-ticked
74
- optional consent categories (`cookie-consent`); the hamburger markup (the renderer's job,
75
- `nav-simple-cta`).
76
-
77
- | Entry | Pick it when |
78
- |---|---|
79
- | `nav-simple-cta` | Logo, a short link row, one button, maybe a tel: link |
80
- | `nav-announcement-utility` | Stacked rows: a one-sentence strip, a utility row, the main nav |
81
- | `nav-mega-featured-promo` | Top items open panels with headed columns, product cards and a promo tile |
82
- | `nav-docs-sidebar` | The same nav tree on every /docs page; auto from the page tree when order matches the URLs |
83
- | `nav-ecommerce-header` | Search, account and cart controls; collection links |
84
- | `nav-locale-header` | Language and currency switchers; the same header in every locale |
85
- | `sticky-mobile-cta-bar` | position fixed at the bottom, one button, shown on phones |
86
- | `announcement-countdown` | A separate dismissible strip with a countdown or rotating messages |
87
- | `cookie-consent` | A consent banner with category toggles (unless a vendor CMP script owns it: report it) |
88
- | `breadcrumb-config` | A crumb trail; auto when every crumb is a prefix of the current path |
89
- | `footer-multi-column` | Headed link groups, a newsletter form, socials, payment badges |
90
- | `footer-minimal` | No headings and under about eight links |
91
- | `footer-mega-locations` | Locations with addresses, hours and map links, app badges |
92
-
93
- ### Heroes
94
-
95
- **When:** the page's opening band: one `h1`, a lede, one or two buttons, a media column or a
96
- background.
97
-
98
- **Shape:** plain strings for eyebrow, heading and body; buttons as button units; one media slot
99
- whose type (image, video, gallery) is a select revealing its own fields (`hero-split-media`).
100
- Live data comes through a reference (a product, a testimonial), forms through a form reference,
101
- dates as datetimes with an offset.
102
-
103
- **Traps:** price, SKU or stock copied into the hero (`hero-product`); a count-up read mid-animation
104
- as "0" (`hero-stats`); QR or store-badge images uploaded instead of their links
105
- (`hero-app-download`); raw CSS gradients stored as strings (`hero-centered-gradient`); every
106
- audience holding a full copy of the hero (`hero-personalized`).
107
-
108
- | Entry | Pick it when |
109
- |---|---|
110
- | `hero-split-media` | Text column plus one media column (image, video or a few images) |
111
- | `hero-centered-gradient` | Centered `h1`, an optional pill link, a gradient background, a screenshot |
112
- | `hero-video-fullbleed` | A background `<video>` behind the text; needs captions, poster and a text alternative |
113
- | `hero-slider` | Several full-width slides from a carousel library |
114
- | `hero-product` | A product form at the top: pickers, add to cart |
115
- | `hero-search` | A search form is the main control |
116
- | `hero-lead-form` | A hero beside a form of a few inputs |
117
- | `hero-app-download` | App store badges, maybe a QR code and device screens |
118
- | `hero-stats` | Two to four big numerals in the hero |
119
- | `hero-logo-wall` | A strip of customer logos under the hero copy |
120
- | `hero-split-testimonial` | A quote card beside or over the hero image |
121
- | `hero-event` | A date, a place and a ticket button |
122
- | `hero-personalized` | Hidden variants in the DOM or different copy per UTM or region |
123
-
124
- ### Features & storytelling
125
-
126
- **When:** bands that explain the product: grids of features, rows of text and media, steps,
127
- timelines, tabs, comparisons, stats, a manifesto.
128
-
129
- **Shape:** a list of same-shaped items inside one band. Column count, side and layout direction
130
- are variants. Media per item is a select revealing image or video. Numbers are value, prefix and
131
- suffix; dates carry their precision.
132
-
133
- **Traps:** a free `columns` number (`feature-icon-grid`); a stored step number
134
- (`steps-how-it-works`); a `side` on every row, which breaks the zigzag on reorder
135
- (`feature-alternating-rows`); a bare year in a date field (`timeline-company-history`); FAQ
136
- markup on rows that are not questions (`accordion-feature-image-swap`); comparative claims with
137
- no date (`comparison-vs-competitors`).
138
-
139
- | Entry | Pick it when |
140
- |---|---|
141
- | `feature-icon-grid` | Same-shaped cards: small icon, title, text, optional link |
142
- | `feature-alternating-rows` | Text and media rows that flip sides |
143
- | `bento-feature-showcase` | Grid cells with different spans and different inner shapes |
144
- | `steps-how-it-works` | Numbered steps, maybe a connector line |
145
- | `timeline-company-history` | Dated milestones on a line |
146
- | `tabs-with-media` | Same-shaped tab panels with an image or video each (different shapes: core `tabs`) |
147
- | `accordion-feature-image-swap` | Accordion rows with one image per row |
148
- | `comparison-vs-competitors` | Brand columns with check and cross cells |
149
- | `stats-band` | A row of big numbers with labels and footnoted sources |
150
- | `manifesto-pull-quote` | One large quote, or several short display lines |
151
- | `problem-solution` | Two lists, problems and fixes, paired |
152
- | `integrations-grid` | Logo tiles linking to /integrations pages, filter chips |
153
-
154
- ### Social proof
155
-
156
- **When:** testimonials, video stories, a wall of social posts, case study cards, logos, press,
157
- awards, a review summary.
158
-
159
- **Shape:** testimonials, case studies and reviews are collection entries the section references,
160
- by pick or by query (tags, limit, minimum rating). Logos are rows of name plus file. Awards carry
161
- a kind that changes what is required (certificate id and expiry for certifications).
162
-
163
- **Traps:** inlined quotes that drift from the approved wording (`testimonial-grid`); a logo named
164
- "logo" (`logo-cloud`); marquee duplicates imported twice (`logo-cloud`); expired certifications
165
- shown as current, or "HIPAA certified" (`awards-certifications`); self-serving Review or
166
- AggregateRating markup (`testimonial-grid`, `review-summary`); hosted video without captions
167
- (`testimonial-video-slider`).
168
-
169
- | Entry | Pick it when |
170
- |---|---|
171
- | `testimonial-grid` | Three or more quote cards with name and role |
172
- | `testimonial-video-slider` | Slides that are videos or quotes |
173
- | `wall-of-love` | Mixed social posts, store reviews and photo posts |
174
- | `case-study-cards` | Customer cards linking to story pages with a metric |
175
- | `logo-cloud` | Logos only, no quotes |
176
- | `press-mentions` | Outlet logos with quotes linking to articles |
177
- | `awards-certifications` | Badges: awards, certifications, memberships |
178
- | `review-summary` | An average, a star distribution, "based on N reviews" |
179
-
180
- ### Media & galleries
181
-
182
- **When:** photo grids, filtered galleries, video, before and after, hotspots, 3D or Lottie, mixed
183
- carousels, a full-bleed image with overlay text, a social photo feed.
184
-
185
- **Shape:** image rows with alt, caption and a separate credit; per-breakpoint columns as design;
186
- positions as percentages; the video source a select between a link and a hosted file.
187
-
188
- **Traps:** the thumbnail imported instead of the full file (`masonry-gallery`); credits pasted
189
- into captions (`masonry-gallery`); pixel hotspot positions (`image-hotspots`); a 40 MB model or
190
- an un-vetted iframe host (`interactive-3d-lottie-embed`); cloned slides (`mixed-media-carousel`);
191
- UGC widget scripts and photo rights assumed (`ugc-social-feed`).
192
-
193
- | Entry | Pick it when |
194
- |---|---|
195
- | `masonry-gallery` | Many photos in CSS columns or masonry, a lightbox |
196
- | `lightbox-gallery-filters` | Filter buttons over a photo grid |
197
- | `video-section` | A YouTube, Vimeo or hosted video with captions and chapters |
198
- | `before-after-slider` | Two stacked images, one clipped, a handle |
199
- | `image-hotspots` | Positioned pins on an image opening a product or a note |
200
- | `interactive-3d-lottie-embed` | `<model-viewer>`, a Lottie player or an allow-listed 3D iframe |
201
- | `mixed-media-carousel` | Slides of different kinds: photo, video, quote, product |
202
- | `parallax-image-overlay` | One full-bleed image with text on top |
203
- | `ugc-social-feed` | A grid of social photos from an account, a hashtag or hand-picked |
204
-
205
- ### Commerce
206
-
207
- **When:** product grids, a product page, a featured product, collection filters, bundles, cart
208
- offers, a promo with a code, shop the look, size guides, store locators, product comparisons.
209
-
210
- **Shape:** products are referenced (a Products entry linked to the store product); a grid stores
211
- a query or picked references; a product page is a Products entry, not a block; size guides and
212
- comparisons use the `table` type keyed by column.
213
-
214
- **Traps:** prices, compare-at prices, options, variants or stock copied into content
215
- (`commerce-product-detail`, `commerce-product-grid-query`); facet values imported as content
216
- (`commerce-collection-header-facets`); an invented discount code (`commerce-bundle-builder`); a
217
- deadline without an offset (`commerce-promo-countdown`); inches converted to centimetres
218
- (`commerce-size-guide`); map API keys imported (`commerce-store-locator`).
219
-
220
- | Entry | Pick it when |
221
- |---|---|
222
- | `commerce-product-grid-query` | Product cards sharing a rule (one collection, "New") or picked |
223
- | `commerce-product-detail` | The product page itself: gallery, options, specs, policies |
224
- | `commerce-featured-product` | One product spotlighted, maybe with add to cart |
225
- | `commerce-collection-header-facets` | A collection header with filter groups and sort |
226
- | `commerce-bundle-builder` | Choose N products, tiered or fixed bundle price |
227
- | `commerce-cart-cross-sell` | Upsell offers in the cart, a free-shipping threshold |
228
- | `commerce-promo-countdown` | A promo strip with a code and a timer |
229
- | `commerce-shop-the-look` | Looks: images with product dots |
230
- | `commerce-size-guide` | Size tables, maybe a unit toggle, measuring steps |
231
- | `commerce-store-locator` | A map beside an address list, hours, holiday exceptions |
232
- | `commerce-product-comparison` | Two to four products side by side |
233
-
234
- ### Pricing & plans
235
-
236
- **When:** plan cards with a billing switch, usage calculators, feature matrices, a single plan
237
- with add-ons, enterprise contact, pricing FAQs, a pay-yearly banner.
238
-
239
- **Shape:** both cycle prices written per plan (never derived by a percentage); per-seat, free and
240
- custom pricing as a select; "Everything in X, plus" as inheritance with only the new bullets; the
241
- matrix as a `table` keyed by plan key; payment price ids developer-locked.
242
-
243
- **Traps:** a "Save 20%" badge that is not true for every plan (`pricing-tiers-billing-toggle`);
244
- a formula stored as a string (`pricing-usage-calculator`); pre-checked paid add-ons
245
- (`pricing-single-plan-addons`); contact fields inlined instead of a form reference
246
- (`pricing-enterprise-card`); two FAQPage blocks on one URL (`pricing-faq`); a saving not computed
247
- from real prices (`pricing-annual-savings-banner`).
248
-
249
- | Entry | Pick it when |
250
- |---|---|
251
- | `pricing-tiers-billing-toggle` | Plan cards with a monthly or yearly switch |
252
- | `pricing-usage-calculator` | Range sliders and a live total from graduated rates |
253
- | `pricing-feature-matrix` | A large table of plans against features |
254
- | `pricing-single-plan-addons` | One plan plus optional extras with units |
255
- | `pricing-enterprise-card` | A contact-sales card: form, calendar link or plain link |
256
- | `pricing-faq` | Grouped questions on the pricing page, maybe price tokens in answers |
257
- | `pricing-annual-savings-banner` | A banner urging monthly payers to switch |
258
-
259
- ### Conversion & forms
260
-
261
- **When:** CTA bands, newsletter sign-up, lead capture, multi-step forms, booking embeds, waitlists,
262
- contact sections, quizzes, popups, trust badges.
263
-
264
- **Shape:** a section holds presentation and a Form reference; inputs belong to the Form document
265
- ([forms-and-interactions.md](forms-and-interactions.md)). Success behaviour (message, redirect,
266
- download) is a select. Booking widgets store the provider and its id only.
267
-
268
- **Traps:** a per-section copy of the inputs, which forks submissions (`conversion-lead-capture`);
269
- placeholder-only labels (`conversion-newsletter-consent`); a pre-ticked consent box
270
- (`conversion-newsletter-consent`); a widget script stored as HTML (`conversion-booking-embed`); a
271
- static "join 12,000 others" number (`conversion-waitlist-referral`); guessed quiz weights
272
- (`conversion-quiz-product-finder`); a popup on checkout or full-screen on phones
273
- (`conversion-exit-intent-popup`); typed review scores (`conversion-trust-badges`).
274
-
275
- | Entry | Pick it when |
276
- |---|---|
277
- | `conversion-cta-band` | Heading, one sentence, one or two buttons, no inputs |
278
- | `conversion-newsletter-consent` | One email input with consent |
279
- | `conversion-lead-capture` | Copy beside a multi-field form with a success state |
280
- | `conversion-multi-step-form` | Fieldsets with Next and Back, branching |
281
- | `conversion-booking-embed` | Resy, OpenTable, Tock or Cal widget |
282
- | `conversion-waitlist-referral` | A launch waitlist with referral rewards |
283
- | `conversion-contact-section` | Email, phone and chat channels, a form, a map |
284
- | `conversion-quiz-product-finder` | Questions that end in product recommendations |
285
- | `conversion-exit-intent-popup` | A dialog on exit, delay, scroll or click (one global per dialog) |
286
- | `conversion-trust-badges` | A row of guarantees, shipping, certifications, payment icons |
287
-
288
- ### Listings & feeds
289
-
290
- **When:** blog indexes, category filter bars, related posts, resource libraries, podcast episode
291
- lists, changelogs, roadmaps, search results.
292
-
293
- **Shape:** the section stores a query against a collection (filters, sort, page size,
294
- pagination), with pinned or featured picks as references. Items are entries imported first.
295
- Related posts go on the post template, not on each post.
296
-
297
- **Traps:** nine visible cards frozen into an inline list (`blog-index`); typed chip labels
298
- (`category-filter-bar`); picks on a template, which apply to every post (`related-posts`); facets
299
- on non-indexed fields (`resource-library`); an embed iframe instead of the RSS audio
300
- (`podcast-episode-list`); free-text change badges (`changelog`); editable vote counts
301
- (`public-roadmap`); search results imported or a noindex field added (`search-results-page`).
302
-
303
- | Entry | Pick it when |
304
- |---|---|
305
- | `blog-index` | Article cards with dates and a shared href prefix |
306
- | `category-filter-bar` | Topic chips that filter a list or navigate |
307
- | `related-posts` | Two to four cards after an article |
308
- | `resource-library` | Typed resources with facet filters and gated downloads |
309
- | `podcast-episode-list` | Show details, platform links and an episode list |
310
- | `changelog` | Version or date headings with typed changes |
311
- | `public-roadmap` | Stage columns of items, votes, a suggestion form |
312
- | `search-results-page` | The /search page: sources, pins, empty state |
313
-
314
- ### Long-form & docs
315
-
316
- **When:** a page whose main content is one body: articles, docs, knowledge-base answers, recipes,
317
- legal policies, case studies, podcast episodes; or a block inside such a page (author box, code
318
- tabs, glossary, curriculum).
319
-
320
- **Shape:** most entries here are collection entries, not components (their notCarried says
321
- `kind:entry`): title, slug, dates, references to authors and topics, and ONE rich-text body with
322
- islands for callouts, tables, code, images, video and component instances
323
- ([portable-text.md](portable-text.md)). Structured parts (ingredients, sections and clauses,
324
- modules and lessons, chapters) are lists, not prose.
325
-
326
- **Traps:** an `h1` in the body (`rich-blog-post`); the author box copied instead of referenced
327
- (`rich-blog-post`, `author-bio`); the on-page table of contents imported (`docs-page`); hard-coded
328
- prev and next (`docs-page`); highlighted code `<span>` soup (`code-tabs`); letter headings stored
329
- as terms (`glossary`); ingredient lines as plain strings (`recipe`); typed clause numbers and
330
- changed anchors (`legal-policy`); durations as text (`course-curriculum`); the episode embed
331
- instead of the audio file (`podcast-episode`).
332
-
333
- | Entry | Pick it when |
334
- |---|---|
335
- | `rich-blog-post` | An article with a byline, cover and rich body (entry) |
336
- | `author-bio` | An author box under posts (on the template) |
337
- | `docs-page` | A docs article with sidebar position, admonitions and pager (entry) |
338
- | `code-tabs` | One example in several languages, synced tabs |
339
- | `kb-article` | A help answer with "Was this helpful?" (entry) |
340
- | `glossary` | Terms and definitions with a letter index |
341
- | `recipe` | Ingredients, steps, times and nutrition (entry) |
342
- | `course-curriculum` | Modules of lessons with types and free previews |
343
- | `legal-policy` | Numbered sections and clauses, effective date, versions (entry) |
344
- | `case-study` | Customer facts, metrics, challenge, solution, results (entry) |
345
- | `podcast-episode` | One episode: audio, notes, chapters, transcript (entry) |
346
-
347
- ### Company & people
348
-
349
- **When:** team grids, leadership bios, values and culture, careers, job postings, about and
350
- timeline, press kits.
351
-
352
- **Shape:** people are People entries referenced from the section (by query or pick), with
353
- per-placement overrides for title or bio. Jobs are Jobs entries or synced from a hiring system.
354
- Asset groups in a press kit are lists of assets with formats.
355
-
356
- **Traps:** people retyped per page (`team-grid`); the CEO duplicated for the board group
357
- (`leadership-bios`); job rows copied into the careers section (`careers-list`); salary minimum
358
- above maximum, which the schema cannot catch (`job-posting`); closed roles deleted instead of
359
- closed (`job-posting`); year ranges stored as strings (`about-timeline`); photos without credit
360
- (`press-kit`).
361
-
362
- | Entry | Pick it when |
363
- |---|---|
364
- | `team-grid` | Headshots with name and role, maybe department tabs |
365
- | `leadership-bios` | Leadership, board and advisor groups with bio modals |
366
- | `values-culture` | Values with examples, perks, a culture photo wall |
367
- | `careers-list` | Open roles from the site or a hiring system |
368
- | `job-posting` | One job page (entry) |
369
- | `about-timeline` | Mission, story, founders, milestones |
370
- | `press-kit` | Downloadable logos and photos, boilerplate, facts, coverage |
371
-
372
- ### Events & places
373
-
374
- **When:** events lists, conference pages, webinar registration, location directories, restaurant
375
- menus, single location pages.
376
-
377
- **Shape:** events and locations are entries; a list stores a query and splits upcoming from past
378
- by computing it. Datetimes carry an offset or a separate zone. Opening hours are rows per day
379
- (closed flag, one or more shifts), with special-hours rows for holidays. Menus nest menus,
380
- sections, items and price variants.
381
-
382
- **Traps:** past events flagged "upcoming" by hand (`events-list`); agenda times in the visitor's
383
- zone (`event-detail`); speakers typed into session titles (`event-detail`); a separate replay page
384
- instead of switching the webinar stage (`webinar-registration`); hours as one text line
385
- (`locations-directory`); overnight closing read as earlier than opening (`location-page`);
386
- "$18 / $32" in one price (`restaurant-menu`); allergen data no owner signed off
387
- (`restaurant-menu`).
388
-
389
- | Entry | Pick it when |
390
- |---|---|
391
- | `events-list` | Upcoming and past events as list, cards, calendar or map |
392
- | `event-detail` | One conference: days, sessions, speakers, venue, ticket tiers (entry) |
393
- | `webinar-registration` | One webinar across upcoming, live and on demand |
394
- | `locations-directory` | Map plus list of many locations with hours and services |
395
- | `restaurant-menu` | Menus, sections, dishes, prices, dietary tags |
396
- | `location-page` | One location's page: address, hours, booking (entry) |
397
-
398
- ### Data & interactive
399
-
400
- **When:** spec tables, comparison tables, charts, calculators, product configurators, maps with
401
- markers, product comparison sliders, countdowns and live counters, polls, embeds, code snippets.
402
-
403
- **Shape:** tables use the `table` type: typed, keyed columns, a caption, row headers, row groups,
404
- footnotes; more than about 60 rows comes from a CSV or a collection (`data-table-specs`). Charts
405
- store their data as series and points, plus a required one-sentence summary
406
- (`chart-attrition`). Calculators store typed inputs and outputs whose formulas name input keys
407
- (`roi-calculator`). Maps store markers with geopoints and keep a list beside the map
408
- (`stockist-map`). Embeds store a URL or provider id with a required title (`safe-embed`).
409
-
410
- **Traps:** a rasterised chart, or values read off bar heights without review
411
- (`chart-attrition`); an approximated formula, which should stay a code component with copy
412
- fields only (`roi-calculator`); rules by index instead of keys (`product-configurator`); product
413
- names typed into a comparison (`product-compare-slider`); a local time without a zone
414
- (`countdown-live-counter`); a fabricated live counter or a per-visitor countdown
415
- (`countdown-live-counter`); vote counts imported, or a survey nested in a poll (`reader-poll`);
416
- tracking pixels or chat widgets migrated as embeds (`safe-embed`); highlighted HTML stored as
417
- code (`code-snippet-tabs`).
418
-
419
- | Entry | Pick it when |
420
- |---|---|
421
- | `data-table-specs` | A spec table with typed columns, maybe from a CSV or collection |
422
- | `table-comparison` | The reference table: typed columns, groups, footnotes, notes |
423
- | `chart-attrition` | A chart with series, axes and annotations |
424
- | `roi-calculator` | Inputs that feed computed outputs |
425
- | `product-configurator` | Steps of options with price deltas and availability rules |
426
- | `stockist-map` | A map with categorised markers and popups |
427
- | `product-compare-slider` | Products compared by attributes, swipe on phones |
428
- | `countdown-live-counter` | A countdown to a launch, maybe a live counter |
429
- | `reader-poll` | One question with options, a scale or an open answer |
430
- | `safe-embed` | Any iframe, oEmbed URL or script widget |
431
- | `code-snippet-tabs` | A code block with tabs per language or package manager |
432
-
433
- ### Advanced patterns
434
-
435
- **When:** the shape itself is the hard part: deep nesting, mixed item types, conditional sources,
436
- globals with overrides, translations, personalization, responsive settings, grids with per-cell
437
- layout, reference webs, nested validation, rich text with components, query listings, overlays,
438
- scrollytelling, accessibility-critical media, section theme tokens.
439
-
440
- **Shape and traps:** each entry is the long form of one [edge tag](#edge-tags); read the tag rule
441
- first, then the entry. Most of their pitfalls are SCHEMA GAPs: listed in
442
- [Gaps the kit names](#gaps-the-kit-names).
443
-
444
- | Entry | Pick it when | Tag rule |
445
- |---|---|---|
446
- | `page-builder-six-deep` | Tabs holding columns holding accordions holding cards | deep-nesting |
447
- | `polymorphic-modules` | An article built from mixed module types | polymorphic-array |
448
- | `conditional-source-matrix` | One section whose items come manually, from a collection, a query or an external service | conditional |
449
- | `global-announcement-overrides` | A bar on most pages whose copy differs on a few | global-synced |
450
- | `localized-section` | Copy in several locales, right-to-left included | localized |
451
- | `personalized-hero` | Audience segments and an A/B split on one hero | personalization |
452
- | `responsive-layout-settings` | Columns, gap and spacing that differ per breakpoint | responsive-layout |
453
- | `grid-cell-layout` | A grid whose cells set their own span, surface and alignment | slots |
454
- | `references-web` | Products, authors, recipes and pages linked, pinned and auto-filled | references |
455
- | `changelog-nested-validation` | Releases of changes with semver and issue-key patterns | validation |
456
- | `rich-text-embedded-components` | A body with component islands, footnotes and glossary terms | rich-text |
457
- | `dynamic-query-listing` | A listing defined by filters, sort, limit and pins | dynamic-query |
458
- | `overlays-with-triggers` | Modals, drawers and popovers opened by triggers | slots |
459
- | `scrollytelling` | Sticky media that changes as text steps scroll past | media-heavy |
460
- | `a11y-critical-media-form` | Video with captions and transcript, image roles, a labelled form | a11y |
461
- | `section-theme-tokens` | A dark or brand-coloured section inside a light page | variants |
462
-
463
- ### Core examples
464
-
465
- Seven original examples (`core-hero`, `core-faq`, `core-pricing`, `core-tabs`, `core-bento`,
466
- `core-megaMenu`, `core-journalStory`). They carry a schema and a sample value but no notes:
467
- use them as reference shapes and take the guidance from the matching category above
468
- (`core-bento` is worked through end to end in [worked-examples.md](worked-examples.md) §9).
469
-
470
- ## Edge tags
471
-
472
- An edge tag names one modelling difficulty. Every entry lists its tags; a section you build
473
- inherits the rules of every tag it has.
474
-
475
- ### deep-nesting
476
-
477
- Count levels where the content changes kind, not DOM wrappers: same-shaped siblings are a list,
478
- siblings that differ are slot children (`page-builder-six-deep`). Up to six content levels stay in
479
- one section; past six, or past about ten children at a level, split the deepest repeated subtree
480
- into its own block. Never make one thing both a list item and a slot. Keep the deepest tiny rows
481
- inline (sizes in `restaurant-menu`, resources in `course-curriculum`). A sidebar deeper than four
482
- levels is flattened and reported (`nav-docs-sidebar`). Cases: `core-megaMenu`,
483
- `nav-mega-featured-promo`, `commerce-product-detail`, `conversion-quiz-product-finder`,
484
- `press-kit`, `event-detail`, `location-page`.
485
-
486
- ### repeater
487
-
488
- Same-shaped items in one band are one list in one section, never one section per item
489
- (`feature-alternating-rows`). Never store what order already says: a step number
490
- (`steps-how-it-works`), clause numbering (`legal-policy`), a totals line (`course-curriculum`).
491
- Dedupe clones a carousel or marquee adds (`hero-slider`, `logo-cloud`). Unequal paired lists are
492
- flagged, never truncated (`problem-solution`). Long lists move to a collection: 60+ releases
493
- (`changelog`), 300 glossary terms (`glossary`). Plain strings are a string list, not rows of one
494
- field (`hero-split-media` points).
495
-
496
- ### polymorphic-array
497
-
498
- One list whose items have different shapes: each item names its type, and the editor picks the
499
- type on add (`polymorphic-modules`). Classify each item by its dominant element: a figure is an
500
- image item, a blockquote a quote item, a big numeral a stat item, else text
501
- (`polymorphic-modules`, `wall-of-love`, `mixed-media-carousel`). Merge consecutive paragraphs into
502
- one text item; one item per paragraph is the commonest bad conversion. Over about 40 items, or
503
- mostly prose, use a rich-text body with islands instead. On a component this is a `table` prop in
504
- types mode (playbook §5). Cases: `commerce-product-detail` gallery,
505
- `conversion-multi-step-form` fields, `core-tabs`, `core-bento`.
506
-
507
- ### slots
508
-
509
- Blocks placed inside a block: the children differ in shape and the editor chooses them
510
- (`core-tabs`, `core-bento`). Per-child layout (span, surface, alignment, order on phones) goes
511
- in the slot's settings, never on the child's own fields, or reusing the child elsewhere carries a
512
- meaningless span (`bento-feature-showcase`, `grid-cell-layout`). Validate spans against the
513
- variant's column count. Regular same-shaped cells are a plain card list, not a slot
514
- (`grid-cell-layout`). Overlays hold their own slot content, and their triggers are `#id` links
515
- (`overlays-with-triggers`). A marketer-owned promo tile beside structured menu columns is a slot
516
- (`nav-mega-featured-promo`). Worked example: [worked-examples.md](worked-examples.md) §9.
517
-
518
- ### variants
519
-
520
- Layout choices are variants of one section, never a free number or boolean the editor can break:
521
- column count (`feature-icon-grid`), image side (`commerce-featured-product`), grid versus carousel
522
- (`commerce-product-grid-query`), striped or compact tables (`data-table-specs`). Pick the variant
523
- from the rendered layout. A field that only one variant uses cannot be hidden by the variant
524
- today, so its help text says which variant reads it (`conversion-cta-band`). Theme (dark section,
525
- accent, spacing) maps to brand tokens, never stored hex or pixels (`section-theme-tokens`).
526
-
527
- ### conditional
528
-
529
- A select (source, kind, mode, type) reveals only the fields of its branch: media type
530
- (`hero-split-media`), pick versus query (`testimonial-grid`), source manual, collection, query or
531
- external (`conditional-source-matrix`), success message, redirect or download
532
- (`conversion-lead-capture`). Fill only the active branch: hidden values are kept and published.
533
- A condition reads one sibling only, so a child cannot react to its parent (the quiz picture
534
- field in `conversion-quiz-product-finder`, the swatch in `product-configurator`), and toggles do
535
- not chain (`countdown-live-counter`). Decide the source from how the items got there, never how
536
- they look; a snapshot of a dynamic list is never frozen into a manual one.
537
-
538
- ### references
539
-
540
- Link to the existing entry, product or page instead of copying its title, photo or price; store
541
- only what differs as an override (`commerce-featured-product`, `case-study-cards`,
542
- `leadership-bios`). Resolve every link before creating anything, and create missing targets as
543
- entries first (`references-web`, `case-study-cards`). Match existing entries before creating (by
544
- name plus the first 40 characters of a quote, `testimonial-grid`). Chips and filters reference a
545
- taxonomy collection, never typed labels (`category-filter-bar`, `integrations-grid`). A
546
- reference by id survives slug changes; a stored href does not. Picks belong to the instance, not
547
- the template (`related-posts`).
548
-
549
- ### dynamic-query
550
-
551
- A listing stores the query (collection, filters, sort, limit, pagination, pins), never copies of
552
- the cards; otherwise new items never appear (`blog-index`, `dynamic-query-listing`,
553
- `commerce-product-grid-query`). Infer the query by comparing the listing across pages and over
554
- time. Create the collection and import the items as entries first, then place the block. A
555
- hand-curated list with no shared rule is a references list instead. Search, related posts and
556
- synced job boards configure a source and import nothing (`search-results-page`, `related-posts`,
557
- `careers-list`). An empty result hides the block; never add placeholder items.
558
-
559
- ### global-synced
560
-
561
- An element repeated with the same content on most pages (the kit says 80%) is one global,
562
- created once from the most common version (`global-announcement-overrides`). Pages that differ
563
- get an instance overriding only the differing fields; a page without it hides the instance. If
564
- more than half the fields differ on more than a third of pages, it is a normal block. Header,
565
- footer, announcement, cookie banner, docs sidebar and popups are globals in the kit
566
- (`nav-simple-cta`, `footer-multi-column`, `cookie-consent`, `nav-docs-sidebar`,
567
- `conversion-exit-intent-popup`); in BetterCMS chrome is the Global Layout. A field synced from a
568
- global inside a normal block (store links in `hero-app-download`) is written to the global too.
569
-
570
- ### localized
571
-
572
- Crawl every locale and match the same section by position and structure, never by text
573
- (`localized-section`, `nav-locale-header`). The default locale fills the fields; each other locale
574
- carries its copy, direction (rtl for ar, he, fa, ur) and a status; a missing locale is recorded
575
- as missing. Never translate during conversion, and flag legal text for per-locale review
576
- (`legal-policy`). Link to the page and let locale routing resolve it, never typed /de/ URLs; never
577
- model hreflang (`nav-locale-header`). A header that differs in structure per locale is a
578
- per-market header, not a translation. Languages are codes, not flags. Sort term lists with the
579
- locale's collation (`glossary`). Keep credits out of translatable captions (`masonry-gallery`).
580
- Platform locales: [localization.md](localization.md).
581
-
582
- ### rich-text
583
-
584
- An article body is one rich-text field. Non-prose elements become islands: figure to image,
585
- table to table, aside to callout, details to toggle, a known card to a component instance with
586
- overrides (`rich-blog-post`, `rich-text-embedded-components`). Template chrome (share bar, author
587
- box, related cards, the table of contents) is not body content. Demote an `h1` in the body.
588
- Footnotes are keyed annotations with their text held beside the body; internal links resolve to
589
- entries. An island used in more than one article must be a library component. Split a body only
590
- where the layout needs it (challenge, solution, results in `case-study`). Shapes:
591
- [portable-text.md](portable-text.md).
592
-
593
- ### media-heavy
594
-
595
- Import the largest source (lightbox href or the biggest srcset entry), keep real alt text, and
596
- never "logo", "image" or "team photo" (`masonry-gallery`, `logo-cloud`, `values-culture`). Video
597
- needs a poster (extract or generate one) and, when it has speech, captions; a missing caption
598
- file is flagged, never invented (`hero-video-fullbleed`, `testimonial-video-slider`). Positions on
599
- an image are percentages (`image-hotspots`, `commerce-shop-the-look`). Art-directed phone images
600
- are their own field (`parallax-image-overlay`, `hero-slider`). Keep logos' original files and never
601
- recolour them in content (`integrations-grid`, `press-mentions`). Sticky-panel media lives on the
602
- step it belongs to (`scrollytelling`).
603
-
604
- ### embed
605
-
606
- Store the provider and its id, or the canonical URL, never the widget script or its HTML
607
- (`conversion-booking-embed`, `safe-embed`, `ugc-social-feed`). An iframe needs a title; write one
608
- if missing. Only allow-listed hosts go into the embed block; anything else becomes a developer
609
- task (`interactive-3d-lottie-embed`). Tracking pixels, analytics and chat widgets are site
610
- settings, not embeds (`safe-embed`). Never import API keys (`commerce-store-locator`,
611
- `stockist-map`). Prefer importing the underlying data over an embed: RSS audio over a player
612
- iframe (`podcast-episode-list`), a public roadmap's JSON over its iframe (`public-roadmap`), code
613
- as plain text with highlighting at render (`code-tabs`, `code-snippet-tabs`).
614
-
615
- ### form
616
-
617
- A section references a Form document by id; it never holds the inputs (`hero-lead-form`,
618
- `conversion-lead-capture`, `pricing-enterprise-card`). Match an existing form by its inputs or
619
- list id before creating one, and tag a shared list instead of making one form per launch
620
- (`conversion-waitlist-referral`). Per-placement settings are allowed: hidden attribution fields,
621
- a submit label that differs, success behaviour. Static hidden values only; never store personal
622
- data from the URL. A sequence of questions is a Form, not a poll (`reader-poll`). Third-party
623
- form embeds (HubSpot, Typeform) are reported with their id. Rules:
624
- [forms-and-interactions.md](forms-and-interactions.md).
625
-
626
- ### commerce
627
-
628
- The store owns price, compare-at price, SKU, stock, options, variants, cart and currency: reference
629
- the product and never copy them (`commerce-product-detail`, `hero-product`,
630
- `nav-ecommerce-header`). Flag any currency typed into copy. "Add to cart" is an action, not a link
631
- (`sticky-mobile-cta-bar`). Discounts need a real store discount behind them; never invent a code
632
- (`commerce-bundle-builder`, `commerce-cart-cross-sell`). Facet values are computed from products
633
- (`commerce-collection-header-facets`). Typed review scores and ratings go stale; prefer a
634
- connected source (`review-summary`, `conversion-trust-badges`). Menus and plan prices are numbers
635
- without symbols (`restaurant-menu`, `pricing-tiers-billing-toggle`).
636
- Storefront rules: [shopify-headless.md](shopify-headless.md).
637
-
638
- ### personalization
639
-
640
- Model personalization only with evidence: hidden variants in the DOM, an experiment tool's
641
- config, or URLs you were given per audience (`hero-personalized`, `personalized-hero`). The
642
- version shown without parameters is the complete default; each audience stores only the fields
643
- that differ, and order decides when two match. Never import third-party experiment ids as
644
- content. Crawlers and the page title see the default. A test that rewrites more than one section
645
- is a page-level experiment. Audience targeting also shapes promo and cart offers
646
- (`commerce-promo-countdown`, `commerce-cart-cross-sell`, `pricing-annual-savings-banner`,
647
- `conversion-exit-intent-popup`, `conversion-quiz-product-finder`). Experiments on variants:
648
- [content-experiments.md](content-experiments.md).
649
-
650
- ### validation
651
-
652
- Store values in their real type: numbers as value plus prefix and suffix (count-up safe, "1.2k"
653
- stays 1.2 and "k"; `stats-band`, `hero-stats`), datetimes with an offset (`hero-event`,
654
- `announcement-countdown`), dates with their precision (`timeline-company-history`), times as
655
- 24-hour HH:MM (`commerce-store-locator`). Required fields exist for a reason: a certification's
656
- expiry (`awards-certifications`), a comparison's last-checked date
657
- (`comparison-vs-competitors`), a migration note on a breaking change
658
- (`changelog-nested-validation`). Cross-field rules (end after start, minimum under maximum,
659
- weights summing to 100) cannot be declared; check them at import and flag them. Server checks:
660
- [write-limits.md](write-limits.md).
661
-
662
- ### a11y
663
-
664
- Every visual-only element gets text: logo names (`logo-cloud`), hotspot labels
665
- (`commerce-shop-the-look`), iframe titles (`safe-embed`), email labels
666
- (`conversion-newsletter-consent`), carousel names (`hero-slider`), words in yes or no cells
667
- (`comparison-vs-competitors`). Images are decorative (empty alt), informative or complex with a
668
- long description (`a11y-critical-media-form`, `parallax-image-overlay`). Captions and a transcript
669
- are separate things; model both, and leave captions empty rather than auto-generate them
670
- (`hero-video-fullbleed`). A map keeps its list (`stockist-map`, `locations-directory`). Focus
671
- trap, Esc and focus return, pause on hover and reduced motion are the renderer's, never toggles
672
- (`overlays-with-triggers`, `tabs-with-media`). Audit rules: [accessibility.md](accessibility.md).
673
-
674
- ### seo-structured
675
-
676
- Structured data is emitted by the platform from content; never model meta fields, JSON-LD fields
677
- or an SEO group (playbook §5b, [seo.md](seo.md)). Fill the content it reads instead (dates, prices
678
- by reference, hours as rows, geopoints) and verify the output matches the source's markup
679
- (`breadcrumb-config`, `footer-mega-locations`, `recipe`, `job-posting`, `location-page`). Emit
680
- only what is eligible: no Review markup for your own testimonials (`testimonial-grid`), no
681
- AggregateRating without a product subject (`review-summary`), no HowTo for steps
682
- (`steps-how-it-works`), one FAQPage per URL (`pricing-faq`), award markup for awards only
683
- (`awards-certifications`), a table's ItemList only when it is the page's offer
684
- (`table-comparison`). Personalized copy is never the indexed version.
685
-
686
- ### responsive-layout
687
-
688
- Read the computed layout at desktop, tablet and phone widths rather than parsing CSS
689
- (`responsive-layout-settings`). Store per-breakpoint values as settings, with tablet inheriting
690
- desktop unless it differs; spacing maps to tokens, never pixels. Phones stack, carousel or grid;
691
- capture a reorder (`mobileOrder`) rather than hiding content, because hidden content is hidden
692
- from mobile-first indexing too. Visibility per breakpoint and per-cell spans live in settings
693
- (`sticky-mobile-cta-bar`, `bento-feature-showcase`, `grid-cell-layout`). Production today: a
694
- slot item's `style.hideOn` hides it at a breakpoint ([components.md](components.md)).
695
-
696
- ## Gaps the kit names
697
-
698
- Pitfalls marked SCHEMA GAP are rules the schema language cannot express yet. Check them yourself
699
- and flag what you cannot:
700
-
701
- - Cross-field and cross-item rules: end after start, min under max, weights to 100, compare-at
702
- above price (`hero-event`, `commerce-product-detail`, `personalized-hero`).
703
- - Options or references drawn from a sibling field: a marker's category from the categories list,
704
- a default sort column from the table's columns (`stockist-map`, `data-table-specs`).
705
- - References inside one entry: a quiz branch or a plan's "Everything in" naming another item by a
706
- free string, which a rename breaks silently (`conversion-quiz-product-finder`,
707
- `pricing-tiers-billing-toggle`).
708
- - Conditions that read a parent, chain, or combine with AND or OR (`conditional-source-matrix`).
709
- - Enforced uniqueness of keys and ids (`overlays-with-triggers`, `changelog-nested-validation`).
710
- - Formula, responsive, token-reference, time-of-day and address types (`roi-calculator`,
711
- `responsive-layout-settings`, `section-theme-tokens`, `commerce-store-locator`, `stockist-map`).
712
- - What a rich-text field allows, and typed annotations (`rich-text-embedded-components`).
1
+ ---
2
+ title: "Kit scenarios"
3
+ description: "Which component-kit entry fits a section and what to do for it: all 14 kit categories and all 20 edge tags, each with when to pick it, the field shape, the pitfalls and real kit entry ids, plus what a stored component cannot carry."
4
+ ---
5
+
6
+ # Kit scenarios: pick the closest kit entry, then model it
7
+
8
+ The component kit is 149 worked section patterns (142 kit entries in 14 categories, plus 7 core
9
+ examples). Each entry carries notes written for a converting agent: what the section is, the HTML
10
+ that gives it away, the mapping to fields, and the mistakes to avoid. The full list is the MCP
11
+ resource `bettercms://kit` (an index: categories, tags and one line per entry, each with the URI
12
+ of its category and of the entry itself) and the generated reference
13
+ [kit-catalog.md](kit-catalog.md). This file is the hand-written chooser that points into it.
14
+
15
+ ## Contents
16
+
17
+ - [How to use the kit](#how-to-use-the-kit)
18
+ - [What a stored component cannot carry](#what-a-stored-component-cannot-carry)
19
+ - [Categories](#categories)
20
+ - [Edge tags](#edge-tags)
21
+ - [Gaps the kit names](#gaps-the-kit-names)
22
+
23
+ ## How to use the kit
24
+
25
+ 1. **Name the band's category** from the chooser below (or detection.md §7). The category decides
26
+ where the section lives: chrome goes in the Global Layout, a record type becomes a collection,
27
+ everything else is a section component or page fields.
28
+ 2. **Pick the closest entry.** Inside the category, match the strongest HTML signal; the chooser
29
+ lists what separates each entry from its neighbours. Then read that entry's own resource from
30
+ the index: its `mcp` note is the mapping, its `pitfalls` are the review list, its `htmlSignals`
31
+ are what to look for, and its `component` is the props list the kit converts to (step 4).
32
+ 3. **Check its edge tags** against the [edge tag rules](#edge-tags). A band that matches no entry
33
+ still has tags (a list of cards is `repeater`; a price is `commerce`), and the tag rules hold
34
+ for it.
35
+ 4. **Never send an entry's props to `create_component` as they are.** The catalogue's props have
36
+ no `target`; every prop you declare needs one, aimed at the leaf your own `blockJson` renders
37
+ for it (schema playbook §3, [components.md](components.md) §1). Write the `blockJson` first.
38
+ 5. **Copy the shape, never the sample copy.** The entries use a fictional cookware brand
39
+ (Ember, Hearth & Ash, Sierra camp). Write the customer's words.
40
+ 6. **Respect the entry's limits.** A pitfall that starts "SCHEMA GAP" is something the platform
41
+ cannot check; do the check yourself at import time and flag what you cannot.
42
+
43
+ ## What a stored component cannot carry
44
+
45
+ Each catalogue entry lists `notCarried`: kit features the stored component drops. Do the
46
+ production equivalent instead of losing the intent.
47
+
48
+ | notCarried | What to do instead |
49
+ |---|---|
50
+ | `variants` | Each layout variant is its own component sharing one `sectionType`, same prop keys ([components.md](components.md)) |
51
+ | `kind:global` | The section is chrome: the Global Layout (`get_layout`, `update_layout`), never a page section |
52
+ | `kind:entry (props)` | The entry is a record: a collection model and entries, not a component |
53
+ | `slot allow` | Name the allowed child components with the slot prop's `config.componentIds` |
54
+ | `reference target` | Point the reference at a collection with `config.contentModelId` |
55
+ | `ai`, `category`, `item openMode`, `item settings`, `group (nested)`, `fieldset (derived)`, `document preview path`, `segmented>4`, `validation.items`, `itemNoun (derived)` | Editor presentation only; arrange the dock with [editor-chrome.md](editor-chrome.md) where it matters |
56
+
57
+ ## Categories
58
+
59
+ Each category: when it applies, the shape, the traps, and a chooser of every entry in it.
60
+
61
+ ### Navigation & chrome
62
+
63
+ **When:** the band is the same on every page (header, footer, announcement bar, cookie banner,
64
+ docs sidebar), or it is a per-template bar such as a sticky mobile buy bar.
65
+
66
+ **Shape:** one global, created once and placed by the layout. Links are rows of `{label, link}`;
67
+ a call to action is one button; design (sticky, transparent, tone) is a setting, never copy.
68
+ Breadcrumbs and the sticky mobile bar are not globals: the first is per page and mostly automatic,
69
+ the second sits on the template it appears on.
70
+
71
+ **Traps:** a link field for the logo (it always goes home); copying a cart count, prices or the
72
+ customer name (session state, `nav-ecommerce-header`); free-text opening hours
73
+ (`footer-mega-locations`); a countdown stored as text (`announcement-countdown`); pre-ticked
74
+ optional consent categories (`cookie-consent`); the hamburger markup (the renderer's job,
75
+ `nav-simple-cta`).
76
+
77
+ | Entry | Pick it when |
78
+ |---|---|
79
+ | `nav-simple-cta` | Logo, a short link row, one button, maybe a tel: link |
80
+ | `nav-announcement-utility` | Stacked rows: a one-sentence strip, a utility row, the main nav |
81
+ | `nav-mega-featured-promo` | Top items open panels with headed columns, product cards and a promo tile |
82
+ | `nav-docs-sidebar` | The same nav tree on every /docs page; auto from the page tree when order matches the URLs |
83
+ | `nav-ecommerce-header` | Search, account and cart controls; collection links |
84
+ | `nav-locale-header` | Language and currency switchers; the same header in every locale |
85
+ | `sticky-mobile-cta-bar` | position fixed at the bottom, one button, shown on phones |
86
+ | `announcement-countdown` | A separate dismissible strip with a countdown or rotating messages |
87
+ | `cookie-consent` | A consent banner with category toggles (unless a vendor CMP script owns it: report it) |
88
+ | `breadcrumb-config` | A crumb trail; auto when every crumb is a prefix of the current path |
89
+ | `footer-multi-column` | Headed link groups, a newsletter form, socials, payment badges |
90
+ | `footer-minimal` | No headings and under about eight links |
91
+ | `footer-mega-locations` | Locations with addresses, hours and map links, app badges |
92
+
93
+ ### Heroes
94
+
95
+ **When:** the page's opening band: one `h1`, a lede, one or two buttons, a media column or a
96
+ background.
97
+
98
+ **Shape:** plain strings for eyebrow, heading and body; buttons as button units; one media slot
99
+ whose type (image, video, gallery) is a select revealing its own fields (`hero-split-media`).
100
+ Live data comes through a reference (a product, a testimonial), forms through a form reference,
101
+ dates as datetimes with an offset.
102
+
103
+ **Traps:** price, SKU or stock copied into the hero (`hero-product`); a count-up read mid-animation
104
+ as "0" (`hero-stats`); QR or store-badge images uploaded instead of their links
105
+ (`hero-app-download`); raw CSS gradients stored as strings (`hero-centered-gradient`); every
106
+ audience holding a full copy of the hero (`hero-personalized`).
107
+
108
+ | Entry | Pick it when |
109
+ |---|---|
110
+ | `hero-split-media` | Text column plus one media column (image, video or a few images) |
111
+ | `hero-centered-gradient` | Centered `h1`, an optional pill link, a gradient background, a screenshot |
112
+ | `hero-video-fullbleed` | A background `<video>` behind the text; needs captions, poster and a text alternative |
113
+ | `hero-slider` | Several full-width slides from a carousel library |
114
+ | `hero-product` | A product form at the top: pickers, add to cart |
115
+ | `hero-search` | A search form is the main control |
116
+ | `hero-lead-form` | A hero beside a form of a few inputs |
117
+ | `hero-app-download` | App store badges, maybe a QR code and device screens |
118
+ | `hero-stats` | Two to four big numerals in the hero |
119
+ | `hero-logo-wall` | A strip of customer logos under the hero copy |
120
+ | `hero-split-testimonial` | A quote card beside or over the hero image |
121
+ | `hero-event` | A date, a place and a ticket button |
122
+ | `hero-personalized` | Hidden variants in the DOM or different copy per UTM or region |
123
+
124
+ ### Features & storytelling
125
+
126
+ **When:** bands that explain the product: grids of features, rows of text and media, steps,
127
+ timelines, tabs, comparisons, stats, a manifesto.
128
+
129
+ **Shape:** a list of same-shaped items inside one band. Column count, side and layout direction
130
+ are variants. Media per item is a select revealing image or video. Numbers are value, prefix and
131
+ suffix; dates carry their precision.
132
+
133
+ **Traps:** a free `columns` number (`feature-icon-grid`); a stored step number
134
+ (`steps-how-it-works`); a `side` on every row, which breaks the zigzag on reorder
135
+ (`feature-alternating-rows`); a bare year in a date field (`timeline-company-history`); FAQ
136
+ markup on rows that are not questions (`accordion-feature-image-swap`); comparative claims with
137
+ no date (`comparison-vs-competitors`).
138
+
139
+ | Entry | Pick it when |
140
+ |---|---|
141
+ | `feature-icon-grid` | Same-shaped cards: small icon, title, text, optional link |
142
+ | `feature-alternating-rows` | Text and media rows that flip sides |
143
+ | `bento-feature-showcase` | Grid cells with different spans and different inner shapes |
144
+ | `steps-how-it-works` | Numbered steps, maybe a connector line |
145
+ | `timeline-company-history` | Dated milestones on a line |
146
+ | `tabs-with-media` | Same-shaped tab panels with an image or video each (different shapes: core `tabs`) |
147
+ | `accordion-feature-image-swap` | Accordion rows with one image per row |
148
+ | `comparison-vs-competitors` | Brand columns with check and cross cells |
149
+ | `stats-band` | A row of big numbers with labels and footnoted sources |
150
+ | `manifesto-pull-quote` | One large quote, or several short display lines |
151
+ | `problem-solution` | Two lists, problems and fixes, paired |
152
+ | `integrations-grid` | Logo tiles linking to /integrations pages, filter chips |
153
+
154
+ ### Social proof
155
+
156
+ **When:** testimonials, video stories, a wall of social posts, case study cards, logos, press,
157
+ awards, a review summary.
158
+
159
+ **Shape:** testimonials, case studies and reviews are collection entries the section references,
160
+ by pick or by query (tags, limit, minimum rating). Logos are rows of name plus file. Awards carry
161
+ a kind that changes what is required (certificate id and expiry for certifications).
162
+
163
+ **Traps:** inlined quotes that drift from the approved wording (`testimonial-grid`); a logo named
164
+ "logo" (`logo-cloud`); marquee duplicates imported twice (`logo-cloud`); expired certifications
165
+ shown as current, or "HIPAA certified" (`awards-certifications`); self-serving Review or
166
+ AggregateRating markup (`testimonial-grid`, `review-summary`); hosted video without captions
167
+ (`testimonial-video-slider`).
168
+
169
+ | Entry | Pick it when |
170
+ |---|---|
171
+ | `testimonial-grid` | Three or more quote cards with name and role |
172
+ | `testimonial-video-slider` | Slides that are videos or quotes |
173
+ | `wall-of-love` | Mixed social posts, store reviews and photo posts |
174
+ | `case-study-cards` | Customer cards linking to story pages with a metric |
175
+ | `logo-cloud` | Logos only, no quotes |
176
+ | `press-mentions` | Outlet logos with quotes linking to articles |
177
+ | `awards-certifications` | Badges: awards, certifications, memberships |
178
+ | `review-summary` | An average, a star distribution, "based on N reviews" |
179
+
180
+ ### Media & galleries
181
+
182
+ **When:** photo grids, filtered galleries, video, before and after, hotspots, 3D or Lottie, mixed
183
+ carousels, a full-bleed image with overlay text, a social photo feed.
184
+
185
+ **Shape:** image rows with alt, caption and a separate credit; per-breakpoint columns as design;
186
+ positions as percentages; the video source a select between a link and a hosted file.
187
+
188
+ **Traps:** the thumbnail imported instead of the full file (`masonry-gallery`); credits pasted
189
+ into captions (`masonry-gallery`); pixel hotspot positions (`image-hotspots`); a 40 MB model or
190
+ an un-vetted iframe host (`interactive-3d-lottie-embed`); cloned slides (`mixed-media-carousel`);
191
+ UGC widget scripts and photo rights assumed (`ugc-social-feed`).
192
+
193
+ | Entry | Pick it when |
194
+ |---|---|
195
+ | `masonry-gallery` | Many photos in CSS columns or masonry, a lightbox |
196
+ | `lightbox-gallery-filters` | Filter buttons over a photo grid |
197
+ | `video-section` | A YouTube, Vimeo or hosted video with captions and chapters |
198
+ | `before-after-slider` | Two stacked images, one clipped, a handle |
199
+ | `image-hotspots` | Positioned pins on an image opening a product or a note |
200
+ | `interactive-3d-lottie-embed` | `<model-viewer>`, a Lottie player or an allow-listed 3D iframe |
201
+ | `mixed-media-carousel` | Slides of different kinds: photo, video, quote, product |
202
+ | `parallax-image-overlay` | One full-bleed image with text on top |
203
+ | `ugc-social-feed` | A grid of social photos from an account, a hashtag or hand-picked |
204
+
205
+ ### Commerce
206
+
207
+ **When:** product grids, a product page, a featured product, collection filters, bundles, cart
208
+ offers, a promo with a code, shop the look, size guides, store locators, product comparisons.
209
+
210
+ **Shape:** products are referenced (a Products entry linked to the store product); a grid stores
211
+ a query or picked references; a product page is a Products entry, not a block; size guides and
212
+ comparisons use the `table` type keyed by column.
213
+
214
+ **Traps:** prices, compare-at prices, options, variants or stock copied into content
215
+ (`commerce-product-detail`, `commerce-product-grid-query`); facet values imported as content
216
+ (`commerce-collection-header-facets`); an invented discount code (`commerce-bundle-builder`); a
217
+ deadline without an offset (`commerce-promo-countdown`); inches converted to centimetres
218
+ (`commerce-size-guide`); map API keys imported (`commerce-store-locator`).
219
+
220
+ | Entry | Pick it when |
221
+ |---|---|
222
+ | `commerce-product-grid-query` | Product cards sharing a rule (one collection, "New") or picked |
223
+ | `commerce-product-detail` | The product page itself: gallery, options, specs, policies |
224
+ | `commerce-featured-product` | One product spotlighted, maybe with add to cart |
225
+ | `commerce-collection-header-facets` | A collection header with filter groups and sort |
226
+ | `commerce-bundle-builder` | Choose N products, tiered or fixed bundle price |
227
+ | `commerce-cart-cross-sell` | Upsell offers in the cart, a free-shipping threshold |
228
+ | `commerce-promo-countdown` | A promo strip with a code and a timer |
229
+ | `commerce-shop-the-look` | Looks: images with product dots |
230
+ | `commerce-size-guide` | Size tables, maybe a unit toggle, measuring steps |
231
+ | `commerce-store-locator` | A map beside an address list, hours, holiday exceptions |
232
+ | `commerce-product-comparison` | Two to four products side by side |
233
+
234
+ ### Pricing & plans
235
+
236
+ **When:** plan cards with a billing switch, usage calculators, feature matrices, a single plan
237
+ with add-ons, enterprise contact, pricing FAQs, a pay-yearly banner.
238
+
239
+ **Shape:** both cycle prices written per plan (never derived by a percentage); per-seat, free and
240
+ custom pricing as a select; "Everything in X, plus" as inheritance with only the new bullets; the
241
+ matrix as a `table` keyed by plan key; payment price ids developer-locked.
242
+
243
+ **Traps:** a "Save 20%" badge that is not true for every plan (`pricing-tiers-billing-toggle`);
244
+ a formula stored as a string (`pricing-usage-calculator`); pre-checked paid add-ons
245
+ (`pricing-single-plan-addons`); contact fields inlined instead of a form reference
246
+ (`pricing-enterprise-card`); two FAQPage blocks on one URL (`pricing-faq`); a saving not computed
247
+ from real prices (`pricing-annual-savings-banner`).
248
+
249
+ | Entry | Pick it when |
250
+ |---|---|
251
+ | `pricing-tiers-billing-toggle` | Plan cards with a monthly or yearly switch |
252
+ | `pricing-usage-calculator` | Range sliders and a live total from graduated rates |
253
+ | `pricing-feature-matrix` | A large table of plans against features |
254
+ | `pricing-single-plan-addons` | One plan plus optional extras with units |
255
+ | `pricing-enterprise-card` | A contact-sales card: form, calendar link or plain link |
256
+ | `pricing-faq` | Grouped questions on the pricing page, maybe price tokens in answers |
257
+ | `pricing-annual-savings-banner` | A banner urging monthly payers to switch |
258
+
259
+ ### Conversion & forms
260
+
261
+ **When:** CTA bands, newsletter sign-up, lead capture, multi-step forms, booking embeds, waitlists,
262
+ contact sections, quizzes, popups, trust badges.
263
+
264
+ **Shape:** a section holds presentation and a Form reference; inputs belong to the Form document
265
+ ([forms-and-interactions.md](forms-and-interactions.md)). Success behaviour (message, redirect,
266
+ download) is a select. Booking widgets store the provider and its id only.
267
+
268
+ **Traps:** a per-section copy of the inputs, which forks submissions (`conversion-lead-capture`);
269
+ placeholder-only labels (`conversion-newsletter-consent`); a pre-ticked consent box
270
+ (`conversion-newsletter-consent`); a widget script stored as HTML (`conversion-booking-embed`); a
271
+ static "join 12,000 others" number (`conversion-waitlist-referral`); guessed quiz weights
272
+ (`conversion-quiz-product-finder`); a popup on checkout or full-screen on phones
273
+ (`conversion-exit-intent-popup`); typed review scores (`conversion-trust-badges`).
274
+
275
+ | Entry | Pick it when |
276
+ |---|---|
277
+ | `conversion-cta-band` | Heading, one sentence, one or two buttons, no inputs |
278
+ | `conversion-newsletter-consent` | One email input with consent |
279
+ | `conversion-lead-capture` | Copy beside a multi-field form with a success state |
280
+ | `conversion-multi-step-form` | Fieldsets with Next and Back, branching |
281
+ | `conversion-booking-embed` | Resy, OpenTable, Tock or Cal widget |
282
+ | `conversion-waitlist-referral` | A launch waitlist with referral rewards |
283
+ | `conversion-contact-section` | Email, phone and chat channels, a form, a map |
284
+ | `conversion-quiz-product-finder` | Questions that end in product recommendations |
285
+ | `conversion-exit-intent-popup` | A dialog on exit, delay, scroll or click (one global per dialog) |
286
+ | `conversion-trust-badges` | A row of guarantees, shipping, certifications, payment icons |
287
+
288
+ ### Listings & feeds
289
+
290
+ **When:** blog indexes, category filter bars, related posts, resource libraries, podcast episode
291
+ lists, changelogs, roadmaps, search results.
292
+
293
+ **Shape:** the section stores a query against a collection (filters, sort, page size,
294
+ pagination), with pinned or featured picks as references. Items are entries imported first.
295
+ Related posts go on the post template, not on each post.
296
+
297
+ **Traps:** nine visible cards frozen into an inline list (`blog-index`); typed chip labels
298
+ (`category-filter-bar`); picks on a template, which apply to every post (`related-posts`); facets
299
+ on non-indexed fields (`resource-library`); an embed iframe instead of the RSS audio
300
+ (`podcast-episode-list`); free-text change badges (`changelog`); editable vote counts
301
+ (`public-roadmap`); search results imported or a noindex field added (`search-results-page`).
302
+
303
+ | Entry | Pick it when |
304
+ |---|---|
305
+ | `blog-index` | Article cards with dates and a shared href prefix |
306
+ | `category-filter-bar` | Topic chips that filter a list or navigate |
307
+ | `related-posts` | Two to four cards after an article |
308
+ | `resource-library` | Typed resources with facet filters and gated downloads |
309
+ | `podcast-episode-list` | Show details, platform links and an episode list |
310
+ | `changelog` | Version or date headings with typed changes |
311
+ | `public-roadmap` | Stage columns of items, votes, a suggestion form |
312
+ | `search-results-page` | The /search page: sources, pins, empty state |
313
+
314
+ ### Long-form & docs
315
+
316
+ **When:** a page whose main content is one body: articles, docs, knowledge-base answers, recipes,
317
+ legal policies, case studies, podcast episodes; or a block inside such a page (author box, code
318
+ tabs, glossary, curriculum).
319
+
320
+ **Shape:** most entries here are collection entries, not components (their notCarried says
321
+ `kind:entry`): title, slug, dates, references to authors and topics, and ONE rich-text body with
322
+ islands for callouts, tables, code, images, video and component instances
323
+ ([portable-text.md](portable-text.md)). Structured parts (ingredients, sections and clauses,
324
+ modules and lessons, chapters) are lists, not prose.
325
+
326
+ **Traps:** an `h1` in the body (`rich-blog-post`); the author box copied instead of referenced
327
+ (`rich-blog-post`, `author-bio`); the on-page table of contents imported (`docs-page`); hard-coded
328
+ prev and next (`docs-page`); highlighted code `<span>` soup (`code-tabs`); letter headings stored
329
+ as terms (`glossary`); ingredient lines as plain strings (`recipe`); typed clause numbers and
330
+ changed anchors (`legal-policy`); durations as text (`course-curriculum`); the episode embed
331
+ instead of the audio file (`podcast-episode`).
332
+
333
+ | Entry | Pick it when |
334
+ |---|---|
335
+ | `rich-blog-post` | An article with a byline, cover and rich body (entry) |
336
+ | `author-bio` | An author box under posts (on the template) |
337
+ | `docs-page` | A docs article with sidebar position, admonitions and pager (entry) |
338
+ | `code-tabs` | One example in several languages, synced tabs |
339
+ | `kb-article` | A help answer with "Was this helpful?" (entry) |
340
+ | `glossary` | Terms and definitions with a letter index |
341
+ | `recipe` | Ingredients, steps, times and nutrition (entry) |
342
+ | `course-curriculum` | Modules of lessons with types and free previews |
343
+ | `legal-policy` | Numbered sections and clauses, effective date, versions (entry) |
344
+ | `case-study` | Customer facts, metrics, challenge, solution, results (entry) |
345
+ | `podcast-episode` | One episode: audio, notes, chapters, transcript (entry) |
346
+
347
+ ### Company & people
348
+
349
+ **When:** team grids, leadership bios, values and culture, careers, job postings, about and
350
+ timeline, press kits.
351
+
352
+ **Shape:** people are People entries referenced from the section (by query or pick), with
353
+ per-placement overrides for title or bio. Jobs are Jobs entries or synced from a hiring system.
354
+ Asset groups in a press kit are lists of assets with formats.
355
+
356
+ **Traps:** people retyped per page (`team-grid`); the CEO duplicated for the board group
357
+ (`leadership-bios`); job rows copied into the careers section (`careers-list`); salary minimum
358
+ above maximum, which the schema cannot catch (`job-posting`); closed roles deleted instead of
359
+ closed (`job-posting`); year ranges stored as strings (`about-timeline`); photos without credit
360
+ (`press-kit`).
361
+
362
+ | Entry | Pick it when |
363
+ |---|---|
364
+ | `team-grid` | Headshots with name and role, maybe department tabs |
365
+ | `leadership-bios` | Leadership, board and advisor groups with bio modals |
366
+ | `values-culture` | Values with examples, perks, a culture photo wall |
367
+ | `careers-list` | Open roles from the site or a hiring system |
368
+ | `job-posting` | One job page (entry) |
369
+ | `about-timeline` | Mission, story, founders, milestones |
370
+ | `press-kit` | Downloadable logos and photos, boilerplate, facts, coverage |
371
+
372
+ ### Events & places
373
+
374
+ **When:** events lists, conference pages, webinar registration, location directories, restaurant
375
+ menus, single location pages.
376
+
377
+ **Shape:** events and locations are entries; a list stores a query and splits upcoming from past
378
+ by computing it. Datetimes carry an offset or a separate zone. Opening hours are rows per day
379
+ (closed flag, one or more shifts), with special-hours rows for holidays. Menus nest menus,
380
+ sections, items and price variants.
381
+
382
+ **Traps:** past events flagged "upcoming" by hand (`events-list`); agenda times in the visitor's
383
+ zone (`event-detail`); speakers typed into session titles (`event-detail`); a separate replay page
384
+ instead of switching the webinar stage (`webinar-registration`); hours as one text line
385
+ (`locations-directory`); overnight closing read as earlier than opening (`location-page`);
386
+ "$18 / $32" in one price (`restaurant-menu`); allergen data no owner signed off
387
+ (`restaurant-menu`).
388
+
389
+ | Entry | Pick it when |
390
+ |---|---|
391
+ | `events-list` | Upcoming and past events as list, cards, calendar or map |
392
+ | `event-detail` | One conference: days, sessions, speakers, venue, ticket tiers (entry) |
393
+ | `webinar-registration` | One webinar across upcoming, live and on demand |
394
+ | `locations-directory` | Map plus list of many locations with hours and services |
395
+ | `restaurant-menu` | Menus, sections, dishes, prices, dietary tags |
396
+ | `location-page` | One location's page: address, hours, booking (entry) |
397
+
398
+ ### Data & interactive
399
+
400
+ **When:** spec tables, comparison tables, charts, calculators, product configurators, maps with
401
+ markers, product comparison sliders, countdowns and live counters, polls, embeds, code snippets.
402
+
403
+ **Shape:** tables use the `table` type: typed, keyed columns, a caption, row headers, row groups,
404
+ footnotes; more than about 60 rows comes from a CSV or a collection (`data-table-specs`). Charts
405
+ store their data as series and points, plus a required one-sentence summary
406
+ (`chart-attrition`). Calculators store typed inputs and outputs whose formulas name input keys
407
+ (`roi-calculator`). Maps store markers with geopoints and keep a list beside the map
408
+ (`stockist-map`). Embeds store a URL or provider id with a required title (`safe-embed`).
409
+
410
+ **Traps:** a rasterised chart, or values read off bar heights without review
411
+ (`chart-attrition`); an approximated formula, which should stay a code component with copy
412
+ fields only (`roi-calculator`); rules by index instead of keys (`product-configurator`); product
413
+ names typed into a comparison (`product-compare-slider`); a local time without a zone
414
+ (`countdown-live-counter`); a fabricated live counter or a per-visitor countdown
415
+ (`countdown-live-counter`); vote counts imported, or a survey nested in a poll (`reader-poll`);
416
+ tracking pixels or chat widgets migrated as embeds (`safe-embed`); highlighted HTML stored as
417
+ code (`code-snippet-tabs`).
418
+
419
+ | Entry | Pick it when |
420
+ |---|---|
421
+ | `data-table-specs` | A spec table with typed columns, maybe from a CSV or collection |
422
+ | `table-comparison` | The reference table: typed columns, groups, footnotes, notes |
423
+ | `chart-attrition` | A chart with series, axes and annotations |
424
+ | `roi-calculator` | Inputs that feed computed outputs |
425
+ | `product-configurator` | Steps of options with price deltas and availability rules |
426
+ | `stockist-map` | A map with categorised markers and popups |
427
+ | `product-compare-slider` | Products compared by attributes, swipe on phones |
428
+ | `countdown-live-counter` | A countdown to a launch, maybe a live counter |
429
+ | `reader-poll` | One question with options, a scale or an open answer |
430
+ | `safe-embed` | Any iframe, oEmbed URL or script widget |
431
+ | `code-snippet-tabs` | A code block with tabs per language or package manager |
432
+
433
+ ### Advanced patterns
434
+
435
+ **When:** the shape itself is the hard part: deep nesting, mixed item types, conditional sources,
436
+ globals with overrides, translations, personalization, responsive settings, grids with per-cell
437
+ layout, reference webs, nested validation, rich text with components, query listings, overlays,
438
+ scrollytelling, accessibility-critical media, section theme tokens.
439
+
440
+ **Shape and traps:** each entry is the long form of one [edge tag](#edge-tags); read the tag rule
441
+ first, then the entry. Most of their pitfalls are SCHEMA GAPs: listed in
442
+ [Gaps the kit names](#gaps-the-kit-names).
443
+
444
+ | Entry | Pick it when | Tag rule |
445
+ |---|---|---|
446
+ | `page-builder-six-deep` | Tabs holding columns holding accordions holding cards | deep-nesting |
447
+ | `polymorphic-modules` | An article built from mixed module types | polymorphic-array |
448
+ | `conditional-source-matrix` | One section whose items come manually, from a collection, a query or an external service | conditional |
449
+ | `global-announcement-overrides` | A bar on most pages whose copy differs on a few | global-synced |
450
+ | `localized-section` | Copy in several locales, right-to-left included | localized |
451
+ | `personalized-hero` | Audience segments and an A/B split on one hero | personalization |
452
+ | `responsive-layout-settings` | Columns, gap and spacing that differ per breakpoint | responsive-layout |
453
+ | `grid-cell-layout` | A grid whose cells set their own span, surface and alignment | slots |
454
+ | `references-web` | Products, authors, recipes and pages linked, pinned and auto-filled | references |
455
+ | `changelog-nested-validation` | Releases of changes with semver and issue-key patterns | validation |
456
+ | `rich-text-embedded-components` | A body with component islands, footnotes and glossary terms | rich-text |
457
+ | `dynamic-query-listing` | A listing defined by filters, sort, limit and pins | dynamic-query |
458
+ | `overlays-with-triggers` | Modals, drawers and popovers opened by triggers | slots |
459
+ | `scrollytelling` | Sticky media that changes as text steps scroll past | media-heavy |
460
+ | `a11y-critical-media-form` | Video with captions and transcript, image roles, a labelled form | a11y |
461
+ | `section-theme-tokens` | A dark or brand-coloured section inside a light page | variants |
462
+
463
+ ### Core examples
464
+
465
+ Seven original examples (`core-hero`, `core-faq`, `core-pricing`, `core-tabs`, `core-bento`,
466
+ `core-megaMenu`, `core-journalStory`). They carry a schema and a sample value but no notes:
467
+ use them as reference shapes and take the guidance from the matching category above
468
+ (`core-bento` is worked through end to end in [worked-examples.md](worked-examples.md) §9).
469
+
470
+ ## Edge tags
471
+
472
+ An edge tag names one modelling difficulty. Every entry lists its tags; a section you build
473
+ inherits the rules of every tag it has.
474
+
475
+ ### deep-nesting
476
+
477
+ Count levels where the content changes kind, not DOM wrappers: same-shaped siblings are a list,
478
+ siblings that differ are slot children (`page-builder-six-deep`). Up to six content levels stay in
479
+ one section; past six, or past about ten children at a level, split the deepest repeated subtree
480
+ into its own block. Never make one thing both a list item and a slot. Keep the deepest tiny rows
481
+ inline (sizes in `restaurant-menu`, resources in `course-curriculum`). A sidebar deeper than four
482
+ levels is flattened and reported (`nav-docs-sidebar`). Cases: `core-megaMenu`,
483
+ `nav-mega-featured-promo`, `commerce-product-detail`, `conversion-quiz-product-finder`,
484
+ `press-kit`, `event-detail`, `location-page`.
485
+
486
+ ### repeater
487
+
488
+ Same-shaped items in one band are one list in one section, never one section per item
489
+ (`feature-alternating-rows`). Never store what order already says: a step number
490
+ (`steps-how-it-works`), clause numbering (`legal-policy`), a totals line (`course-curriculum`).
491
+ Dedupe clones a carousel or marquee adds (`hero-slider`, `logo-cloud`). Unequal paired lists are
492
+ flagged, never truncated (`problem-solution`). Long lists move to a collection: 60+ releases
493
+ (`changelog`), 300 glossary terms (`glossary`). Plain strings are a string list, not rows of one
494
+ field (`hero-split-media` points).
495
+
496
+ ### polymorphic-array
497
+
498
+ One list whose items have different shapes: each item names its type, and the editor picks the
499
+ type on add (`polymorphic-modules`). Classify each item by its dominant element: a figure is an
500
+ image item, a blockquote a quote item, a big numeral a stat item, else text
501
+ (`polymorphic-modules`, `wall-of-love`, `mixed-media-carousel`). Merge consecutive paragraphs into
502
+ one text item; one item per paragraph is the commonest bad conversion. Over about 40 items, or
503
+ mostly prose, use a rich-text body with islands instead. On a component this is a `table` prop in
504
+ types mode (playbook §5). Cases: `commerce-product-detail` gallery,
505
+ `conversion-multi-step-form` fields, `core-tabs`, `core-bento`.
506
+
507
+ ### slots
508
+
509
+ Blocks placed inside a block: the children differ in shape and the editor chooses them
510
+ (`core-tabs`, `core-bento`). Per-child layout (span, surface, alignment, order on phones) goes
511
+ in the slot's settings, never on the child's own fields, or reusing the child elsewhere carries a
512
+ meaningless span (`bento-feature-showcase`, `grid-cell-layout`). Validate spans against the
513
+ variant's column count. Regular same-shaped cells are a plain card list, not a slot
514
+ (`grid-cell-layout`). Overlays hold their own slot content, and their triggers are `#id` links
515
+ (`overlays-with-triggers`). A marketer-owned promo tile beside structured menu columns is a slot
516
+ (`nav-mega-featured-promo`). Worked example: [worked-examples.md](worked-examples.md) §9.
517
+
518
+ ### variants
519
+
520
+ Layout choices are variants of one section, never a free number or boolean the editor can break:
521
+ column count (`feature-icon-grid`), image side (`commerce-featured-product`), grid versus carousel
522
+ (`commerce-product-grid-query`), striped or compact tables (`data-table-specs`). Pick the variant
523
+ from the rendered layout. A field that only one variant uses cannot be hidden by the variant
524
+ today, so its help text says which variant reads it (`conversion-cta-band`). Theme (dark section,
525
+ accent, spacing) maps to brand tokens, never stored hex or pixels (`section-theme-tokens`).
526
+
527
+ ### conditional
528
+
529
+ A select (source, kind, mode, type) reveals only the fields of its branch: media type
530
+ (`hero-split-media`), pick versus query (`testimonial-grid`), source manual, collection, query or
531
+ external (`conditional-source-matrix`), success message, redirect or download
532
+ (`conversion-lead-capture`). Fill only the active branch: hidden values are kept and published.
533
+ A condition reads one sibling only, so a child cannot react to its parent (the quiz picture
534
+ field in `conversion-quiz-product-finder`, the swatch in `product-configurator`), and toggles do
535
+ not chain (`countdown-live-counter`). Decide the source from how the items got there, never how
536
+ they look; a snapshot of a dynamic list is never frozen into a manual one.
537
+
538
+ ### references
539
+
540
+ Link to the existing entry, product or page instead of copying its title, photo or price; store
541
+ only what differs as an override (`commerce-featured-product`, `case-study-cards`,
542
+ `leadership-bios`). Resolve every link before creating anything, and create missing targets as
543
+ entries first (`references-web`, `case-study-cards`). Match existing entries before creating (by
544
+ name plus the first 40 characters of a quote, `testimonial-grid`). Chips and filters reference a
545
+ taxonomy collection, never typed labels (`category-filter-bar`, `integrations-grid`). A
546
+ reference by id survives slug changes; a stored href does not. Picks belong to the instance, not
547
+ the template (`related-posts`).
548
+
549
+ ### dynamic-query
550
+
551
+ A listing stores the query (collection, filters, sort, limit, pagination, pins), never copies of
552
+ the cards; otherwise new items never appear (`blog-index`, `dynamic-query-listing`,
553
+ `commerce-product-grid-query`). Infer the query by comparing the listing across pages and over
554
+ time. Create the collection and import the items as entries first, then place the block. A
555
+ hand-curated list with no shared rule is a references list instead. Search, related posts and
556
+ synced job boards configure a source and import nothing (`search-results-page`, `related-posts`,
557
+ `careers-list`). An empty result hides the block; never add placeholder items.
558
+
559
+ ### global-synced
560
+
561
+ An element repeated with the same content on most pages (the kit says 80%) is one global,
562
+ created once from the most common version (`global-announcement-overrides`). Pages that differ
563
+ get an instance overriding only the differing fields; a page without it hides the instance. If
564
+ more than half the fields differ on more than a third of pages, it is a normal block. Header,
565
+ footer, announcement, cookie banner, docs sidebar and popups are globals in the kit
566
+ (`nav-simple-cta`, `footer-multi-column`, `cookie-consent`, `nav-docs-sidebar`,
567
+ `conversion-exit-intent-popup`); in BetterCMS chrome is the Global Layout. A field synced from a
568
+ global inside a normal block (store links in `hero-app-download`) is written to the global too.
569
+
570
+ ### localized
571
+
572
+ Crawl every locale and match the same section by position and structure, never by text
573
+ (`localized-section`, `nav-locale-header`). The default locale fills the fields; each other locale
574
+ carries its copy, direction (rtl for ar, he, fa, ur) and a status; a missing locale is recorded
575
+ as missing. Never translate during conversion, and flag legal text for per-locale review
576
+ (`legal-policy`). Link to the page and let locale routing resolve it, never typed /de/ URLs; never
577
+ model hreflang (`nav-locale-header`). A header that differs in structure per locale is a
578
+ per-market header, not a translation. Languages are codes, not flags. Sort term lists with the
579
+ locale's collation (`glossary`). Keep credits out of translatable captions (`masonry-gallery`).
580
+ Platform locales: [localization.md](localization.md).
581
+
582
+ ### rich-text
583
+
584
+ An article body is one rich-text field. Non-prose elements become islands: figure to image,
585
+ table to table, aside to callout, details to toggle, a known card to a component instance with
586
+ overrides (`rich-blog-post`, `rich-text-embedded-components`). Template chrome (share bar, author
587
+ box, related cards, the table of contents) is not body content. Demote an `h1` in the body.
588
+ Footnotes are keyed annotations with their text held beside the body; internal links resolve to
589
+ entries. An island used in more than one article must be a library component. Split a body only
590
+ where the layout needs it (challenge, solution, results in `case-study`). Shapes:
591
+ [portable-text.md](portable-text.md).
592
+
593
+ ### media-heavy
594
+
595
+ Import the largest source (lightbox href or the biggest srcset entry), keep real alt text, and
596
+ never "logo", "image" or "team photo" (`masonry-gallery`, `logo-cloud`, `values-culture`). Video
597
+ needs a poster (extract or generate one) and, when it has speech, captions; a missing caption
598
+ file is flagged, never invented (`hero-video-fullbleed`, `testimonial-video-slider`). Positions on
599
+ an image are percentages (`image-hotspots`, `commerce-shop-the-look`). Art-directed phone images
600
+ are their own field (`parallax-image-overlay`, `hero-slider`). Keep logos' original files and never
601
+ recolour them in content (`integrations-grid`, `press-mentions`). Sticky-panel media lives on the
602
+ step it belongs to (`scrollytelling`).
603
+
604
+ ### embed
605
+
606
+ Store the provider and its id, or the canonical URL, never the widget script or its HTML
607
+ (`conversion-booking-embed`, `safe-embed`, `ugc-social-feed`). An iframe needs a title; write one
608
+ if missing. Only allow-listed hosts go into the embed block; anything else becomes a developer
609
+ task (`interactive-3d-lottie-embed`). Tracking pixels, analytics and chat widgets are site
610
+ settings, not embeds (`safe-embed`). Never import API keys (`commerce-store-locator`,
611
+ `stockist-map`). Prefer importing the underlying data over an embed: RSS audio over a player
612
+ iframe (`podcast-episode-list`), a public roadmap's JSON over its iframe (`public-roadmap`), code
613
+ as plain text with highlighting at render (`code-tabs`, `code-snippet-tabs`).
614
+
615
+ ### form
616
+
617
+ A section references a Form document by id; it never holds the inputs (`hero-lead-form`,
618
+ `conversion-lead-capture`, `pricing-enterprise-card`). Match an existing form by its inputs or
619
+ list id before creating one, and tag a shared list instead of making one form per launch
620
+ (`conversion-waitlist-referral`). Per-placement settings are allowed: hidden attribution fields,
621
+ a submit label that differs, success behaviour. Static hidden values only; never store personal
622
+ data from the URL. A sequence of questions is a Form, not a poll (`reader-poll`). Third-party
623
+ form embeds (HubSpot, Typeform) are reported with their id. Rules:
624
+ [forms-and-interactions.md](forms-and-interactions.md).
625
+
626
+ ### commerce
627
+
628
+ The store owns price, compare-at price, SKU, stock, options, variants, cart and currency: reference
629
+ the product and never copy them (`commerce-product-detail`, `hero-product`,
630
+ `nav-ecommerce-header`). Flag any currency typed into copy. "Add to cart" is an action, not a link
631
+ (`sticky-mobile-cta-bar`). Discounts need a real store discount behind them; never invent a code
632
+ (`commerce-bundle-builder`, `commerce-cart-cross-sell`). Facet values are computed from products
633
+ (`commerce-collection-header-facets`). Typed review scores and ratings go stale; prefer a
634
+ connected source (`review-summary`, `conversion-trust-badges`). Menus and plan prices are numbers
635
+ without symbols (`restaurant-menu`, `pricing-tiers-billing-toggle`).
636
+ Storefront rules: [shopify-headless.md](shopify-headless.md).
637
+
638
+ ### personalization
639
+
640
+ Model personalization only with evidence: hidden variants in the DOM, an experiment tool's
641
+ config, or URLs you were given per audience (`hero-personalized`, `personalized-hero`). The
642
+ version shown without parameters is the complete default; each audience stores only the fields
643
+ that differ, and order decides when two match. Never import third-party experiment ids as
644
+ content. Crawlers and the page title see the default. A test that rewrites more than one section
645
+ is a page-level experiment. Audience targeting also shapes promo and cart offers
646
+ (`commerce-promo-countdown`, `commerce-cart-cross-sell`, `pricing-annual-savings-banner`,
647
+ `conversion-exit-intent-popup`, `conversion-quiz-product-finder`). Experiments on variants:
648
+ [content-experiments.md](content-experiments.md).
649
+
650
+ ### validation
651
+
652
+ Store values in their real type: numbers as value plus prefix and suffix (count-up safe, "1.2k"
653
+ stays 1.2 and "k"; `stats-band`, `hero-stats`), datetimes with an offset (`hero-event`,
654
+ `announcement-countdown`), dates with their precision (`timeline-company-history`), times as
655
+ 24-hour HH:MM (`commerce-store-locator`). Required fields exist for a reason: a certification's
656
+ expiry (`awards-certifications`), a comparison's last-checked date
657
+ (`comparison-vs-competitors`), a migration note on a breaking change
658
+ (`changelog-nested-validation`). Cross-field rules (end after start, minimum under maximum,
659
+ weights summing to 100) cannot be declared; check them at import and flag them. Server checks:
660
+ [write-limits.md](write-limits.md).
661
+
662
+ ### a11y
663
+
664
+ Every visual-only element gets text: logo names (`logo-cloud`), hotspot labels
665
+ (`commerce-shop-the-look`), iframe titles (`safe-embed`), email labels
666
+ (`conversion-newsletter-consent`), carousel names (`hero-slider`), words in yes or no cells
667
+ (`comparison-vs-competitors`). Images are decorative (empty alt), informative or complex with a
668
+ long description (`a11y-critical-media-form`, `parallax-image-overlay`). Captions and a transcript
669
+ are separate things; model both, and leave captions empty rather than auto-generate them
670
+ (`hero-video-fullbleed`). A map keeps its list (`stockist-map`, `locations-directory`). Focus
671
+ trap, Esc and focus return, pause on hover and reduced motion are the renderer's, never toggles
672
+ (`overlays-with-triggers`, `tabs-with-media`). Audit rules: [accessibility.md](accessibility.md).
673
+
674
+ ### seo-structured
675
+
676
+ Structured data is emitted by the platform from content; never model meta fields, JSON-LD fields
677
+ or an SEO group (playbook §5b, [seo.md](seo.md)). Fill the content it reads instead (dates, prices
678
+ by reference, hours as rows, geopoints) and verify the output matches the source's markup
679
+ (`breadcrumb-config`, `footer-mega-locations`, `recipe`, `job-posting`, `location-page`). Emit
680
+ only what is eligible: no Review markup for your own testimonials (`testimonial-grid`), no
681
+ AggregateRating without a product subject (`review-summary`), no HowTo for steps
682
+ (`steps-how-it-works`), one FAQPage per URL (`pricing-faq`), award markup for awards only
683
+ (`awards-certifications`), a table's ItemList only when it is the page's offer
684
+ (`table-comparison`). Personalized copy is never the indexed version.
685
+
686
+ ### responsive-layout
687
+
688
+ Read the computed layout at desktop, tablet and phone widths rather than parsing CSS
689
+ (`responsive-layout-settings`). Store per-breakpoint values as settings, with tablet inheriting
690
+ desktop unless it differs; spacing maps to tokens, never pixels. Phones stack, carousel or grid;
691
+ capture a reorder (`mobileOrder`) rather than hiding content, because hidden content is hidden
692
+ from mobile-first indexing too. Visibility per breakpoint and per-cell spans live in settings
693
+ (`sticky-mobile-cta-bar`, `bento-feature-showcase`, `grid-cell-layout`). Production today: a
694
+ slot item's `style.hideOn` hides it at a breakpoint ([components.md](components.md)).
695
+
696
+ ## Gaps the kit names
697
+
698
+ Pitfalls marked SCHEMA GAP are rules the schema language cannot express yet. Check them yourself
699
+ and flag what you cannot:
700
+
701
+ - Cross-field and cross-item rules: end after start, min under max, weights to 100, compare-at
702
+ above price (`hero-event`, `commerce-product-detail`, `personalized-hero`).
703
+ - Options or references drawn from a sibling field: a marker's category from the categories list,
704
+ a default sort column from the table's columns (`stockist-map`, `data-table-specs`).
705
+ - References inside one entry: a quiz branch or a plan's "Everything in" naming another item by a
706
+ free string, which a rename breaks silently (`conversion-quiz-product-finder`,
707
+ `pricing-tiers-billing-toggle`).
708
+ - Conditions that read a parent, chain, or combine with AND or OR (`conditional-source-matrix`).
709
+ - Enforced uniqueness of keys and ids (`overlays-with-triggers`, `changelog-nested-validation`).
710
+ - Formula, responsive, token-reference, time-of-day and address types (`roi-calculator`,
711
+ `responsive-layout-settings`, `section-theme-tokens`, `commerce-store-locator`, `stockist-map`).
712
+ - What a rich-text field allows, and typed annotations (`rich-text-embedded-components`).