Chrome

Foundation
@wabbit/tome-chromev0.7.0

Tome platform site-shell layer — Header global factory + 7 navbar variants + Footer global factory + 10 footer variants + variant registries + scroll-direction hide/reveal. Distinct from @wabbit/tome-blocks (page-section content) by consumption shape: chrome is configured once per site via Payload globals, not composed per page.

Installnpm install @wabbit/tome-chrome

Overview

@wabbit/tome-chrome

Tome's site-shell layer: a Header global factory with 7 navbar variants, a Footer global factory with 10 footer variants, per-surface variant registries, and a motion-adapter contract that lets @wabbit/tome-motion drive scroll behavior without tome-chrome depending on GSAP directly. app-adjacent layer per root ARCHITECTURE.md — assembles core + ui into a mountable shell rather than exposing atomic primitives. Distinct from @wabbit/tome-blocks* by consumption shape: chrome is configured once per site via two Payload globals, not composed per-page like a content block.

Install

pnpm add @wabbit/tome-chrome

| Peer | Range | |---|---| | @wabbit/tome-core | >=1.0.0 <2.0.0 | | @wabbit/tome-ui | >=0.9.0 <1.0.0 | | next | >=14 | | payload | >=3.67.0 | | react / react-dom | >=19.0.0 | | lucide-react | >=0.460.0 |

60-second quickstart

payload.config.ts:

import { buildConfig } from 'payload'
import { initChrome } from '@wabbit/tome-chrome'

export default buildConfig(
  initChrome(baseConfig, {
    header: { slug: 'header' },
    footer: { slug: 'footer' },
  }),
)

Root layout (verified against wabbit-site-core's real usage — src/app/(frontend)/layout.tsx):

import { HeaderRenderer } from '@wabbit/tome-chrome/header'

<HeaderRenderer
  header={headerGlobalDoc}
  publicContext={{ locale: 'en' }}
  motionAdapters={chromeMotionAdapters} // from '@wabbit/tome-motion/adapters/chrome', optional
/>

Public API

| Export | Description | |---|---| | initChrome(payloadConfig, opts) | Merges Header/Footer globals into a Payload Config; registers built-in variants; registers the layer | | TomeChromeConfig, TomeHeaderConfig, TomeFooterConfig | Factory config types | | HeaderRenderer (./header subpath) | Top-level header renderer — resolves variant, wraps motion/logo/link context | | FooterRenderer (./footer subpath) | Top-level footer renderer — mirrors HeaderRenderer | | createHeaderGlobal(config) / createFooterGlobal(config) | Build the raw Payload GlobalConfig (used internally by initChrome, exposed for advanced wiring) | | headerVariantRegistry, footerVariantRegistry, createVariantRegistry (./registry) | Per-surface variant registries — register/get/has/getAll/toFieldOptions | | ChromeMotionAdapterProvider, useChromeMotionAdapters, NOOP_CHROME_ADAPTERS (./adapters/motion) | Context plumbing for the motion-adapter contract; NOOP keeps chrome fully functional with zero animation when tome-motion isn't installed | | TomeHeaderData, TomeFooterData, TomeNavbarProps, TomeFooterVariantProps, HeaderRendererProps, FooterRendererProps, ChromeMotionAdapters, ChromeVariant<T>, VariantRegistry<T> | Full type surface (./index re-exports ./types) |

The six extension-point slots

Every navbar variant and HeaderRenderer/FooterRenderer accept the same override surface, declared as individual optional props on TomeNavbarProps/HeaderRendererProps/FooterRendererProps (src/types.ts) rather than one grouped "slots" type:

  1. searchAdapter?: () => ReactNode — consumer search UI (header only)
  2. mediaAdapter?: ComponentType<{media, alt?, className?}> — image renderer override (header + footer)
  3. languageSwitcherSlot?: ReactNode — header only
  4. themeToggleSlot?: ReactNode — header only
  5. LogoComponent?: ComponentType<HeaderLogoSlotProps> — override the default <img> logo, e.g. for inline-SVG/theme-adaptive logos (header + footer, via shared HeaderLogoProvider)
  6. LinkComponent?: ComponentType<HeaderLinkSlotProps> — override the default next/link path, e.g. a view-transitions router (header + footer, via shared HeaderLinkProvider)

Footer additionally accepts SocialIconComponent for socialLinks icons (chrome ships no icon set of its own). Audit friction note: because these six live as scattered optional props rather than one ChromeSlots type, a consumer wiring a custom header/footer must cross-reference TomeNavbarProps + HeaderRendererProps (or TomeFooterVariantProps + FooterRendererProps) individually to know the full override surface — there is no single type to import and satisfy.

Server / client posture (load-bearing — verify against src, not assumption)

Current state on `main` (chrome 0.6.2): HeaderRenderer.tsx and FooterRenderer.tsx both declare 'use client' at the top of the file. All 7 navbar variants and Footer10 also declare 'use client'. Footer1–9 don't carry the directive themselves, but they only ever render inside FooterRenderer's client tree, so they execute client-side in practice regardless. The renderers are not currently server components — read this section fresh against src/ before relying on it, since a future decomposition wave may split them.

HeaderVisibilityFrame (the scroll-driven hide-on-scroll-down/reveal-on-scroll-up wrapper) is not a separate exported component — it's a private function defined inline inside HeaderRenderer.tsx (header/HeaderRenderer.tsx, alongside HeaderVisibilityFrameActive). It reads useChromeMotionAdapters() for scroll direction and useReducedMotion(), and is always visible when reduced-motion is active or when enableHideOnScroll is false.

What genuinely is server-safe:

  • initChrome(), createHeaderGlobal(), createFooterGlobal(), factory/fields.ts — Payload config assembly, runs at config-build/admin time, never inside the React render tree.
  • ./registry (createVariantRegistry, headerVariantRegistry, footerVariantRegistry) — plain Map-backed data structures, isomorphic.
  • adapters/motion.tsx (ChromeMotionAdapterProvider) is 'use client' since it's a React Context provider consumed by the client renderer tree.

Extending

Register a new navbar/footer variant via headerVariantRegistry.register({ key, label, component, overlayMode? }) (or the footer equivalent) before HeaderRenderer/FooterRenderer first render — initChrome() is the conventional call site. See docs/superpowers/specs/2026-05-03-tome-chrome-layer-design.md § variant registry for the full contract, including overlayMode precedence rules.

Design history

  • docs/superpowers/specs/2026-05-03-tome-chrome-layer-design.md, 2026-05-03-tome-chrome-orchestrate-prompt.md — Phase 1 (header)
  • docs/superpowers/specs/2026-05-24-tome-chrome-footer-design.md, 2026-05-24-tome-chrome-footer-orchestrate-prompt.md — Phase 2 (footer)
  • docs/superpowers/specs/2026-06-29-tome-breakout-chrome-graduation-design.md — later graduation work
  • docs/claude-gotchas.md → Payload / Typing / Layer Design section — the "NavBar4 is a different navbar, not a flag flip" entry applies directly to anyone touching designVersion resolution

Exports

  • @wabbit/tome-chrome
  • @wabbit/tome-chrome/header
  • @wabbit/tome-chrome/header/factory
  • @wabbit/tome-chrome/footer
  • @wabbit/tome-chrome/footer/factory
  • @wabbit/tome-chrome/registry
  • @wabbit/tome-chrome/adapters/motion

Changelog

v0.7.0minor

6bc419c: R4 rulings #2 + #3 (all additive; every old name keeps working as a `@deprecated` alias until that package's next major). `create*` is canonical for collection/layer factories (`define*` stays reserved for the blocks descriptor system): crm/deals/marketing/intake/forms gain `create*Collection` names for their former `define*Collection` factories. Layer entries converge on `createXLayer(config?) → bundle`: `createCrmLayer`/`createDealsLayer`/`createMarketingLayer`/`createCatalogLayer`/`createEconomyLayer`/`createChromeLayer`/`createLmsLayer`/`createAiLayer` (+ `createFormsLayer`/`createIntakeLayer`), returning bare `CollectionConfig[]` where the layer contributes only collections or an honest named bundle where it hands back more (chrome: `{ globals }`; lms/ai: `{ collections, hooks }`); void-returning `initCatalog`/`initEconomy` stay as the single registration call sites, delegated to internally. Naming note for forms consumers: `createFormsCollection` (singular factory) vs `createFormsCollections` (plural composer) vs `createFormsLayer` (layer entry) — each docblock states the distinction.

  • 6bc419c: R4 rulings #2 + #3 (all additive; every old name keeps working as a `@deprecated` alias until that package's next major). `create*` is canonical for collection/layer factories (`define*` stays reserved for the blocks descriptor system): crm/deals/marketing/intake/forms gain `create*Collection` names for their former `define*Collection` factories. Layer entries converge on `createXLayer(config?) → bundle`: `createCrmLayer`/`createDealsLayer`/`createMarketingLayer`/`createCatalogLayer`/`createEconomyLayer`/`createChromeLayer`/`createLmsLayer`/`createAiLayer` (+ `createFormsLayer`/`createIntakeLayer`), returning bare `CollectionConfig[]` where the layer contributes only collections or an honest named bundle where it hands back more (chrome: `{ globals }`; lms/ai: `{ collections, hooks }`); void-returning `initCatalog`/`initEconomy` stay as the single registration call sites, delegated to internally. Naming note for forms consumers: `createFormsCollection` (singular factory) vs `createFormsCollections` (plural composer) vs `createFormsLayer` (layer entry) — each docblock states the distinction.
  • 36e537a: Peer/dependency contracts now tell the truth. blocks-core: importing the root barrel no longer hard-crashes when the optional peers (`@wabbit/tome-core`, `@wabbit/tome-catalog`) are absent — `productHooks` registration is lazily guarded; NEW explicit `registerBlockBundleProductType()` export (root barrel + `./registry/productHooks` subpath) for deterministic, format-safe registration from `payload.config.ts` (the import-time auto path no-ops under native ESM, which affects `generate:types`-visible product-type options — call the explicit API when composing catalog). chrome: `next` is now a required peer (`>=14`) — it was declared optional while `next/navigation`/`next/link` were hard-imported. readout: declares its real `next` peer; `createReadoutBlocks({ accentPalette })` is now implemented (field-tree narrowing, dispatch's mechanism) instead of a documented no-op. blocks-lms-pack / blocks-catalog-pack: `@wabbit/tome-core` moves from hard `dependencies` to `optionalDependencies`, matching org-pack and the packs' own documented degrade-gracefully design.
  • aef2725: Chrome shell goes server-safe (the audit's remaining clientization item): `HeaderRenderer`/`FooterRenderer` drop `'use client'` — the sole hook consumer (`HeaderVisibilityFrame`) is extracted to its own client module, and the seven static header block components are directive-free; dist-verified that exactly one chrome file ships the directive. tome-ui's Breadcrumb/Separator/ScrollArea likewise. Consumer pages no longer clientize the full navbar/footer variant set by importing the renderers. blocks-extras gains a `./render/shared` subpath (hero background layer + link-list, hook-free so it serves RSC and client call sites) adopted by the four hero blocks that had verbatim copies.
  • 36e537a: `registerLayer` is now statically imported (forms/intake pattern) instead of lazily `require()`d in ten layer packages' init/register paths. The lazy pattern silently no-ops under Payload's native-ESM CLI (`generate:types` / `generate:importmap`), so layer registration could vanish without error. Packages whose tome-core peer is genuinely optional (economy, ai, gamification) deliberately keep the guarded lazy path; tome-core's `admin-nav/self-register.ts` deliberately keeps its subpath `require()` (documented ESM/CJS dual-cache fix — do not convert).
  • 36e537a: Every package now declares an explicit `sideEffects` field (38 added; motion/engine/forms already correct). Registration-bearing modules (render files' `registerRenderer`, `blocks/*/index.ts` `defineBlock` self-registration, widget `register.ts` files, productHooks, permission self-registrations, print templates, chrome built-in variants) are listed so bundlers can tree-shake everything else WITHOUT dropping import-time registrations — previously the field was unset, which blocked cross-module tree-shaking through the barrels entirely. Never blanket `false` on a package with registration or CSS.
  • 36e537a: Small verified fixes: agency-essentials `Contact` gains its missing `'use client'` (it calls the rich-text adapter hook; direct RSC import crashed). chrome `NavGuard` now dev-warns when its capability gate fails to load while a `requiredCapability` is set (the fail-open contract itself is unchanged and now documented). blocks-core `BLOCK_CATALOG.ts` corrupted entries corrected from real block meta (content-two-column, content-with-corner-notch, signal-ship-card names/descriptions; gallery variants filled) + drift-risk header. Stale docstrings fixed (chrome `HeaderLogo`, blocks-gallery registry header, lms-ui payload JSDoc import path). blocks meta-package backcompat suite now asserts the RENDER registry resolves renderers (previously only descriptor registration was tested — a dropped render import shipped silently).
  • a93f478: Re-render and cleanup fixes: chrome's HeaderClient dead theme state + unreachable effect deleted; Navbar6/7 body-scroll-lock now saves and restores the pre-existing overflow value (LearnerSidebar pattern) instead of clobbering to ''; Navbar7's scroll listener is rAF-throttled. marketing-starter's Testimonial derives the clamped slide index during render instead of an effect. forms' `FieldRenderer` is wrapped in `React.memo` (call-site props verified stable), cutting whole-step re-render work per keystroke in multi-field forms. lms-ui's `useLearnerPrefs` gains optional `initialPrefs` server-seeding (non-breaking) + in-flight dedup with TTL for the unseeded path.
v0.6.2patch

bed3f90: Docs-manifest emitter pipeline (W3 ship-readiness). `@wabbit/tome-blocks-core` now ships a standalone Node ESM CLI at `scripts/emit-docs-manifests.mjs` that emits per-package documentation manifests (index.json, packages/<slug>.json, changelog.json) by reading what packages already carry — READMEs, the payload-free `<pkg>/meta` block-usage barrels, package.json exports maps, and CHANGELOG.md. It is the docs-pipeline sibling of the gallery source extractor and is consumed by host sites at prebuild: `node node_modules/@wabbit/tome-blocks-core/scripts/emit-docs-manifests.mjs --output-dir <dir> --scope <scope.json>`. To let the emitter import block metadata uniformly without dragging Payload config into a build script, the `./meta` payload-free subpath (BlockMetaEntry[]) is extended to the remaining offered blocks packs — agency-essentials, catalog-pack, lms-pack, org-pack, and signal-theme — mirroring the existing editorial-pack / marketing-starter / content-writer / extras barrels. Each block's `BlockMeta` was relocated verbatim into a payload-free sibling meta module and re-imported by its block config; no meta values changed. Every supported-core package additionally adds `CHANGELOG.md` to its published `files` array so the next publish cascade ships changelogs the emitter can read from installed tarballs at prebuild.

  • bed3f90: Docs-manifest emitter pipeline (W3 ship-readiness). `@wabbit/tome-blocks-core` now ships a standalone Node ESM CLI at `scripts/emit-docs-manifests.mjs` that emits per-package documentation manifests (index.json, packages/<slug>.json, changelog.json) by reading what packages already carry — READMEs, the payload-free `<pkg>/meta` block-usage barrels, package.json exports maps, and CHANGELOG.md. It is the docs-pipeline sibling of the gallery source extractor and is consumed by host sites at prebuild: `node node_modules/@wabbit/tome-blocks-core/scripts/emit-docs-manifests.mjs --output-dir <dir> --scope <scope.json>`. To let the emitter import block metadata uniformly without dragging Payload config into a build script, the `./meta` payload-free subpath (BlockMetaEntry[]) is extended to the remaining offered blocks packs — agency-essentials, catalog-pack, lms-pack, org-pack, and signal-theme — mirroring the existing editorial-pack / marketing-starter / content-writer / extras barrels. Each block's `BlockMeta` was relocated verbatim into a payload-free sibling meta module and re-imported by its block config; no meta values changed. Every supported-core package additionally adds `CHANGELOG.md` to its published `files` array so the next publish cascade ships changelogs the emitter can read from installed tarballs at prebuild.
v0.6.1patch

4b2f368: Platform-wide peer-range sweep: every `workspace:*`/`workspace:^` entry in `peerDependencies` replaced with an explicit semver range (`@wabbit/tome-core >=1.0.0 <2.0.0`, `tome-ui >=0.9.0 <1.0.0`, `tome-motion >=0.2.0 <1.0.0`, `tome-catalog >=1.1.0 <2.0.0`, `tome-admin >=0.5.0 <1.0.0`; `tome-crm` ranges standardized to `>=0.2.0 <1.0.0`). The workspace protocol publishes as an **exact-version pin**, so every substrate bump stranded installed dependents — the breakage class proven by marketing@0.1.0/deals@0.1.1 requiring `tome-crm@0.2.0` exactly. devDependencies keep `workspace:*` for the local link. (`@wabbit/tome-admin-pro` got the same source fix but is rc-versioned; it carries the change on its next intentional release.) tome-crm additionally gains a once-per-process **production warning when the capability-registry fallback grants access** — the bootstrap heuristic (any authenticated user passes `crm:read`) now announces itself instead of running silently on sites that forgot to seed capability grants (2026-06-10 audit hardening item). Graph-truth additions (same hygiene wave): tome-deals declares its lazy print integration as an optional peer (`@wabbit/tome-print >=0.1.0 <1.0.0`); tome-intake declares its lazy catalog routing strategy (`@wabbit/tome-catalog >=1.1.0 <2.0.0`, optional). These were undeclared dynamic imports — invisible to consumers and to pnpm's build topology.

  • 4b2f368: Platform-wide peer-range sweep: every `workspace:*`/`workspace:^` entry in `peerDependencies` replaced with an explicit semver range (`@wabbit/tome-core >=1.0.0 <2.0.0`, `tome-ui >=0.9.0 <1.0.0`, `tome-motion >=0.2.0 <1.0.0`, `tome-catalog >=1.1.0 <2.0.0`, `tome-admin >=0.5.0 <1.0.0`; `tome-crm` ranges standardized to `>=0.2.0 <1.0.0`). The workspace protocol publishes as an **exact-version pin**, so every substrate bump stranded installed dependents — the breakage class proven by marketing@0.1.0/deals@0.1.1 requiring `tome-crm@0.2.0` exactly. devDependencies keep `workspace:*` for the local link. (`@wabbit/tome-admin-pro` got the same source fix but is rc-versioned; it carries the change on its next intentional release.) tome-crm additionally gains a once-per-process **production warning when the capability-registry fallback grants access** — the bootstrap heuristic (any authenticated user passes `crm:read`) now announces itself instead of running silently on sites that forgot to seed capability grants (2026-06-10 audit hardening item). Graph-truth additions (same hygiene wave): tome-deals declares its lazy print integration as an optional peer (`@wabbit/tome-print >=0.1.0 <1.0.0`); tome-intake declares its lazy catalog routing strategy (`@wabbit/tome-catalog >=1.1.0 <2.0.0`, optional). These were undeclared dynamic imports — invisible to consumers and to pnpm's build topology.
v0.4.0minor

feat(chrome): NavBar4 submenu `layout` — compact dropdown vs mega-menu Submenu blocks (designVersion 4) gain a **Submenu Layout** field: `mega` (default, unchanged — the full-width panel centered under the bar) or `dropdown` (new — a compact panel pinned directly beneath its own trigger). The `dropdown` layout renders outside the shared Radix mega-menu viewport (a CSS hover / focus-within flyout anchored to its `NavigationMenuItem`), so it sits under its trigger instead of centering under the bar, and it leaves the mega-menu's positioning untouched. Best for a short list of links; `mega` stays best for cover cards / multi-column content. The flyout's trigger mirrors the mega trigger, and its panel surface reads the same `--tome-nav-surface-*` vars, so the `megaMenuBackgroundColor` field recolors it identically. Mobile (the drill-in sheet) is unaffected — it already lists submenu blocks regardless of layout.

  • feat(chrome): NavBar4 submenu `layout` — compact dropdown vs mega-menu Submenu blocks (designVersion 4) gain a **Submenu Layout** field: `mega` (default, unchanged — the full-width panel centered under the bar) or `dropdown` (new — a compact panel pinned directly beneath its own trigger). The `dropdown` layout renders outside the shared Radix mega-menu viewport (a CSS hover / focus-within flyout anchored to its `NavigationMenuItem`), so it sits under its trigger instead of centering under the bar, and it leaves the mega-menu's positioning untouched. Best for a short list of links; `mega` stays best for cover cards / multi-column content. The flyout's trigger mirrors the mega trigger, and its panel surface reads the same `--tome-nav-surface-*` vars, so the `megaMenuBackgroundColor` field recolors it identically. Mobile (the drill-in sheet) is unaffected — it already lists submenu blocks regardless of layout.
v0.3.0minor

feat(chrome): NavBar4 admin-configurable mega-menu panel color New Header field **Mega-menu Background Color** (`megaMenuBackgroundColor`), shown only for designVersion 4. `Default` keeps the standard tome-ui popover surface (light card + border). Any Layer-2 token (Background / Foreground / Primary / Secondary / Accent / Muted / Card / Transparent) paints the NavBar4 mega-menu panel with that color and drops the popover border so the color reads cleanly — e.g. set `Secondary` to match an eggplant bar. Implemented by setting the `--tome-nav-surface-bg` / `--tome-nav-surface-border` CSS vars (added in `@wabbit/tome-ui@0.9.1`) on the NavigationMenu root; the shadow is kept for depth. Requires `@wabbit/tome-ui >= 0.9.1`. No effect on other navbar variants or when the field is left at Default.

  • feat(chrome): NavBar4 admin-configurable mega-menu panel color New Header field **Mega-menu Background Color** (`megaMenuBackgroundColor`), shown only for designVersion 4. `Default` keeps the standard tome-ui popover surface (light card + border). Any Layer-2 token (Background / Foreground / Primary / Secondary / Accent / Muted / Card / Transparent) paints the NavBar4 mega-menu panel with that color and drops the popover border so the color reads cleanly — e.g. set `Secondary` to match an eggplant bar. Implemented by setting the `--tome-nav-surface-bg` / `--tome-nav-surface-border` CSS vars (added in `@wabbit/tome-ui@0.9.1`) on the NavigationMenu root; the shadow is kept for depth. Requires `@wabbit/tome-ui >= 0.9.1`. No effect on other navbar variants or when the field is left at Default.
v0.2.1patch

fix(chrome): NavBar4 mega-menu block fill + nav-link gap Two NavBar4 (designVersion 4) defaults that every consumer was fighting: - **Mega-menu blocks rendered at half width.** A refactor split the starter's single `<BlockRenderer blocks={...}/>` into one `<BlockRenderer>` per block, but `blockRenderer.module.css .wrapper` kept its `repeat(2, minmax(0, 1fr))` grid. Since each wrapper now holds exactly one block (and every block component returns a single root), the block sat in column 1 at half width with an empty column 2 — collapsing featuredImage cover cards to a sliver when combined with a consumer mega-menu grid. The wrapper is now a single column; multi-block layout is owned by `megaContent` / the consumer, not the per-block wrapper. - **Nav links jammed together.** `.desktopList` set no gap and inherited the NavigationMenu primitive's ~4.5px, mashing multi-word labels. It now has a readable `1.5rem` gap (`2rem` at ≥80em). No API or class-name changes. The 18rem featuredImage card cap, default stacked mega-menu layout, and all other variant behavior are unchanged — consumers that want a horizontal card row still grid `megaContent` themselves.

  • fix(chrome): NavBar4 mega-menu block fill + nav-link gap Two NavBar4 (designVersion 4) defaults that every consumer was fighting: - **Mega-menu blocks rendered at half width.** A refactor split the starter's single `<BlockRenderer blocks={...}/>` into one `<BlockRenderer>` per block, but `blockRenderer.module.css .wrapper` kept its `repeat(2, minmax(0, 1fr))` grid. Since each wrapper now holds exactly one block (and every block component returns a single root), the block sat in column 1 at half width with an empty column 2 — collapsing featuredImage cover cards to a sliver when combined with a consumer mega-menu grid. The wrapper is now a single column; multi-block layout is owned by `megaContent` / the consumer, not the per-block wrapper. - **Nav links jammed together.** `.desktopList` set no gap and inherited the NavigationMenu primitive's ~4.5px, mashing multi-word labels. It now has a readable `1.5rem` gap (`2rem` at ≥80em). No API or class-name changes. The 18rem featuredImage card cap, default stacked mega-menu layout, and all other variant behavior are unchanged — consumers that want a horizontal card row still grid `megaContent` themselves.
v0.2.0minor

9e13b9d: feat(chrome): LinkComponent slot on HeaderRenderer `<HeaderRenderer>` now accepts an optional `LinkComponent` prop. When provided, every `<NavLink>` instance — across all 7 navbar variants AND the 5 header block types (CardGrid, CategoryGrid, FeatureList, FeaturedImage, FeaturedBanner, SimpleLinks) — routes through that component instead of `next/link`'s `Link`. Mirrors the existing `LogoComponent` pattern: chrome propagates the override via internal context (`HeaderLinkProvider`), so variant + block code stays unchanged. The slot type `HeaderLinkSlotProps` is the standard anchor surface (`href`, `className`, `children`, plus forwarded HTML attributes), so consumers can drop in `next/link`, a route-transition wrapper, a Remix/Astro `<Link>`, or any other anchor-shaped component without prop translation. Default behavior is unchanged: when `LinkComponent` is omitted, NavLink continues to use `next/link`. External links and `link.newTab` paths still render plain `<a>` regardless of the override (Phase-1.5 behavior preserved verbatim). Resolves the framework-agnostic TODO at `_shared/NavLink.tsx:2`. Unblocks consumers that need View-Transitions-API hooks or per-link side effects (analytics, prefetch policy) without forking the chrome variants.

  • 9e13b9d: feat(chrome): LinkComponent slot on HeaderRenderer `<HeaderRenderer>` now accepts an optional `LinkComponent` prop. When provided, every `<NavLink>` instance — across all 7 navbar variants AND the 5 header block types (CardGrid, CategoryGrid, FeatureList, FeaturedImage, FeaturedBanner, SimpleLinks) — routes through that component instead of `next/link`'s `Link`. Mirrors the existing `LogoComponent` pattern: chrome propagates the override via internal context (`HeaderLinkProvider`), so variant + block code stays unchanged. The slot type `HeaderLinkSlotProps` is the standard anchor surface (`href`, `className`, `children`, plus forwarded HTML attributes), so consumers can drop in `next/link`, a route-transition wrapper, a Remix/Astro `<Link>`, or any other anchor-shaped component without prop translation. Default behavior is unchanged: when `LinkComponent` is omitted, NavLink continues to use `next/link`. External links and `link.newTab` paths still render plain `<a>` regardless of the override (Phase-1.5 behavior preserved verbatim). Resolves the framework-agnostic TODO at `_shared/NavLink.tsx:2`. Unblocks consumers that need View-Transitions-API hooks or per-link side effects (analytics, prefetch policy) without forking the chrome variants.
v0.1.20patch

059db7f: NavLink: resolve href from populated reference slug (was using doc id). Chrome's `link()` field is configured with `maxDepth: 1`, so internal links arrive with the referenced doc populated and `slug` attached. NavLink's `resolveHref` was casting `reference.value` to `{ id: string }` and producing `/${relationTo}/${id}`, which sent every navbar link to `/pages/{mongo-id}` instead of `/{slug}`. New resolution: - `/{slug}` for `relationTo: 'pages'` (the dominant Payload root convention) - `/` when `slug === 'home'` - `/{relationTo}/{slug}` for non-pages collections - `/{relationTo}/{id}` fallback when slug is missing or `value` is a raw id string Consumers whose routing diverges from this contract should pre-resolve to `link.url` upstream of NavLink. `TomeLink.reference.value` widened to include the populated-doc shape so callers no longer need an `as { id: string }` cast.

  • 059db7f: NavLink: resolve href from populated reference slug (was using doc id). Chrome's `link()` field is configured with `maxDepth: 1`, so internal links arrive with the referenced doc populated and `slug` attached. NavLink's `resolveHref` was casting `reference.value` to `{ id: string }` and producing `/${relationTo}/${id}`, which sent every navbar link to `/pages/{mongo-id}` instead of `/{slug}`. New resolution: - `/{slug}` for `relationTo: 'pages'` (the dominant Payload root convention) - `/` when `slug === 'home'` - `/{relationTo}/{slug}` for non-pages collections - `/{relationTo}/{id}` fallback when slug is missing or `value` is a raw id string Consumers whose routing diverges from this contract should pre-resolve to `link.url` upstream of NavLink. `TomeLink.reference.value` widened to include the populated-doc shape so callers no longer need an `as { id: string }` cast.
  • Updated dependencies [1d90b24] - @wabbit/tome-ui@0.6.1