Theme Framework How the LeaseLeads theme frontend works

Layouts

Site chrome the page shell emits — gated by global settings, never by the module loop.

On this page 3

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:

php
// 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', []);
php
// 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.

Note

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:

js
// 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:

js
processor.convert({
  name: 'Footer',
  selector: '.footer',
  loader: () => import('../../layouts/Footer/Footer.js'),
})