SCSS
Rules marked [enforced] are caught automatically by Stylelint (ddev npm run lint:scss).
File organisation
Section titled “File organisation”All SCSS lives under assets/src/scss/. Each directory has a clear purpose:
| Folder | Contents |
|---|---|
abstracts/ | Variables, mixins, functions — produces no CSS output |
base/ | Reset, typography, global element styles |
layout/ | Grid, containers, structural styles |
pages/ | Page-specific styles |
partials/ | Component styles (one file per component) |
vendor/ | Third-party CSS overrides |
New component styles belong in partials/. Import the new partial in site.scss.
File naming [enforced]
Section titled “File naming [enforced]”Name files using lowercase letters and hyphens. Prefix partials with an underscore.
_button.scss ✓_team-member.scss ✓Button.scss ✗teamMember.scss ✗Indentation [enforced]
Section titled “Indentation [enforced]”Use 4 spaces. No tabs.
BEM in SCSS
Section titled “BEM in SCSS”Use & to write BEM selectors, keeping all related styles in one block. The nesting mirrors the HTML structure without creating deeply nested CSS selectors.
.card { padding: 1rem;
&__image { width: 100%; }
&__title { font-size: 1.25rem; }
&__body { color: $colour-text; }
&--featured { border: 2px solid $colour-primary; }}<!-- Corresponding HTML --><article class="card card--featured"> <img class="card__image" src="..." alt="..." /> <h2 class="card__title">Title</h2> <p class="card__body">Body text</p></article>Nesting
Section titled “Nesting”Do not exceed 4 levels of nesting. Most components only need 2–3 levels.
// Good — 2 levels.nav { &__item { display: flex; }}
// Bad — 4 levels deep.nav { &__list { &__item { &__link { color: red; // stop before you get here } } }}Variables
Section titled “Variables”Declare variables in assets/src/scss/abstracts/variables/. Always prefix with the category.
// Good$colour-primary: #e63946;$colour-text: #333333;$font-size-base: 1rem;$font-family-body: 'Inter', sans-serif;$spacing-sm: 0.5rem;$spacing-md: 1rem;$spacing-lg: 2rem;
// Bad — no category prefix$primary: #e63946;$text: #333333;Media queries
Section titled “Media queries”Use a breakpoint mixin from abstracts/mixins/ instead of raw @media blocks.
// Good.card { padding: $spacing-md;
@include breakpoint(md) { padding: $spacing-lg; }}
// Bad.card { padding: 1rem;
@media (min-width: 768px) { padding: 2rem; }}Comments
Section titled “Comments”Use comments to explain non-obvious decisions, not to describe what the code does.
// Good — explains a non-obvious constraint// z-index: 10 keeps the dropdown above the hero image (z-index: 5).dropdown { z-index: 10; }
// Bad — the code already says this// Set the width to 100%.element { width: 100%; }Stylelint
Section titled “Stylelint”Config: .stylelintrc in the theme root. Run before committing:
ddev npm run lint:scss
