core · Current reference
Site shell contract
Configure the shared Foundry header, navigation, footer, and shell behavior.
Applies to v1.1.2
Component pins for v1.1.2
| Component | Role | Version |
|---|---|---|
| core | runtime | v1.1.2 |
| theme-shield | theme | v0.1.15 |
| docs | reference | v0.1.6 |
| distribution | tooling | v0.3.19 |
Phase: 08-shell-contract-governance Status: Stable (v1)
The Foundry shell contract defines how downstream themes and sites configure site chrome — header, navigation, footer, utility surfaces, and page-level exceptions — without copying shell partials locally.
Why a Shell Contract?
Without a contract, every site ends up with:
- Local copies of
layouts/partials/header/,layouts/partials/footer/, andlayouts/_default/baseof.html - Bespoke template conditions scattered across dozens of partials
- No machine-readable ownership model → AI editors can’t safely touch shell code
- Governance by tribal knowledge rather than validated structure
The shell contract moves this behaviour into typed, schema-backed Core ownership that downstream repos configure rather than copy.
Artifacts
| File | Purpose |
|---|---|
schemas/foundry/shell-manifest.schema.json | Typed contract for all shell surfaces |
schemas/foundry/page-shell.schema.json | Page-level chrome exception fields |
layouts/partials/helpers/shell-model.html | Normalizes the optional site manifest with backward-compatible defaults |
layouts/partials/helpers/page-shell-params.html | Hugo partial that normalizes page front matter into the contract |
layouts/partials/header/site-header.html | Shared header consuming brand, nav, utilities, search, preferences, and CTA fields |
layouts/partials/shell/contact-bar.html | Optional contact/action composition called once by the site base layout |
foundry validate shell | Go-native Core contract validation |
foundry validate shell-overrides | Go-native audit for unallowlisted consumer shell shadows |
Shell Surfaces (shell-manifest)
The shell manifest covers these surfaces:
| Surface | Required | Description |
|---|---|---|
brand | ✓ | Site name, logo assets, home URL |
nav | ✓ | Primary navigation (menu-driven or explicit links) |
utility_nav | — | Secondary nav (language, preferences, auth) |
search | — | Search entry point |
preferences | — | Language/theme/locale toggles |
announcement | — | Top-of-page announcement banner |
primary_cta | — | Header call-to-action button |
contact_bar | — | Optional group of one to three consumer-configured links |
footer | — | Footer columns, legal nav, contact, social |
The shared header consumes the normalized model directly. Without a manifest,
it preserves the established site-title, home-link, and main menu behavior.
Themes still own density, typography, color, and responsive presentation.
The contact bar is deliberately not injected by a universal base layout. A site that wants it calls the portable composition once near body-end:
{{ partial "foundry/shell/contact-bar.html" (dict "page" .) }}
Typed Link Kinds
All link entries in the manifest use a typed kind field:
| Kind | Meaning |
|---|---|
internal | Site-local link (Hugo relURL) |
external | Off-site link (opens with explicit rel) |
anchor | On-page anchor (#section-id) |
menu-driven | Resolved from Hugo menus at build time |
route-owner | Points to the canonical owner of a content section |
Page-Level Chrome Exceptions (page-shell)
Pages declare shell behaviour through front matter fields that are normalized by helpers/page-shell-params.html:
---
title: Landing Page
header_mode: transparent # overlays first content block
footer_mode: minimal # legal links only
hide_site_chrome: false # true = full chrome suppression
route_owner: services # canonical section key
utility_surface: cta-only # show only primary CTA in header
show_breadcrumbs: true # render visible breadcrumbs when shell supports it
noindex: false
suppress_announcement: false
suppress_contact_bar: false
shell_compat_mode: false # set true only during phased migration
---
Field Reference
| Field | Type | Default | Values |
|---|---|---|---|
hide_site_chrome | bool | false | Suppresses header + footer |
header_mode | string | "default" | default, transparent, minimal, hidden |
footer_mode | string | "default" | default, minimal, hidden |
announcement_key | string | — | Maps to data/announcements/{key}.yaml |
suppress_announcement | bool | false | Hides site-level announcement on this page |
suppress_contact_bar | bool | false | Hides the optional contact bar on this page |
route_owner | string | — | Content section key (e.g. services, journal) |
utility_surface | string | "default" | default, none, cta-only, search-only |
show_breadcrumbs | bool | false | Opts the page into visible breadcrumbs |
breadcrumb_home_label | string | site home title | Optional home crumb label |
breadcrumb_label | string | "Breadcrumb" | Accessible nav label |
breadcrumb_include_current | bool | true | Includes current page as final crumb |
noindex | bool | false | Adds <meta name="robots" content="noindex"> |
shell_compat_mode | bool | false | Enables legacy param resolution during migration |
Using the Helper Partial
In your base layout or shell partial, resolve page-shell params once at the top:
{{/* layouts/_default/baseof.html */}}
{{ $shell := partial "foundry/helpers/page-shell-params.html" . }}
{{ if not $shell.hide_site_chrome }}
{{ partial "header/site-header.html" (dict "page" . "shell" $shell) }}
{{ end }}
{{ block "main" . }}{{ end }}
{{ if not $shell.hide_site_chrome }}
{{ partial "footer/site-footer.html" (dict "page" . "shell" $shell) }}
{{ partial "foundry/shell/contact-bar.html" (dict "page" .) }}
{{ end }}
The helper handles:
- Reading typed contract fields from
.Params - Applying defaults for missing fields
- Legacy compat: falls back to older param names during migration
Visible Breadcrumbs
Foundry already emits breadcrumb structured data through SEO schema. For visible page breadcrumbs, call the shared partial from the shell where the site wants breadcrumbs to appear:
{{ partial "foundry/navigation/breadcrumbs.html" (dict "page" . "shell" $shell) }}
The partial renders only when show_breadcrumbs: true and when the current page has more than one crumb. It uses Hugo ancestors, respects breadcrumb_home_label, and marks the current page with aria-current="page".
Navigation Interaction Contract
Foundry Core owns the portable interaction behavior for menus. Themes and sites continue to own menu content, density, visual styling, and information architecture.
The Core header loads assets/js/foundry-navigation.js through
foundry/framework/foundry-navigation.html. The controller provides:
- synchronized
Menu/Closelabels andaria-expandedstate - mobile scroll locking, contained focus, scroll reset, and outside dismissal
- submenu-first Escape handling with focus restoration
- viewport-change cleanup so desktop and mobile state cannot leak across modes
- click activation for touch/coarse pointers and optional hover activation for fine pointers
- pointer-safe submenu state and automatic closure when a navigation link or another header surface is selected
- an
is-scrolledheader state for theme-owned transparent/sticky treatments
The default Core header switches at 48rem. A custom header must declare the
same mobile maximum used by its CSS:
<header
data-foundry-header
data-foundry-nav-breakpoint="899px"
data-foundry-nav-hover="true"
data-foundry-nav-scroll-lock="true"
data-foundry-nav-contain-focus="true">
<button data-foundry-nav-toggle>...</button>
<nav data-foundry-nav>...</nav>
</header>
Submenu buttons use data-foundry-submenu-toggle; their owning list item uses
foundry-nav-item--has-submenu; and the panel uses
data-foundry-submenu. Core applies is-menu-open,
is-mobile-navigation, is-submenu-open, and foundry-nav-open as runtime
state hooks.
Custom shells that shadow the Core header must load the shared controller after their navigation markup:
{{ partial "foundry/framework/foundry-navigation.html" . }}
Sites should not copy the controller into a local inline script. Brand-specific mega-menu columns, mobile content priorities, icons, and CTA copy remain in the theme or site layer.
Shared Layer Tokens
Core exposes a stacking contract so a downstream sticky header cannot cover a search overlay or modal:
| Token | Default | Intended surface |
|---|---|---|
--foundry-layer-sticky | 30 | Sticky in-page controls |
--foundry-layer-nav-backdrop | 35 | Mobile navigation backdrop |
--foundry-layer-header | 40 | Sticky site header |
--foundry-layer-popover | 60 | Submenus and preference popovers |
--foundry-layer-overlay | 100 | Full-screen search and overlays |
--foundry-layer-modal | 120 | Modal dialogs requiring top priority |
Themes may raise the scale, but must preserve that ordering. Override the
tokens instead of patching individual Core selectors with unrelated
z-index values.
Ownership Model
| Layer | Owns |
|---|---|
| Foundry Core | Shell schemas, typed surface definitions, validators, helper partial |
| Theme | Shell variant implementations, menu density, responsive visual treatment, and brand presentation |
| Site | Shell configuration, menu keys/grouping, CTA copy, contact info, and route priorities |
Sites configure shell behaviour through the contract. They do not copy shell partials unless:
- The needed behaviour cannot be expressed through the contract
- The shadow is documented in
.planning/shadow-copy-review.mdwith a reason and migration phase - The shadow passes the override validator with an explicit allowlist entry
Validation
foundry validate shell --root /path/to/likestyle-foundry-core --json
foundry validate shell-overrides --site /path/to/downstream-repo --json
foundry validate shell-overrides --site /path/to/downstream-repo \
--allowlist /path/to/downstream-repo/.planning/shell-shadow-allowlist.json \
--json
Core’s Go validators are the only supported shell-contract maintenance path. See CLI Reference and Tooling Policy.
Shell Shadow Migration Path
If a downstream repo has temporary shell shadows (common during initial adoption):
- Document each shadow in
.planning/shadow-copy-review.mdunder “Shell Shadows” with a reason and target phase. - Create
.planning/shell-shadow-allowlist.jsonlisting the shadow paths. - Run
foundry validate shell-overrides --allowlist <file>in CI to catch any new unreviewed shadows. - Retire the shadow in the designated phase by adopting the Core contract field or theme variant.
- Remove the allowlist entry and confirm the validator exits 0.
Compatibility Mode
During migration, set shell_compat_mode: true in page front matter to enable legacy param resolution alongside the new contract. This allows incremental per-page migration without a big-bang rewrite. Remove shell_compat_mode once all pages in a section are migrated.
What This Phase Does Not Do
- Move high-consideration service proof site route labels, CTA copy, or contact info into Core defaults
- Implement final header/footer rendering for any specific theme
- Expand into runtime personalization or non-shell product work
- Promote any shell primitive to “shield” status before a second consumer proves genericity
These are targets for Phase 09 (Theme Capability & Shared Shell Rendering) and Phase 10 (high-consideration service proof site Pilot & Exception Retirement).