Skip to content

SCSS

Rules marked [enforced] are caught automatically by Stylelint (ddev npm run lint:scss).

All SCSS lives under assets/src/scss/. Each directory has a clear purpose:

FolderContents
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.

Name files using lowercase letters and hyphens. Prefix partials with an underscore.

_button.scss ✓
_team-member.scss ✓
Button.scss ✗
teamMember.scss ✗

Use 4 spaces. No tabs.

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.scss
.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>

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
}
}
}
}

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;

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;
}
}

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%; }

Config: .stylelintrc in the theme root. Run before committing:

Terminal window
ddev npm run lint:scss