> ## Documentation Index
> Fetch the complete documentation index at: https://docs.member.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Page-builder catalog

> Generated reference for the templates and section types available to the page builder.

The page-builder catalog is the single source of truth for the templates and section types available when building hub pages. Every Membership.io surface that composes pages — backend, hub, admin, and CLI — consumes this catalog.

<Info>
  `GET /api/v1/page-builder/catalog` returns the raw catalog JSON. The response `ETag` header is the catalog's content digest — send `If-None-Match: "<digest>"` to get a `304 Not Modified`, and cache by digest.

  Current `catalogVersion`: `0.22.1` · digest: `sha256:bf03f334ebc11302490979ca4b2cf552d6cfbbb0c32c6f284f810d8f637b1ace`
</Info>

## Node shape

Every node in a page tree is the same envelope. The renderer dispatches on **`kind`** — never on `type` — and reads the node's content from a **top-level `value`**, a sibling of `settings`:

```json theme={null}
{
  "id": "019f8c3a-1f4e-7b22-8c40-6d7e8f901a2b",
  "kind": "headline",
  "value": "Welcome to Member Academy",
  "settings": { "level": 1, "weight": 700 }
}
```

A **section** node is the same envelope plus a `template` from the table below and a `children` array. Putting a node's content in `settings.value` instead of `value`, or omitting a section's `template`, is accepted by the API and then silently dropped by the renderer; an unrecognized `settings.weight` (a CSS keyword such as `"bold"`) is quietly discarded rather than applied. Every node also needs an `id`.

See [Node shape](/guides/pages-page-builder#node-shape) for the full contract, including the leaf-kind settings and the `surface.background` enum.

## Templates

| Template       | Label        | Category | Section type   | Page types                                                       | Variants                                                                         | Intent                                            |
| -------------- | ------------ | -------- | -------------- | ---------------------------------------------------------------- | -------------------------------------------------------------------------------- | ------------------------------------------------- |
| `carousel`     | Carousel     | section  | `carousel`     | `homepage`, `custom`, `playlist-detail`, `file-detail`, `search` | `playlist`                                                                       | Feature carousel — one sliding feature at a time  |
| `compact`      | Scroll       | section  | `compact`      | `homepage`, `custom`, `playlist-detail`, `file-detail`, `search` | `playlist`                                                                       | Horizontal scroll of content cards                |
| `content-card` | Content Card | element  | —              | —                                                                | —                                                                                | Reusable content-item card baseline               |
| `content-grid` | Content Grid | section  | `content-grid` | `homepage`, `custom`, `playlist-detail`, `file-detail`, `search` | —                                                                                | Auto-content grid (data-bound)                    |
| `grid`         | Grid         | section  | `grid`         | `homepage`, `custom`, `playlist-detail`, `file-detail`, `search` | `playlist`                                                                       | Content grid                                      |
| `hero`         | Hero         | section  | `feature`      | `homepage`, `custom`, `playlist-detail`, `file-detail`           | `file`, `playlist`                                                               | Full-width feature/hero                           |
| `row`          | Row          | section  | `row`          | `homepage`, `custom`, `playlist-detail`, `file-detail`           | `1col`, `2eq`, `2left`, `2right`, `3eq`, `4eq`, `bound-cards`, `cta-band`, `faq` | Row of columns                                    |
| `search-bar`   | Search Bar   | section  | `search`       | `homepage`, `custom`, `search`                                   | —                                                                                | Standalone search section                         |
| `testimonials` | Testimonials | section  | `testimonials` | `homepage`, `custom`                                             | —                                                                                | Member quotes — one focused testimonial at a time |

## Node kinds

The catalog's `settingsSchema` map is the authority on what each kind accepts: 33 `kind:<nodeKind>` entries plus the shared `background`, `gradient`, `structural`, `surface` shapes, each listing typed properties with their enums, defaults, and per-field notes, split into `core` and `presentational` buckets matching the consumer profiles below. See [Node shape](/guides/pages-page-builder#node-shape) for the shapes that most often cause a silent render drop.

| Kind                | Child rules |
| ------------------- | ----------- |
| `accordion`         | many        |
| `banner`            | many        |
| `button`            | none        |
| `carousel`          | many        |
| `container`         | many        |
| `content-card`      | many        |
| `countdown`         | none        |
| `cta-slot`          | none        |
| `divider`           | none        |
| `doodle`            | none        |
| `featured-icon`     | none        |
| `field`             | none        |
| `file-attachments`  | none        |
| `file-player`       | none        |
| `grid`              | many        |
| `headline`          | none        |
| `horizontal-scroll` | many        |
| `icon`              | none        |
| `image`             | none        |
| `logo`              | none        |
| `media-slot`        | none        |
| `plan-card`         | none        |
| `plan-group`        | many        |
| `plan-price`        | none        |
| `progress-ring`     | none        |
| `quote`             | none        |
| `row`               | many        |
| `search-bar`        | none        |
| `stack`             | many        |
| `tabs`              | many        |
| `text`              | none        |
| `theme-toggle`      | none        |
| `video`             | none        |

## Section types

| Type           | Lifecycle | Anonymous-safe | Writable | Compiled from       |
| -------------- | --------- | -------------- | -------- | ------------------- |
| `calendar`     | active    | ✗              | ✗        | `calendar`          |
| `carousel`     | active    | ✗              | ✓        | `carousel`          |
| `compact`      | active    | ✗              | ✓        | `compact`           |
| `content-grid` | active    | ✗              | ✓        | `content-grid`      |
| `feature`      | active    | ✓              | ✓        | `hero`              |
| `grid`         | active    | ✗              | ✓        | `grid`              |
| `row`          | active    | ✓              | ✓        | `row`, `free-stack` |
| `search`       | active    | ✗              | ✓        | `search-bar`        |
| `testimonials` | active    | ✗              | ✓        | `testimonials`      |

*Anon-safe — whether the section type is whitelisted on the backend's sanitized pre-auth render (login / register / payment pages for logged-out visitors). It does not affect public-hub page visibility: anonymous visitors get the full published tree of any public page; only author-set access gates prune content there.*

## Settings vocabulary

`settingsSchema` is the per-`kind:<k>` settings contract for the page-builder node tree. Each kind's schema splits into two tiers: **core** settings that every consumer — the web hub, native apps, any future renderer — must honor to render the node correctly, and **presentational** settings that native apps and other constrained consumers are free to ignore. Only keys and values the mio-hub renderer actually implements are listed below; this documents what's real, not an aspirational spec. Recipes must not restate a setting's default value — omitting a key defers to the hub's built-in default for that key. The mio-hub renderer is the source of truth for this vocabulary, and it is declared directly in the catalog (MIO-2685).

### `shared:background`

Flattened TemplateSurface Background discriminated union (src/lib/page-tree/types.ts). Validation limitation: enum applies to 'type'; all variant fields are optional and the validator cannot enforce which fields are required per 'type' value. 'thumbnail' is excluded per the honesty policy (decision 4) — declared as a TODO in hub types, implemented nowhere, used by no recipe.

| Key            | Tier | Type      | Allowed values                                                                                 | Default   | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| -------------- | ---- | --------- | ---------------------------------------------------------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`         | —    | `string`  | `none`, `color`, `custom-color`, `tint`, `image`, `gradient`, `gradient-glow`, `gradient-tint` | —         | Discriminant. 'none' = no override (most common). 'color' = theme token (see 'token'). 'custom-color' = explicit hex (see 'value'). 'tint' = themed secondary tint band, no extra fields. 'image' = image URL (see 'url'/'blur'); the renderer ALWAYS composes a secondary-tint scrim over the image (MIO-492 amendment) — this is not authorable. 'gradient' = resolves via the sibling 'gradient' setting on the same surface. 'gradient-glow' = page fill + one wide accent radial-gradient ellipse rising from the top edge, masked to fade before the bottom 40% (DS hub-section-bg.tsx gradient-glow parity; 'swap' not ported). 'gradient-tint' = opaque page fill + one overlay linear-gradient at a fixed 20% color-mix strength, resolved via the same resolveGradientStops() the 'gradient' type consults, with the second stop hue/lightness-boosted after resolution (boostGradientTintEndStop()) so close-together schemes don't read as imperceptible once mixed down. |
| `token`        | —    | `string`  | `primary`, `secondary`, `muted`, `accent`, `background`                                        | —         | Used when type='color'. Theme token name, resolved via BG\_TOKEN\_CLASS (node-surface.tsx) to a Tailwind bg-\* class. An unrecognized token renders no background (dev console.warn).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `value`        | —    | `string`  | —                                                                                              | —         | Used when type='custom-color'. Explicit hex value (e.g. '#FF5722'), validated via hexToRgb before use.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `url`          | —    | `string`  | —                                                                                              | —         | Used when type='image'. Validated same-origin-relative or absolute http(s) URL; CSS-delimiter characters are rejected (isValidImageUrl, node-surface.tsx). An invalid URL renders no image layer.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `blur`         | —    | `boolean` | —                                                                                              | —         | Used when type='image'. Opt-in additional 25px backdrop blur layer on top of the always-on scrim.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `tone`         | —    | `string`  | `neutral`, `primary`                                                                           | `neutral` | Used when type='tint'. 'primary' (MIO-2896) is the brand-tinted wash — the composite bg-hub-primary-bg token at node tier; at section tier (no opaque primary-family class exists) it routes to a two-layer composite (opaque bg-background fill + flat bg-hub-primary-bg overlay) instead of falling back to neutral's class.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `fade`         | —    | `string`  | `down`                                                                                         | —         | Used when type='tint'. Unset = flat fill. 'down' renders the same tone colour as a top-to-bottom gradient into transparent (3-stop curve: boosted top stop → floor at 40% → hold).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `glowTint`     | —    | `boolean` | —                                                                                              | —         | Used when type='gradient-glow'. Tints the radial ellipse with the theme's accent colour.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `glowStrength` | —    | `number`  | `1`, `2`                                                                                       | —         | Used when type='gradient-glow'. Ellipse opacity/intensity step.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `glowSpread`   | —    | `number`  | `1`, `2`                                                                                       | —         | Used when type='gradient-glow'. Ellipse radius step.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |

### `shared:gradient`

Paired with shared:background when type='gradient' (src/lib/page-tree/types.ts Gradient, src/lib/theme/types.ts GradientType).

| Key           | Tier | Type     | Allowed values                                                                         | Default | Description                                                                                                                                      |
| ------------- | ---- | -------- | -------------------------------------------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `type`        | —    | `string` | `complementary`, `analogous`, `triadic`, `monochrome`, `split`, `warm-shift`, `custom` | —       | When not 'custom', resolves against the hub theme's primary color via computeGradient(). When 'custom', customStart/customEnd are used directly. |
| `customStart` | —    | `string` | —                                                                                      | —       | Used when type='custom'. Hex start stop, validated via hexToRgb. An invalid/missing pair falls back to 'split' against the hub theme.            |
| `customEnd`   | —    | `string` | —                                                                                      | —       | Used when type='custom'. Hex end stop, validated via hexToRgb.                                                                                   |

### `shared:surface`

TemplateSurface (src/lib/page-tree/types.ts). Nested under a kind's own 'surface' settings key (see kind:stack, kind:content-card, and the section-root template-level surface consumed via TemplateSurface). Resolved by useNodeSurface() (src/components/primitives/node-surface.tsx).

| Key               | Tier | Type      | Allowed values                                                                                                                                                               | Default | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| ----------------- | ---- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `background`      | —    | `object`  | object (background)                                                                                                                                                          | —       | See shared:background.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `gradient`        | —    | `object`  | object (gradient)                                                                                                                                                            | —       | See shared:gradient. Only consulted when background.type='gradient'.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `padding`         | —    | `string`  | `none`, `sm`, `md`, `lg`, `xl`, `section`, `gutter`, `gutter-b-mobile`, `hero-mobile-insets`, `card`, `band`, `band-b`, `band-carousel`, `band-wave`, `card-md` + any string | —       | Named spacing token (PADDING\_CLASS, node-surface.tsx) OR a raw CSS escape-hatch string (e.g. '15px', '1.25rem') — freeform:true means the enum lists the KNOWN named tokens but any other string value is also legal (rendered as an inline style), not a validation error. 'section' contributes VERTICAL inset only (py-hub-section) and is only visually complete on a maxWidth:'content' container root (see kind:container.maxWidth). 'gutter' = px-hub-section-x alone (X-only named step, for a surface whose vertical padding is a custom freeform value that can't merge with a named step). 'gutter-b-mobile' = mobile-only pb-4. 'hero-mobile-insets' = combined pt-16/pb-4 mobile pair. 'card' = the DS card padding pair (p-7, @max-\[659px]/hub:p-4). 'band' = the standard sales section band rhythm (py-\[80px], @max-\[659px]/hub:py-\[48px]). 'band-b' = bottom-only band variant (pb-\[80px], @max-\[659px]/hub:pb-\[48px]). 'band-carousel' = the tighter carousel band rhythm (py-\[56px], @max-\[659px]/hub:py-\[40px]). 'band-wave' = the wave-notch band (pt-\[112px] pb-\[128px], @max-\[659px]/hub:pt-\[80px] @max-\[659px]/hub:pb-\[96px]). 'card-md' = the 32px/24px card pair (p-8, @max-\[659px]/hub:p-6), distinct from 'md' (p-8 flat, unchanged at mobile). |
| `borderRadius`    | —    | `string`  | `none`, `sm`, `md`, `lg`, `full`, `hub-s`, `hub-m` + any string                                                                                                              | —       | Named radius token (RADIUS\_CLASS, node-surface.tsx) OR a raw CSS escape-hatch string (e.g. '12px', '50%') — freeform:true, same semantics as padding above.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `shadow`          | —    | `string`  | `none`, `sm`, `md`, `lg`, `xl`                                                                                                                                               | —       | Named shadow token (SHADOW\_CLASS, node-surface.tsx). NO raw-CSS escape hatch (legacy parity, discrete sizes only) — unlike padding/borderRadius, an unrecognized value is NOT a freeform fallback, it resolves to no shadow class.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `visibility`      | —    | `object`  | —                                                                                                                                                                            | —       | Responsive show/hide. Section-root-only concern — TemplateSurface applies it directly; useNodeSurface() (the resolver shared by non-section-root surfaces like stack/content-card) does NOT read this field at all. 'visibility' is now ALSO honoured at node tier (kind:stack's own 'surface' reads the same classes via useNodeSurface()) — no longer section-root-only.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `minHeight`       | —    | `number`  | `500`, `440`                                                                                                                                                                 | —       | Section-root-only concern (like visibility). Literal whitelist, NO raw-CSS escape hatch — a fixed DS design token, not free-form author input (MIN\_HEIGHT\_CLASS, template-surface.tsx). 500 = unconditional min-h-\[500px] (search-bar band). 440 = @\[660px]/hub:min-h-\[440px], gated to >=660px container width (testimonials carousel band, MIO-2678) — mobile takes natural height.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `margin`          | —    | `string`  | —                                                                                                                                                                            | —       | Freeform raw-CSS escape hatch (e.g. '-40px 0 0'), no named-token form. Lets a node escape its own box — padding can only add inset space. NOT authorable via any admin content field; this is a template-recipe-level layout escape hatch (straddle/edge authoring contracts), not a writable author knob.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `translate`       | —    | `string`  | —                                                                                                                                                                            | —       | Freeform raw-CSS escape hatch (e.g. '0 -50%') mapped to the CSS 'translate' property. Own-box-relative percentages (unlike vertical margin %, which resolves against containing-block width) — the vocabulary's only 'shift by half MY OWN height' mechanism. Composes with rotate/scale in the fixed CSS individual-transform-property order. Recipe-authoring-level escape hatch, not a writable author knob.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `clip`            | —    | `boolean` | —                                                                                                                                                                            | —       | Opt-in overflow-hidden clipping the surface's own in-flow children to its borderRadius — independent of the pre-existing backgroundLayer clip gate.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `elevate`         | —    | `boolean` | —                                                                                                                                                                            | —       | Opt-in relative z-10 giving the surface's own box paint-order priority over a neighboring section that is independently position:relative (e.g. via its own layered background). Required whenever 'edge' or a margin+translate straddle overlaps a positioned neighbor.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `edge`            | —    | `object`  | —                                                                                                                                                                            | —       | A repeating shape-tile mask cut into the surface's own top/bottom edge, painted with the surface's own resolved background. AUTHORING CONTRACT: setting edge.top/bottom:'wave' requires ALSO setting, on the SAME surface: (1) 'margin' — negative offset on that side equal to the 32px tile height; (2) 'padding' on that same side increased by the same 32px; (3) 'elevate:true'. None of the three are auto-computed — omitting any renders a clipped/misaligned/wrongly-stacked strip.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `minScreenHeight` | —    | `number`  | `60`, `70`, `80`, `90`                                                                                                                                                       | —       | dvh (not vh, for mobile chrome collapse). Pairs with flex flex-col — does not force centering itself. WINS over 'minHeight' when both are set (minHeight's resolution is skipped entirely, not merged).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `maxHeight`       | —    | `number`  | `800`, `1000`, `1200`                                                                                                                                                        | —       | px, static class-map ladder, no freeform. Independent of minHeight/minScreenHeight.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |

### `shared:structural`

NOT referenced via \{type:'object',shape:'structural'} from a kind descriptor like the other shared shapes. This is a validator-level ADDITIVE allowlist: every node's settings object is validated against 'kind:\<its-kind>' UNIONED with these three keys — tab\_label/slot are read generically off ANY child node's settings by an ANCESTOR container (tabs.tsx, accordion.tsx, find-by-slot.ts), independent of the child's own kind, and are not part of any single kind's own \*Settings TS interface; surface is read generically off ANY node's settings by renderer.tsx's \<TemplateSurface> wrapper, gated on that node's 'template' annotation naming a templates\[]/pageTemplates\[] entry that itself declares a 'surface' property (the surface-declaring template set), independent of the node's own kind. This is an extension beyond the design spec's literal decision-1 text (which only names shared:surface as a referenced shape); documented here because the real catalog already uses tab\_label/slot on stack/text/tabs nodes and surface on section-root container nodes (see Task A1 defaults-audit notes + MIO-2685 review round 1), and excluding any of the three would make the validator reject existing recipes. MIO-2724 addendum: 'name'/'role'/'salesMeta' (REVIEW — flagged for owner confirmation, see their own descriptions) extend this additive allowlist with catalog-authoring-only narrative-arc metadata used by the page-sales recipe's section-root nodes — distinct from any mio-hub renderer concern, and (for 'role') a deliberate same-name/different-vocabulary overload against kind:field.core.role, resolved safely by this validator's kind-first-then-structural-fallback lookup order.

| Key         | Tier           | Type     | Allowed values                        | Default | Description                                                                                                                                                                                                                                                                                                                |
| ----------- | -------------- | -------- | ------------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tab_label` | core           | `string` | —                                     | —       | Read by tabs.tsx and accordion.tsx off a DIRECT CHILD's own settings (getLabel()) to label that child's tab/accordion trigger — independent of the child's own kind. Falls back to 'Tab N' / index-based label when absent or non-string.                                                                                  |
| `slot`      | core           | `string` | —                                     | —       | Read by find-by-slot.ts (src/lib/page-tree/data-binding/find-by-slot.ts) to locate a node by a named slot, for patch/cascade addressing — independent of the node's own kind.                                                                                                                                              |
| `surface`   | presentational | `object` | object (surface)                      | —       | Read generically off ANY node's settings by renderer.tsx's \<TemplateSurface> wrapper (renderer.tsx:150-162), gated on node.template being in the surface-declaring template set, independent of the node's own kind. Kinds that ALSO read it at the node tint tier declare it themselves (kind:stack, kind:content-card). |
| `name`      | —              | `string` | —                                     | —       | REVIEW: catalog-authoring-only narrative section slug (e.g. "the-struggle") — generic across kinds via the same additive fallback as slot/tab\_label. Not consumed by the mio-hub renderer. See the shared:structural top-level description for the full name-collision + scope note.                                      |
| `role`      | —              | `string` | `connect`, `reveal`, `prove`, `close` | —       | REVIEW: catalog-authoring-only narrative-arc role tag (which beat of the sales-page arc this section plays). Distinct vocabulary from kind:field.core.role — see the shared:structural top-level description.                                                                                                              |
| `salesMeta` | —              | `object` | —                                     | —       | REVIEW: catalog-authoring-only per-section narrative metadata block, sibling of 'name'/'role'/'slot'. Not consumed by the mio-hub renderer — pure recipe-authoring documentation. See the shared:structural top-level description for the full rationale and the owner-review flag.                                        |

### `kind:stack`

| Key            | Tier           | Type      | Allowed values                                                    | Default | Description                                                                                                                                                                                                                                                                                                                                                                                                                       |
| -------------- | -------------- | --------- | ----------------------------------------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `gap`          | presentational | `number`  | `0`, `0.5`, `1`, `1.5`, `2`, `2.5`, `3`, `4`, `5`, `6`, `8`, `12` | —       | Flex-column gap step.                                                                                                                                                                                                                                                                                                                                                                                                             |
| `align`        | presentational | `string`  | `start`, `center`, `end`, `stretch`                               | —       | Cross-axis alignment (items-\*).                                                                                                                                                                                                                                                                                                                                                                                                  |
| `width`        | presentational | `string`  | `full`, `1/2`, `1/3`, `1/4`, `2/3`, `3/4`, `fit`                  | —       | Row/columns unification (MIO-2137): makes this stack behave as a responsive COLUMN inside a 'row' — full-width on mobile, its flex-grow ratio at/above the hub 660px container-query breakpoint. Requires an ancestor declaring @container/hub. "fit" is a DIFFERENT mechanism from the row-column members — a shrink-wrap (w-fit), for pairing a button with e.g. a doodle\{variant:"underline"} at the button's own edge width. |
| `surface`      | presentational | `object`  | object (surface)                                                  | —       | Column-level visual surface (MIO-2335) — same TemplateSurface vocabulary as section roots, at the 'node' tint tier. 'visibility' is NOT honoured here (section-root-only concern).                                                                                                                                                                                                                                                |
| `mobileGap`    | presentational | `number`  | `1.5`, `3`, `4`, `6`                                              | —       | Opt-in mobile-narrower gap step (DS playlist header content column).                                                                                                                                                                                                                                                                                                                                                              |
| `grow`         | presentational | `boolean` | —                                                                 | —       | Opt-in min-w-0 flex-1 so this stack fills the remaining width of a plain flex row beside a fixed-size sibling.                                                                                                                                                                                                                                                                                                                    |
| `px`           | presentational | `number`  | `0.5`                                                             | —       | Opt-in 2px horizontal optical inset (px-0.5).                                                                                                                                                                                                                                                                                                                                                                                     |
| `justify`      | presentational | `string`  | `start`, `center`, `end`, `between`                               | —       | Main-axis (vertical) distribution, mirroring row\.justify. No "around" member.                                                                                                                                                                                                                                                                                                                                                    |
| `fitMobile`    | presentational | `boolean` | —                                                                 | `false` | Drops the 'width:"fit"' full-width-on-mobile flip (pairs with dropping the paired button's own fullWidthMobile — the two move together).                                                                                                                                                                                                                                                                                          |
| `hideOnMobile` | presentational | `boolean` | —                                                                 | `false` | Literal @max-\[659px]/hub:hidden on the whole stack — hides the subtree below the hub container's 660px breakpoint.                                                                                                                                                                                                                                                                                                               |

### `kind:row`

| Key           | Tier           | Type      | Allowed values                                                   | Default | Description                                                                                                                                                                                           |
| ------------- | -------------- | --------- | ---------------------------------------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `gap`         | presentational | `number`  | `1`, `1.5`, `2`, `2.5`, `3`, `4`, `5`, `6`, `8`, `12`, `section` | —       | Flex-row gap step, plus the 'section' sentinel (gap-hub-section-gap, row\.tsx:92) — matches RowSettings.gap's mixed union; under the enum-subsumes-type rule the type field is not load-bearing here. |
| `align`       | presentational | `string`  | `start`, `center`, `end`, `stretch`                              | —       | Cross-axis alignment (items-\*).                                                                                                                                                                      |
| `justify`     | presentational | `string`  | `start`, `center`, `end`, `between`, `around`                    | —       | Main-axis distribution (justify-\*).                                                                                                                                                                  |
| `responsive`  | presentational | `boolean` | —                                                                | —       | Opt-in DS responsive-intent row: stacks below the container's 660px, becomes a row (vertically centered) at/above it. Requires an ancestor declaring @container/hub.                                  |
| `split`       | presentational | `boolean` | —                                                                | —       | Opt-in \[&>*]:flex-1 \[&>*]:min-w-0 so direct children divide the row evenly (DS 50/50 split).                                                                                                        |
| `wrap`        | presentational | `boolean` | —                                                                | —       | Opt-in flex-wrap, used by the unified 'row' section's body so 1-4 column stack children wrap.                                                                                                         |
| `fullWidth`   | presentational | `boolean` | —                                                                | —       | Opt-in w-full so the row fills its parent's width even inside an items-start flex column.                                                                                                             |
| `mobileGap`   | presentational | `number`  | `1.5`, `3`, `6`                                                  | —       | Opt-in mobile-narrower gap step.                                                                                                                                                                      |
| `maxWidth`    | presentational | `number`  | `800`                                                            | —       | Opt-in max-width cap (DS playlist header sections).                                                                                                                                                   |
| `reverse`     | presentational | `boolean` | —                                                                | —       | Opt-in desktop-only visual-order reverse (@\[660px]/hub:flex-row-reverse). Mobile/stacked state unaffected.                                                                                           |
| `alignMobile` | presentational | `string`  | `start`, `center`, `end`                                         | —       | Cross-axis alignment override applied only below the hub container's 660px breakpoint.                                                                                                                |

### `kind:grid`

| Key       | Tier           | Type     | Allowed values                       | Default | Description                                                                                                                                                                                                                                                                                                                                   |
| --------- | -------------- | -------- | ------------------------------------ | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `cols`    | presentational | `number` | `1`, `2`, `3`, `4`, `6`, `12`        | —       | Column count (legacy variant only).                                                                                                                                                                                                                                                                                                           |
| `gap`     | presentational | `number` | `1`, `2`, `3`, `4`, `6`, `8`, `12`   | —       | Grid gap step (legacy variant only).                                                                                                                                                                                                                                                                                                          |
| `variant` | presentational | `string` | `legacy`, `ds-content`, `responsive` | —       | Layout variant. 'legacy' = flat grid with configurable cols/gap (default). 'ds-content' = DS-aligned content-page grid with its own section band + containment. 'responsive' = DS grid-section column progression (1->3\@660->4\@1024) WITHOUT its own section band — relies on an ancestor's TemplateSurface for the @container/hub context. |

### `kind:carousel`

| Key             | Tier           | Type      | Allowed values                  | Default | Description                                                                                                                                                                                                                                                                                         |
| --------------- | -------------- | --------- | ------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `slidesPerView` | core           | `number`  | —                               | `1`     | Slides visible per view (drives static SLIDE\_BASIS flex-basis classes; the primitive supports 1-4 with static Tailwind classes).                                                                                                                                                                   |
| `loop`          | core           | `boolean` | —                               | `true`  | Embla infinite-loop mode.                                                                                                                                                                                                                                                                           |
| `show_dots`     | presentational | `boolean` | —                               | `true`  | Dot pagination indicator visibility.                                                                                                                                                                                                                                                                |
| `autoplay`      | presentational | `boolean` | —                               | `false` | Embla autoplay plugin toggle.                                                                                                                                                                                                                                                                       |
| `interval_ms`   | presentational | `number`  | —                               | —       | Autoplay interval in milliseconds. Only meaningful when autoplay=true.                                                                                                                                                                                                                              |
| `bleed`         | presentational | `boolean` | —                               | —       | MIO-2678 full-bleed opt-in — the viewport spans the full section width; each slide's content and the controls row carry the section gutter internally instead of a wrapping container.                                                                                                              |
| `effect`        | presentational | `string`  | `slide`, `blur-fade`, `marquee` | `slide` | 'blur-fade': one slide per grid cell, 400ms fade+blur+translate, banked-pause autoplay. 'marquee': continuously drifting strip via real scrollLeft, pauses on hover/focus/touch/reduced-motion, ignores 'bleed'. Both bypass Embla; a 'quote' child auto-switches to the DS grid-card presentation. |
| `marqueeSpeed`  | presentational | `number`  | —                               | `30`    | Drift speed in px/s. 'effect:"marquee"' only.                                                                                                                                                                                                                                                       |

### `kind:horizontal-scroll`

| Key         | Tier           | Type     | Allowed values            | Default | Description                                                                                                                                         |
| ----------- | -------------- | -------- | ------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `gap`       | presentational | `number` | `2`, `4`, `6`, `8`        | —       | Inter-item gap step.                                                                                                                                |
| `snap`      | presentational | `string` | `none`, `start`, `center` | `start` | CSS scroll-snap-align mode.                                                                                                                         |
| `itemWidth` | presentational | `string` | `auto`, `card`            | —       | 'card' fixes each item wrapper to a DS card width (\~300px, the 'compact' scroll recipe). 'auto' (default) leaves sizing to the child's own layout. |

### `kind:tabs`

No settings.

*The Tabs component reads NO settings of its own (PRIMITIVE\_DEFAULTS explicitly documents this: "tabs — the component reads no settings at all"). Its per-child tab label comes from shared:structural's tab\_label on each CHILD's own settings, not from a tabs-kind setting.*

### `kind:container`

| Key          | Tier           | Type      | Allowed values                           | Default | Description                                                                                                                                                  |
| ------------ | -------------- | --------- | ---------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `padding`    | presentational | `number`  | `0`, `2`, `4`, `6`, `8`, `12`, `16`      | —       | Padding scale.                                                                                                                                               |
| `background` | presentational | `string`  | `default`, `muted`, `accent`             | —       | Simple background token — UNRELATED to shared:background/TemplateSurface; this is container.tsx's own small enum.                                            |
| `rounded`    | presentational | `boolean` | —                                        | —       | Opt-in rounded corners.                                                                                                                                      |
| `maxWidth`   | presentational | `string`  | `content`, `search`, `4xl`, `6xl`, `7xl` | —       | Content-width cap + gutter. 'content' is the DS gutter-outside-cap idiom (MIO-2128) that pairs with shared:surface.padding='section' on an ancestor surface. |

*No PRIMITIVE\_DEFAULTS entry (container: \{}) — the DS-intended maxWidth:'content' default has NO inline '??' fallback in container.tsx, deliberately undeclared per that file's own comment (would change rendering the moment MIO-2358 wires cascade layer 1).*

### `kind:content-card`

| Key               | Tier           | Type     | Allowed values   | Default | Description                                                                                                                                                                                                                                               |
| ----------------- | -------------- | -------- | ---------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `actionFromScope` | core           | `string` | —                | —       | Names a scope key (a data-source-injected Action object) that becomes this card's stretched-link navigation target when the content-node/playlist-target-derived href doesn't resolve.                                                                    |
| `surface`         | presentational | `object` | object (surface) | —       | Generic-card-body visual surface (MIO-2335), 'node' tint tier, applied to the outer role=group wrapper. Only affects the generic (NodeRenderer) card body — NOT the delegated HubPlaylistCard path (hub\_playlists scope), which supplies its own chrome. |

### `kind:accordion`

| Key               | Tier | Type     | Allowed values       | Default | Description                                    |
| ----------------- | ---- | -------- | -------------------- | ------- | ---------------------------------------------- |
| `expansion`       | core | `string` | `single`, `multiple` | —       | Single vs multiple simultaneously-open panels. |
| `defaultExpanded` | core | `array`  | —                    | —       | Node ids expanded by default.                  |

### `kind:video`

| Key          | Tier           | Type      | Allowed values     | Default  | Description                                                                                                                                                            |
| ------------ | -------------- | --------- | ------------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `embed_type` | core           | `string`  | `native`, `iframe` | `native` | 'native' renders an HTML5 \<video>; 'iframe' renders a sandboxed \<iframe> gated through isAllowedEmbedUrl (an unlisted host renders an inline error, not the iframe). |
| `controls`   | presentational | `boolean` | —                  | `true`   | Native \<video controls> attribute.                                                                                                                                    |
| `autoplay`   | presentational | `boolean` | —                  | `false`  | Autoplay toggle.                                                                                                                                                       |
| `loop`       | presentational | `boolean` | —                  | `false`  | Loop toggle.                                                                                                                                                           |
| `muted`      | presentational | `boolean` | —                  | `true`   | Muted toggle.                                                                                                                                                          |

### `kind:divider`

| Key           | Tier           | Type     | Allowed values             | Default      | Description                                                                                                     |
| ------------- | -------------- | -------- | -------------------------- | ------------ | --------------------------------------------------------------------------------------------------------------- |
| `spacing`     | presentational | `number` | `0`, `2`, `4`, `6`, `8`    | `4`          | Vertical margin scale (my-\*).                                                                                  |
| `variant`     | presentational | `string` | `line`, `wave`             | `line`       | 'wave' — a drawn rule tiling at a fixed amplitude.                                                              |
| `orientation` | presentational | `string` | `horizontal`, `vertical`   | `horizontal` | —                                                                                                               |
| `size`        | presentational | `string` | `small`, `normal`, `large` | `normal`     | Wave wavelength/amplitude; ignored by variant:"line".                                                           |
| `weight`      | presentational | `number` | —                          | —            | Stroke thickness, clamped \[0.5,24]. No hard default — computed (1 for line, size-derived for wave) when unset. |
| `opacity`     | presentational | `number` | —                          | `15`         | Rule strength as CSS opacity (0-100).                                                                           |
| `inset`       | presentational | `number` | —                          | `0`          | Percent, clamped \[0,50]. Shortens the rule from each end.                                                      |

### `kind:progress-ring`

| Key          | Tier           | Type      | Allowed values     | Default | Description                                                                                                                                                                                                          |
| ------------ | -------------- | --------- | ------------------ | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `value`      | core           | `number`  | —                  | —       | Static 0-100 progress value.                                                                                                                                                                                         |
| `valueFrom`  | core           | `string`  | —                  | —       | Names a scope key holding a numeric 0-100 progress value (MIO-2248); takes priority over the static 'value' when the scope value coerces to a finite number.                                                         |
| `label`      | core           | `string`  | —                  | —       | Accessible name for the progressbar role. Defaults to 'Progress'.                                                                                                                                                    |
| `size`       | presentational | `number`  | —                  | `64`    | Pixel diameter. DS ring sizes are 16 \| 24 \| 48 \| 64 \| 76, but the underlying type is an unconstrained number (ui/progress accepts any value) — not a hard enum.                                                  |
| `mobileSize` | presentational | `number`  | —                  | —       | Opt-in mobile pixel diameter — when set, renders TWO ring hosts that container-query-swap (>=660px shows 'size', \<660px shows 'mobileSize'), matching the DS's dual-ring pattern rather than one ring that resizes. |
| `variant`    | presentational | `string`  | `default`, `white` | —       | Surface variant. 'default' = light. 'white' = for dark/colourful surfaces.                                                                                                                                           |
| `disabled`   | presentational | `boolean` | —                  | —       | Dims the ring.                                                                                                                                                                                                       |

### `kind:headline`

| Key                  | Tier           | Type                  | Allowed values                     | Default  | Description                                                                                                                                                            |
| -------------------- | -------------- | --------------------- | ---------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `level`              | core           | `number`              | `1`, `2`, `3`, `4`, `5`, `6`       | `2`      | Heading level 1-6, drives both the rendered tag and the DS type-scale size mapping (SIZE\_BY\_LEVEL).                                                                  |
| `align`              | presentational | `string`              | `left`, `center`, `right`          | `left`   | Text alignment.                                                                                                                                                        |
| `weight`             | presentational | `number`              | `400`, `500`, `600`, `700`         | `400`    | Font weight. MIO-985 Thread B: default headlines are font-normal (400), not the old font-semibold.                                                                     |
| `highlight`          | presentational | `['string', 'array']` | —                                  | —        | Phrase(s) to emphasise — accent colour, no wash by default. Case-sensitive literal substring, every occurrence flagged.                                                |
| `highlightUnderline` | presentational | `boolean`             | —                                  | —        | Underlines HIGHLIGHTED runs only. No-op without 'highlight'.                                                                                                           |
| `highlightStyle`     | presentational | `string`              | `accent`, `wash`                   | `accent` | 'wash' = the strong marker wash (bg-hub-primary-bg-hover).                                                                                                             |
| `size`               | presentational | `string`              | `title`, `large-title`, `xl-title` | —        | Opt-in DISPLAY-size override — 'level' still owns semantics (heading tag, one-h1-dedupe). Landing/sales-page only — no hub PAGE should reach for large-title/xl-title. |
| `alignMobile`        | presentational | `string`              | `left`, `center`, `right`          | —        | Text-alignment override applied only below the hub container's 660px breakpoint.                                                                                       |

### `kind:text`

| Key             | Tier           | Type                  | Allowed values                    | Default | Description                                                                                                                                                                                                                         |
| --------------- | -------------- | --------------------- | --------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `align`         | presentational | `string`              | `left`, `center`, `right`         | `left`  | Text alignment.                                                                                                                                                                                                                     |
| `size`          | presentational | `string`              | `body`, `small`, `body-big`       | —       | 2-size DS scale (MIO-985 Thread B). "body-big" is a third opt-in step for callout/bridge lines; no responsive container-query shrink (unlike body/small).                                                                           |
| `weight`        | presentational | `number`              | `400`, `500`, `600`, `700`        | —       | Opt-in font-weight. Unset = normal.                                                                                                                                                                                                 |
| `clamp`         | presentational | `number`              | —                                 | —       | Opt-in line clamp (DS HubDescription maxLines). Unconstrained number type, but only small integers 1-6 are honoured — any other value is defensively ignored by the renderer (no error, silent no-op), not enforced as a hard enum. |
| `marginBottom`  | presentational | `number`              | `0`, `1`, `2`, `3`, `4`, `6`, `8` | —       | Opt-in bottom margin (mb-\* scale).                                                                                                                                                                                                 |
| `muted`         | presentational | `boolean`             | —                                 | —       | Opt-in muted-text override (text-foreground/75). Only applies on the non-clamped render path — the clamped path already renders muted unconditionally.                                                                              |
| `tone`          | presentational | `string`              | `primary`                         | —       | Opt-in color tone. 'primary' applies text-primary and wins over 'muted' when both are set.                                                                                                                                          |
| `highlight`     | presentational | `['string', 'array']` | —                                 | —       | Search-highlight wash. Mutually exclusive with 'clamp' (clamp wins).                                                                                                                                                                |
| `highlightTone` | presentational | `string`              | `wash`, `strong`                  | `wash`  | 'strong' = one ramp higher.                                                                                                                                                                                                         |
| `italic`        | presentational | `boolean`             | —                                 | —       | Opt-in italic. Independent of 'highlight'.                                                                                                                                                                                          |
| `variant`       | presentational | `string`              | `eyebrow`                         | —       | Opt-in bundle: uppercase, letter-spaced label above a headline. Wins over size/weight/color. No effect on the clamped render path.                                                                                                  |
| `alignMobile`   | presentational | `string`              | `left`, `center`, `right`         | —       | Text-alignment override applied only below the hub container's 660px breakpoint.                                                                                                                                                    |

### `kind:image`

| Key           | Tier           | Type      | Allowed values               | Default  | Description                                                                                                                                                                                                                                                         |
| ------------- | -------------- | --------- | ---------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `alt`         | core           | `string`  | —                            | —        | Accessible alt text.                                                                                                                                                                                                                                                |
| `lightbox`    | presentational | `boolean` | —                            | —        | Opt-in click-to-expand lightbox.                                                                                                                                                                                                                                    |
| `aspectRatio` | presentational | `string`  | `16:9`, `4:3`, `1:1`, `auto` | `16:9`   | Fixed aspect preset, mapped to ui/thumbnail's 'aspect' prop. '4:3' has no direct thumbnail preset — mapped to 'preview' plus an override class.                                                                                                                     |
| `objectFit`   | presentational | `string`  | `cover`, `contain`           | `cover`  | 'contain' takes priority over aspectRatio and always resolves to ui/thumbnail's natural (shrink-wrap) mode.                                                                                                                                                         |
| `radius`      | presentational | `string`  | `control`, `control-l`, `m`  | —        | DS thumbnail radius preset (ui/thumbnail 'radius').                                                                                                                                                                                                                 |
| `outline`     | presentational | `boolean` | —                            | —        | Opt-in shadow-hub-xs outline.                                                                                                                                                                                                                                       |
| `backdrop`    | presentational | `boolean` | —                            | `true`   | Opaque thumbnail backdrop (MIO-288 bg-background fill under the image, so transparent pixels don't composite with content below). Set false for intentionally transparent artwork that should let the section surface show through. No-op without a src (MIO-3086). |
| `maxWidth`    | presentational | `number`  | `352`, `128`                 | —        | Per-node width sub-cap in px (literal whitelist, no freeform) — 352 = the narrative one-column sections' measured image sub-cap, 128 = the deliverable-card icon-box cap. Applies max-w-\* + (when alignX='center') mx-auto.                                        |
| `alignX`      | presentational | `string`  | `center`, `start`            | `center` | Horizontal alignment of the maxWidth-capped box within its wider cell. No-op without maxWidth.                                                                                                                                                                      |

*EXCLUDES 'width'/'height' from ImageSettings — the hub interface comment states they are 'presently no-ops' (no longer forwarded to the rendered \<img>, ui/thumbnail owns that element with no sizing passthrough), kept on the TS contract only for BE-served-data forward-compat parsing, not for authoring. Per the honesty policy, a key with no rendering effect does not enter the vocabulary.*

### `kind:button`

| Key               | Tier           | Type      | Allowed values                                                                                                                        | Default   | Description                                                                                                                                                                                                                                                                                      |
| ----------------- | -------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `action`          | core           | `object`  | —                                                                                                                                     | —         | Canonical navigation target object.                                                                                                                                                                                                                                                              |
| `href`            | core           | `string`  | —                                                                                                                                     | —         | @deprecated legacy alias for 'action' (a bare URL string). Still read as a fallback.                                                                                                                                                                                                             |
| `actionFromScope` | core           | `string`  | —                                                                                                                                     | —         | Names a scope key (a data-source-injected Action object) used INSTEAD of the static action/href when the scope value resolves to a valid Action.                                                                                                                                                 |
| `labelFrom`       | core           | `string`  | —                                                                                                                                     | —         | Names a scope key whose STRING value replaces the static label (node.value) when present and non-empty.                                                                                                                                                                                          |
| `planGroupId`     | core           | `string`  | —                                                                                                                                     | —         | Id of the plan-group whose selected plan-card priceId is appended to this button's resolved href as ?price\_id=…. Resolves like plan-price groupId (exact id, else the page's sole plan-group). No-op when unset, when the group has no selection, or when the selected card carries no priceId. |
| `variant`         | presentational | `string`  | `primary`, `secondary`, `ghost-light`, `overlay-light`, `destructive`, `link`, `muted`, `ghost-dark`, `overlay-dark`, `ghost-primary` | `primary` | Visual style, forwarded 1:1 to ui/button's variant prop. 'ghost-primary' — same silhouette as ghost-dark, for use ON a solid bg-primary surface (adaptive text-primary-foreground instead of hardcoded text-white, design-system.md §4c).                                                        |
| `size`            | presentational | `string`  | `sm`, `md`, `lg`                                                                                                                      | `md`      | Leaf-facing size name, mapped to ui/button size tokens (sm->sm, md->default, lg->lg).                                                                                                                                                                                                            |
| `newTab`          | presentational | `boolean` | —                                                                                                                                     | `false`   | Opens the resolved href in a new tab.                                                                                                                                                                                                                                                            |
| `icon`            | presentational | `string`  | —                                                                                                                                     | —         | Leading icon (hub sprite name), rendered before the label.                                                                                                                                                                                                                                       |
| `iconRight`       | presentational | `string`  | —                                                                                                                                     | —         | Trailing icon (hub sprite name), rendered after the label.                                                                                                                                                                                                                                       |
| `disabled`        | presentational | `boolean` | —                                                                                                                                     | —         | Renders a non-interactive disabled button: no Link/anchor wrapper, no action/onClick. Used for locked/drip-scheduled CTAs.                                                                                                                                                                       |
| `fullWidthMobile` | presentational | `boolean` | —                                                                                                                                     | `false`   | Full-width, tap-friendly button below the hub 660px container-query breakpoint.                                                                                                                                                                                                                  |
| `compactMobile`   | presentational | `boolean` | —                                                                                                                                     | `false`   | Steps the 'md' size from 40px to 36px below the hub 659px breakpoint. Scoped to size:'md' only.                                                                                                                                                                                                  |

### `kind:icon`

| Key           | Tier           | Type     | Allowed values                | Default   | Description                                                                                                   |
| ------------- | -------------- | -------- | ----------------------------- | --------- | ------------------------------------------------------------------------------------------------------------- |
| `size`        | presentational | `number` | `16`, `20`, `24`, `32`, `48`  | `24`      | Pixel size, mapped to the nearest ui/icon-valid size (12\|14\|16\|20\|24\|32) via toIconSize().               |
| `color`       | presentational | `string` | `default`, `primary`, `muted` | `default` | Color token.                                                                                                  |
| `strokeWidth` | presentational | `number` | —                             | —         | Opt-in stroke-width override, passed straight to ui/icon. Unset preserves that component's own default (1.5). |

### `kind:field`

| Key               | Tier           | Type      | Allowed values                                        | Default | Description                                                                                                                                                                               |
| ----------------- | -------------- | --------- | ----------------------------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`            | core           | `string`  | —                                                     | —       | Required. The scope key this field reads (scope\[name]).                                                                                                                                  |
| `role`            | core           | `string`  | `title`, `subtitle`, `meta`, `body`                   | —       | Semantic role within the parent template — the cascade resolver applies role-specific defaults (e.g. title gets heading-weight styling), all overridable via the explicit settings below. |
| `actionFromScope` | core           | `string`  | —                                                     | —       | Title hover-chevron affordance — names a scope key (an Action object) that, when valid, wraps this field's text in a Link plus a decorative hover chevron.                                |
| `size`            | presentational | `string`  | `title`, `subtitle`, `body-big`, `body`, `body-small` | —       | Explicit size override. Falls back to the role default, then 'body'.                                                                                                                      |
| `weight`          | presentational | `number`  | `400`, `500`, `600`, `700`                            | —       | Explicit font-weight override. Falls back to the role default (title only), then unset.                                                                                                   |
| `align`           | presentational | `string`  | `left`, `center`, `right`                             | —       | Text alignment.                                                                                                                                                                           |
| `muted`           | presentational | `boolean` | —                                                     | —       | Explicit muted-text override (text-foreground/75). Falls back to the role default.                                                                                                        |
| `tone`            | presentational | `string`  | `default`, `primary`                                  | —       | Opt-in color tone. 'primary' wins over 'muted' when both are set. 'default' is the explicit no-tone path — same rendered result as leaving 'tone' unset.                                  |
| `clamp`           | presentational | `number`  | —                                                     | —       | Opt-in line clamp, routes through ui/expandable-text. Only integers 1-6 are honoured; other values are defensively ignored (no error).                                                    |
| `marginBottom`    | presentational | `number`  | `0`, `1`, `2`, `3`, `4`, `6`, `8`                     | —       | Opt-in bottom margin (mb-\* scale).                                                                                                                                                       |
| `icon`            | presentational | `string`  | —                                                     | —       | Opt-in leading hub sprite glyph name (DS meta-chip spec). Loosely typed string (not IconName) — an unknown name safely renders an empty \<use> reference rather than crashing.            |
| `ring`            | presentational | `object`  | —                                                     | —       | Opt-in leading progress-ring chip (DS 'Completed' chip spec) instead of 'icon'. Mutually exclusive with 'icon' in practice — if both set, 'ring' wins.                                    |
| `fade`            | presentational | `boolean` | —                                                     | —       | Opt-in single-line horizontal fade-truncate. Ignored on the clamped render path — clamp wins if both are set.                                                                             |
| `optional`        | presentational | `boolean` | —                                                     | —       | When true and the bound scope value is nullish, renders null in EVERY environment (no dev '\[missing: name]' marker).                                                                     |

### `kind:media-slot`

| Key           | Tier           | Type      | Allowed values                                             | Default | Description                                                                                                                                                                                              |
| ------------- | -------------- | --------- | ---------------------------------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`        | core           | `string`  | —                                                          | —       | Required. The scope key this slot reads (scope\[name]).                                                                                                                                                  |
| `alt`         | core           | `string`  | —                                                          | —       | Accessible alt text.                                                                                                                                                                                     |
| `preset`      | core           | `string`  | `thumbnail-160`, `medium-720`, `large-1440`, `webp-medium` | —       | Opt-in poster preset — when set AND scope.poster\_variants is a non-empty object, resolves the URL via the pickPoster fallback chain instead of reading a ready scope\[name] URL.                        |
| `aspectRatio` | presentational | `string`  | `16:9`, `4:3`, `1:1`, `auto`                               | —       | Same styling contract as kind:image's aspectRatio.                                                                                                                                                       |
| `objectFit`   | presentational | `string`  | `cover`, `contain`                                         | —       | Same styling contract as kind:image's objectFit.                                                                                                                                                         |
| `radius`      | presentational | `string`  | `control`, `control-l`, `m`                                | —       | Same styling contract as kind:image's radius.                                                                                                                                                            |
| `outline`     | presentational | `boolean` | —                                                          | —       | Same styling contract as kind:image's outline.                                                                                                                                                           |
| `width`       | presentational | `number`  | —                                                          | —       | Opt-in FIXED pixel width for a small tile beside a growing flex sibling. Height derives from the existing aspectRatio class, not set separately.                                                         |
| `progressBar` | presentational | `boolean` | —                                                          | —       | Opt-in in-progress watch-progress bar overlay, reading scope.progressPercent. Do NOT also set on a contentCardChild()'s media-slot (content-card.tsx already renders its own bar) — would double-render. |

### `kind:cta-slot`

| Key       | Tier           | Type     | Allowed values         | Default   | Description                                                                                      |
| --------- | -------------- | -------- | ---------------------- | --------- | ------------------------------------------------------------------------------------------------ |
| `name`    | core           | `string` | —                      | —         | Required. The scope key this slot reads (scope\[name]), expected shape \{label, href?, action?}. |
| `variant` | presentational | `string` | `primary`, `secondary` | `primary` | Visual style, forwarded to buttonVariants().                                                     |

### `kind:file-player`

| Key          | Tier | Type     | Allowed values | Default | Description                                                  |
| ------------ | ---- | -------- | -------------- | ------- | ------------------------------------------------------------ |
| `contentId`  | core | `string` | —              | —       | Content-node id to load.                                     |
| `fileId`     | core | `string` | —              | —       | File id to load (alternative binding path to contentId).     |
| `playlistId` | core | `string` | —              | —       | Playlist id context for the player's up-next/queue behavior. |

### `kind:file-attachments`

| Key           | Tier | Type     | Allowed values | Default | Description                                                                                                                                                                                        |
| ------------- | ---- | -------- | -------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `attachments` | core | `array`  | —              | —       | Attachment list to render (src/lib/api/content/types.ts Attachment — an API-owned shape; items are represented opaquely here, not re-declared, since Attachment is not a page-tree settings type). |
| `contentId`   | core | `string` | —              | —       | Content-node id, used to build download hrefs for legacy (non-file\_id) attachments.                                                                                                               |

### `kind:search-bar`

| Key           | Tier | Type     | Allowed values | Default | Description                                                                                                                            |
| ------------- | ---- | -------- | -------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `placeholder` | core | `string` | —              | —       | Passes through to the spotlight trigger's label. Non-string values are left undefined (SearchSpotlightTrigger's own fallback applies). |

### `kind:quote`

| Key          | Tier           | Type      | Allowed values | Default | Description                                                                             |
| ------------ | -------------- | --------- | -------------- | ------- | --------------------------------------------------------------------------------------- |
| `showAvatar` | presentational | `boolean` | —              | `true`  | Show the member's photo beside their name; off centres name+profession under the quote. |

### `kind:banner`

| Key            | Tier           | Type      | Allowed values           | Default  | Description                                                                                                                                                                    |
| -------------- | -------------- | --------- | ------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `sticky`       | presentational | `boolean` | —                        | `false`  | Pins the bar to the top of the viewport for the whole page (MIO-2734). 'reveal:"after-scroll"' forces sticky on regardless of this flag.                                       |
| `tone`         | presentational | `string`  | `warning`, `info`        | `info`   | Drives the notice-bar tint AND the ARIA role (alert/status).                                                                                                                   |
| `appearance`   | presentational | `string`  | `tint`, `solid`, `page`  | `tint`   | 'page' is the DS nav-bar frosted-chrome surface (page bg + blur), tone-independent — used by the scroll-revealed action bar, not the tint/solid urgency-bar family.            |
| `justify`      | presentational | `string`  | `center`, `between`      | `center` | 'between' is the DS nav-bar row shape (logo left / CTA right).                                                                                                                 |
| `contentWidth` | presentational | `string`  | `full`, `content`        | `full`   | 'content' caps the row to max-w-hub-content + the section gutter, landing its edges at the same x position as the page's content column — the surface itself stays full-bleed. |
| `reveal`       | presentational | `string`  | `always`, `after-scroll` | `always` | 'after-scroll' hides the bar until the page scrolls past the first viewport, forces sticky on, and paints at a lower z-index than an urgency banner above it.                  |

### `kind:countdown`

| Key            | Tier           | Type      | Allowed values    | Default                      | Description                                                                                               |
| -------------- | -------------- | --------- | ----------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------- |
| `title`        | core           | `string`  | —                 | —                            | Deadline sentence override. Omit to auto-format from 'value' (an ISO date string), locale+timezone aware. |
| `expiredLabel` | core           | `string`  | —                 | `Registration is now closed` | Sentence shown once the deadline in 'value' has passed.                                                   |
| `ctaLabel`     | core           | `string`  | —                 | —                            | Built-in CTA label — only rendered when 'size:'small''.                                                   |
| `ctaAction`    | core           | `object`  | —                 | —                            | Same Action pipeline as 'button.action' — paired with 'ctaLabel'.                                         |
| `size`         | presentational | `string`  | `small`, `large`  | `large`                      | Doubles as the layout switch (cell strip vs deadline-sentence block).                                     |
| `align`        | presentational | `string`  | `center`, `start` | `center`                     | —                                                                                                         |
| `hideOnExpire` | presentational | `boolean` | —                 | `false`                      | Hide the whole widget once expired, instead of swapping to 'expiredLabel'.                                |

### `kind:logo`

| Key      | Tier           | Type     | Allowed values   | Default  | Description                                                          |
| -------- | -------------- | -------- | ---------------- | -------- | -------------------------------------------------------------------- |
| `height` | presentational | `string` | `normal`, `hero` | `normal` | 'normal'=24px chrome-scale, 'hero'=71px (48px mobile) content-scale. |

### `kind:theme-toggle`

No settings.

### `kind:plan-group`

| Key     | Tier           | Type     | Allowed values | Default         | Description                                                   |
| ------- | -------------- | -------- | -------------- | --------------- | ------------------------------------------------------------- |
| `label` | presentational | `string` | —              | `Choose a plan` | aria-label on the radiogroup wrapping the plan-card children. |

### `kind:plan-card`

| Key               | Tier           | Type      | Allowed values | Default | Description                                                                                                                                                                                                                                                                                              |
| ----------------- | -------------- | --------- | -------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `defaultSelected` | presentational | `boolean` | —              | —       | Seeds selection when this is the first such child in a plan-group (or seeds the standalone local toggle when rendered outside one). Content (name/price/currency/period/description/badge/save) lives on 'value', not settings — outside the settings-vocabulary contract, same precedent as kind:quote. |

### `kind:plan-price`

| Key        | Tier           | Type     | Allowed values | Default | Description                                                                                                                                                                                                                                                                                                              |
| ---------- | -------------- | -------- | -------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `groupId`  | core           | `string` | —              | —       | Required. Id of the plan-group node this leaf subscribes to. Resolved contextually, not just literally: when no plan-group is registered under this exact id but exactly one group is registered on the page, that sole group is used instead; with zero or 2+ candidate groups, no link is made and 'fallback' renders. |
| `fallback` | presentational | `object` | —              | —       | Rendered for SSR/first paint and whenever 'groupId' has never published a selection. Must byte-match the subscribed group's 'defaultSelected' card's own 'price'/'period' — no reformatting happens in the pub-sub relay.                                                                                                |

### `kind:doodle`

| Key            | Tier           | Type      | Allowed values                                            | Default       | Description                                                                                                                                 |
| -------------- | -------------- | --------- | --------------------------------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `variant`      | presentational | `string`  | `arrow-plain`, `arrow-straight`, `underline`, `knot-curl` | `arrow-plain` | —                                                                                                                                           |
| `size`         | presentational | `string`  | `sm`, `md`, `lg`, `xl`                                    | `md`          | Each step bakes in its own mobile value at the hub 659px breakpoint (sm 40→23, md 56→33, lg 81→47, xl 110→64).                              |
| `flip`         | presentational | `boolean` | —                                                         | `false`       | —                                                                                                                                           |
| `rotate`       | presentational | `number`  | `90`, `180`, `270` + any string                           | —             | Named values (90\|180\|270) resolve to static Tailwind rotate classes; any other numeric value falls back to an inline style.rotate.        |
| `strokeWidth`  | presentational | `number`  | —                                                         | `2`           | Clamped \[1,6].                                                                                                                             |
| `align`        | presentational | `string`  | `start`, `center`, `end`                                  | —             | —                                                                                                                                           |
| `draw`         | presentational | `boolean` | —                                                         | `false`       | Once-per-viewport-entry stroke-draw animation (Motion pathLength).                                                                          |
| `drawDelay`    | presentational | `number`  | —                                                         | `0`           | Extra delay (seconds) on top of the viewport trigger — used to chain a second doodle so it visibly waits for a first one to finish drawing. |
| `hideOnMobile` | presentational | `boolean` | —                                                         | `false`       | Literal @max-\[659px]/hub:hidden — scoped to this leaf, not a general leaf capability.                                                      |

### `kind:featured-icon`

| Key       | Tier           | Type      | Allowed values                       | Default  | Description                                                                                                                  |
| --------- | -------------- | --------- | ------------------------------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `icon`    | presentational | `string`  | —                                    | —        | Hub sprite glyph name. Optional when 'label' is given.                                                                       |
| `label`   | presentational | `string`  | —                                    | —        | Short text (e.g. a step numeral). Wins over 'icon' when both are set.                                                        |
| `variant` | presentational | `string`  | `solid`, `accent-overlay`, `outline` | `solid`  | 'outline' (hollow disc, rides ambient currentColor) is the journey-stage marker treatment.                                   |
| `tone`    | presentational | `string`  | `accent`, `destructive`, `neutral`   | `accent` | —                                                                                                                            |
| `size`    | presentational | `string`  | `small`, `normal`, `large`           | `normal` | 72/88/104px, steps down one tier at max-sm.                                                                                  |
| `draw`    | presentational | `boolean` | —                                    | `false`  | Pen-draw a single stroked glyph on mount — a CSS-keyframe mechanism, distinct from doodle.draw (Motion, viewport-triggered). |

## Consumer profiles

* **`ios-min`** — templates: `hero`; settings: `core`
* **`web`** — templates: all templates; settings: `core+presentational`
