Theme Framework How the LeaseLeads theme frontend works

Overview

What the framework is, and the four ideas that shape every file in it.

On this page 8

This is the LeaseLeads theme framework: a convention-driven way to build WordPress themes where an editor composes a page out of Modules, and each Module ships its own markup, behaviour, and styles — loaded only when it's actually on the page.

The framework is generic. Novaa is its reference implementation — the theme these docs are drawn from — but almost nothing here is Novaa-specific. When a page describes something that is Novaa-only, it says so.

Note

A few pieces are the reference theme's own, not framework canon. The notched-border Component (the Border class) is the clearest example — it exists because Novaa's design uses corner-shaped borders. Treat anything flagged this way as an example of the pattern, not a rule you must reproduce.

Two mirrored halves

Every part of the theme has a place in two parallel trees that share one taxonomy:

  • Views — PHP partials under resources/public/views/{type}/ that render markup.
  • Assets — JS and SCSS under resources/public/assets/{type}/ that add behaviour and style.

A Module called Intro is a view at views/modules/intro.php and an asset folder at assets/modules/Intro/ (Intro.js + Intro.scss). The view emits the HTML; the asset brings it to life. Because the two halves are colocated and named the same way, you always know where the other half lives.

The taxonomy has nine categories — Module, Component, Layout, Template, Widget, Global, Utility, Vendor, and lib. The next page, Directory structure & the taxonomy, maps all nine.

Four ideas

Everything downstream follows from four decisions.

Convention over configuration

You wire almost nothing by hand. A Module is registered in config/modules.php; its view is found by name (View::module('intro', …)views/modules/intro.php); its behaviour is found by a static selector matching an element on the page; its class is resolved by an export alias that must equal its registered name. Follow the naming and the machinery finds each piece. Break the naming and it silently doesn't load.

Presence-gated lazy loading

This is the organizing performance principle. An asset loads only if its selector matches an element on the page. The two boot orchestrators (Crucial, Deferred) and every host's component discovery all work this way — a descriptor names a selector and a dynamic import(), and the import only fires when the selector hits:

js
processor.convert({
  name: 'Reviews',
  selector: '.reviews',
  loader: () => import('../../modules/Reviews/Reviews.js'),
})

Over Vite's per-component code-splitting, that means a page ships only the JS and CSS for the Modules it actually contains. Nothing else is downloaded.

The editor owns content, the page owns behaviour

An author working in WordPress chooses what is on the page and how it looks — text, images, colours, spacing — through ACF fields. Those choices cross into the frontend as data (data-* attributes and data islands) and as style (CSS custom properties emitted by Field classes). The frontend developer owns how it behaves: carousels, disclosure, scroll animation. Neither side reaches into the other. A designer never edits PHP to restyle; a developer never hard-codes an editor's copy.

JS drives state, SCSS expresses it

Behaviour and appearance are strictly separated. JavaScript toggles state classesis-open, is-animated, is-settled, loaded — and never sets styles directly. The colocated SCSS owns every pixel of how those states look.

Important

"JS drives state" is not "don't write JS." Authoring JS is expected. The rule is narrow: JS must not define styles. It flips a class; the SCSS keyed off that class decides what the class means visually.

Where to go next