A Layout is site chrome: the header, the footer, the cookie banner, the accessibility
widget. It's emitted by the page shell (header.php / footer.php) as part of the
document frame, and it's gated only by global theme settings — never by per-page ACF
content and never by the module loop.
The distinction from a Module is authorial placeability. An author places a Module into page content; a Layout an author only configures, once, in site settings. Its presence is a property of the site, not the page.
Examples: HeaderBar, HeaderTakeover, Footer, CookieComplianceBanner,
Accessibility.
The shell emits it, not the loop
Modules are appended to the_content (or run by a Template's own loop). Layouts are
emitted directly by the shell, and each is guarded by a global setting:
// header.php
if ( empty(get_field('header', 'options')) ) {
return;
}
get_template_part('resources/public/views/layouts/header', 'bar', []);
get_template_part('resources/public/views/layouts/header', 'takeover', []);
// footer.php
$options = ThemeSettings::get('layout-settings.footer')::get();
if ( empty($options) ) {
return;
}
?>
<footer id="footer" class="footer …">
<?php View::layout('footer-top', [ /* … */ ]); ?>
<?php View::layout('footer-bottom', [ /* … */ ]); ?>
</footer>
Read the gate: the footer reads its config from layout-settings.footer — a global
settings tree — not from get_sub_field inside a module row. That's the signature of a
Layout. It renders because the site is configured for it, on every page, regardless of
which Template or Modules the page uses.
Why Layouts are their own category. They look like Modules — a view, an asset, the same Field-driven styling — but they answer a different question. A Module answers "what did the author put on this page"; a Layout answers "what chrome does this site wear". Separating them keeps the module loop free of frame concerns and lets the shell own the document boundary.
The asset
A Layout's asset is an Instantiable exactly like a
Module's — it mixes AutoInitializes, declares its HasX capabilities, and controls its
own subtree:
// resources/public/assets/layouts/HeaderBar/HeaderBar.js
class HeaderBar extends Instantiable {
static selector = '.header-bar'
async init() {
this.#bindEvents()
this.toggleClass()
await this.autoInitializeComponents()
await super.init()
}
toggleClass() {
window.scrollY
? manipulateDOM(() => this.element.classList.add('is-sticky'))
: manipulateDOM(() => this.element.classList.remove('is-sticky'))
}
}
const Final = new Traits(HeaderBar, [
AutoInitializes, FadesIn, HasBackground, HasGrid, HasImage,
AnimatesOnScroll, HasButtons, HasMenu,
])
export { Final as HeaderBar }
HeaderBar toggles an is-sticky state class on scroll —
the SCSS owns what sticky looks like — and inits its child Components (menu, buttons,
images) through autoInitializeComponents(). Nothing here is different from a Module's
asset. The category difference is entirely about how it gets onto the page: the shell
emits it, a global setting gates it.
Layouts register in a loader like any other behaviour — Footer, for instance, has a
Deferred.js entry keyed to .footer:
processor.convert({
name: 'Footer',
selector: '.footer',
loader: () => import('../../layouts/Footer/Footer.js'),
})
Related
- Modules — the author-placed counterpart.
- Templates — the archetype that owns the module region the Layout frames.
- Sub-components &
HasX— how a Layout drives its child Components. - The state-class contract — how
is-stickyand friends drive style.