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.
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
Fieldclasses (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:hoveron.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:
.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:
@use 'lib/rhythm' as rhythm;
.intro {
@include rhythm.default(sm, md, none, none);
@include rhythm.apply();
.intro__item-content {
padding: 2.5rem 3rem;
}
}
After:
@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 fluidclamp()scale, andrhythm.apply()emits it. Because those defaults are custom properties, the Spacing field can still override them per-instance from the editor — a hardcodedmarginwould 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.
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.
Related
- The rhythm system — gutter vs rhythm, and the fluid scale.
- The state-class contract — the classes JS toggles for you to style.
- Fields → CSS custom properties — the editor's style settings as custom properties.
- SCSS architecture & colocation — where styles live and how they bundle.