Theme Framework How the LeaseLeads theme frontend works

SCSS Architecture

Where styles live — one colocated .scss per component, code-split into the bundle by the JS import side-effect.

On this page 4

Styles live next to the thing they style. Every Module and Component keeps a single .scss file in its own directory, right beside its .js and (for views) its .php. There is no central stylesheet you scroll through to find a rule — you open the component.

How you use it

Give a component one SCSS file named after it, and pull it into the bundle from the component's JS with a bare import:

js
// modules/Intro/Intro.js
import "./Intro.scss"

That import has no binding — it exists purely for its side-effect. Vite sees the edge from the JS chunk to the CSS and emits the styles as part of that component's chunk. Because components are presence-gated and code-split, a page only ever downloads the CSS for components it actually renders.

Note

Why the import, not a manifest. Colocation plus the import side-effect makes CSS follow the same per-component code-splitting as JS for free — no separate stylesheet registry to keep in sync, and no styles shipped for components the page doesn't use. The rule is simply: if a component has styles, its JS imports its .scss.

How selectors are structured

Scope everything under the component's root class and nest to mirror the markup. A Module owns its block; elements inside it read as block__element; state and modifiers hang off the block with &:

scss
// modules/Intro/Intro.scss
@use 'lib/rhythm' as rhythm;

.intro {
  @include rhythm.default(sm, md, none, none);
  @include rhythm.apply();

  .intro__item {
    display: grid;
    place-items: center;

    &-content {
      grid-area: pile;
      z-index: 1;
    }

    &:hover {
      .intro__item-content--static {
        opacity: 0;
      }
    }
  }

  .navigation {
    margin-top: 2rem;
  }
}

Keep nesting shallow enough to read the shape of the markup at a glance, and lean on & for state hooks (&:hover, &.is-animated) so a component's behavioural states sit beside the base rules they modify.

Important

The framework does not mandate a naming methodology. The block__element convention above is what the codebase happens to use and what you'll see most often, but BEM (or any other scheme) is not required — how you name selectors within your component is your call. What is fixed is the structure: one colocated file per component, scoped under its root class.

Global styles

A small amount of CSS can't be colocated because it isn't owned by any one component — the reset, base element defaults, and shared @keyframes. That lives in Crucial.scss and ships eagerly on the critical path:

scss
// global/Crucial/Crucial.scss
@keyframes fade {
  to {
    opacity: 1;
    translate: 0 0;
  }
}

@layer reset {
  *, *::before, *::after { box-sizing: border-box; }
  * { margin: 0; }
  // …a modern CSS reset
}

The reset is wrapped in @layer reset so component styles always win over it without a specificity fight, and the globally-emitted fade keyframe is what the animation mixin references by name.