Theme Framework How the LeaseLeads theme frontend works

Restyle a Module to match a design

The everyday loop — edit colocated SCSS, respect the two inputs you don't own, and reach for the rhythm system.

On this page 5

Most design work is this loop: open a module's colocated SCSS, adjust it to match a comp, reload. The module already renders and behaves — you are only changing how it looks. The rules below keep your edits inside the framework's grain.

1. Find the colocated SCSS

Styles live beside the module's JS, imported for the side-effect. For Intro that's resources/public/assets/modules/Intro/Intro.scss. Everything visual for the module is in that one file; you rarely touch anything else.

Note

There is no central stylesheet to hunt through. One module, one SCSS file — colocation is the whole SCSS architecture.

2. Respect the two inputs you don't own

Before changing anything, know what is already feeding the element. Two streams arrive from outside the SCSS, and fighting them is the usual cause of "why won't this override":

  • CSS custom properties from the Fields. The editor's Field classes (Background, Border, Spacing, Navigation…) print inline custom properties and utility classes onto the element — that is how an author's colour, spacing, and border choices reach you. Consume them; don't hardcode over them. See Fields → CSS custom properties.
  • State classes the JS toggles. The JS drives behaviour by adding classes (is-dismissed, loaded, is-animated, and Intro's hover parity via :hover on .intro__item). You express those states; the JS never sets styles itself. See The state-class contract.

In Intro.scss you can see both at work — the hover state swaps card faces, and the padding sits on top of whatever background the Background field emitted:

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

3. Make the change

Say the comp wants roomier cards and more breathing room around the whole module. Reach for the rhythm system for the outer spacing and a custom property for the inner padding.

Before:

scss
@use 'lib/rhythm' as rhythm;

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

  .intro__item-content {
    padding: 2.5rem 3rem;
  }
}

After:

scss
@use 'lib/rhythm' as rhythm;

.intro {
  @include rhythm.default(md, lg, none, none);   // larger top/bottom gutter
  @include rhythm.apply();

  .intro__item-content {
    padding: var(--intro-card-padding, 3rem 3.5rem);
  }
}

Two things happened, both deliberately in-grain:

  • Spacing went through rhythm, not raw margins. rhythm.default(md, lg, none, none) sets the module's default gutter (space around the module) from the fluid clamp() scale, and rhythm.apply() emits it. Because those defaults are custom properties, the Spacing field can still override them per-instance from the editor — a hardcoded margin would have severed that.
  • The inner padding became a custom property with a fallback. Prefer CSS custom properties over Sass variables: they cascade, they're overridable per-instance, and they survive into the browser where the Fields also speak. var(--intro-card-padding, 3rem 3.5rem) matches the comp today and leaves a named hook for later.

4. Keep styles out of the JS

If the design needs a new visual state, add the class toggle in the JS and express the look in SCSS — never assign styles from JavaScript. The JS side of a restyle is at most a one-line classList.add('is-…'); the appearance is yours, in the .scss.

Warning

Reaching for element.style.… in a component is the anti-pattern the state-class contract exists to prevent. It puts design in a file designers don't edit and breaks the Fields' custom-property cascade.