Skip to content
← All posts
5 min read Dawid Skłodowski

SASS architecture: from spaghetti to a design system

SASS gives you power; architecture is what stops you abusing it. A deep-dive on structuring stylesheets so they scale — design tokens, the 7-1 folder pattern, BEM, managing specificity, and thinking in components instead of pages.

A while back I wrote about the features SASS gives you — variables, mixins, nesting. This post is about the thing that determines whether those features help or hurt: architecture. SASS without a plan produces something worse than plain CSS — a tangle of deeply nested rules, !important wars, and a 4,000-line stylesheet nobody dares touch. SASS with a plan produces a maintainable design system that a team can extend for years. The difference is not the tool; it is how you organise it. Here is the structure we have arrived at.

The goal: a system, not a pile of pages

The mental shift that fixes most CSS problems is to stop thinking in pages and start thinking in components. The old way writes .homepage .sidebar .promo h2 { ... } — styles scoped to where they appear, which means every new page re-solves the same problems and the stylesheet grows linearly with the site. The component way writes a .card once and reuses it on the homepage, the product page, and the dashboard. Your CSS stops being a description of pages and becomes a library of reusable parts — a design system. Everything below is in service of that shift.

Design tokens: a single source of truth

The foundation is a layer of variables that name your design decisions — colours, spacing, type scale, breakpoints — in one place that everything else references:

// _tokens.scss
$color-brand:   #b00020;
$color-ink:     #1a1a1a;
$color-muted:   #6b7280;

$space-unit:    0.5rem;
$space-1:       $space-unit;        // 0.5rem
$space-2:       $space-unit * 2;    // 1rem
$space-3:       $space-unit * 3;    // 1.5rem

$font-scale:    1.25;
$breakpoint-m:  48rem;
$breakpoint-l:  64rem;

These are design tokens — the vocabulary of your system. No component should ever hard-code #b00020 or 17px; it references $color-brand and a spacing token. The payoff is enormous: a rebrand is an edit to one file, your spacing stays on a consistent rhythm because everything is a multiple of one unit, and the design becomes consistent by construction rather than by everyone remembering the right hex code. A derived layer of semantic tokens ($color-danger: $color-brand) adds a level of meaning on top, so components reference intent rather than raw values.

The 7-1 pattern: a place for everything

As the stylesheet grows you need a folder structure, and the widely-adopted “7-1” pattern is a sensible default — partials grouped into folders, all pulled together by one manifest:

styles/
├── abstracts/    _tokens.scss, _mixins.scss, _functions.scss
├── base/         _reset.scss, _typography.scss
├── components/   _card.scss, _button.scss, _form.scss
├── layout/       _header.scss, _footer.scss, _grid.scss
├── pages/        _home.scss          (page-specific overrides only)
├── themes/       _dark.scss
└── application.scss   (imports everything, in order)

The order of imports matters and follows increasing specificity: abstracts (no output, just tools), then base (element defaults), then layout and components, then the rare page-specific override. Most of your real CSS lives in components/ — one file per component — and pages/ stays nearly empty, because a well-built system needs page-specific styles only at the edges. When pages/ starts filling up, that is the smell that you are styling by page again instead of building components.

BEM: naming that keeps specificity flat

The hardest problem in CSS at scale is specificity — the cascade means a more specific selector wins, so as rules accumulate you end up fighting your own stylesheet, escalating selectors and eventually reaching for !important. BEM (Block, Element, Modifier) defeats this with a naming convention that keeps almost every selector a single, flat class:

// Block: the component. Element: a part of it. Modifier: a variant.
.card { /* ... */ }
.card__title { /* ... */ }
.card__body { /* ... */ }
.card--featured { /* ... */ }   // a variant of the whole card
.card.card--featured
  %h2.card__title= title
  .card__body= body

Because every rule is one class, every selector has the same low specificity, so ordering and overriding become predictable — a modifier class just adds to the base class, no specificity battle. BEM looks verbose, and the first reaction is always “those class names are ugly”, but it buys the single most valuable property a large stylesheet can have: you can read a class name and know exactly what it styles and where it lives, and you can override it without a fight. Combined with SASS’s &, the source stays neatly grouped while the output stays flat:

.card {
  padding: $space-3;
  &__title { color: $color-ink; }
  &__body  { color: $color-muted; }
  &--featured { border: 2px solid $color-brand; }
}

Rules that keep it healthy

A few disciplines hold the whole thing together over time:

  • Keep nesting shallow. Nest for the & modifier pattern, never to mirror the DOM. A selector more than two levels deep is a future specificity problem; flatten it by giving the element its own BEM class.
  • No magic numbers, no hard-coded values. Every colour, space, and breakpoint is a token. If you find yourself typing margin-top: 23px, stop — either it belongs on the scale or the design needs a decision, not a one-off number.
  • One component, one file, one responsibility. A component file styles its block and nothing else. It should not reach out and style its neighbours.
  • Prefer mixins to @extend. As covered before, @extend’s selector-grouping has surprising specificity effects; mixins copy declarations predictably. Reserve @extend (with placeholders) for genuinely static shared chunks.
  • Document the system. A living style guide — a page that renders every component in its variants — turns your CSS from tribal knowledge into a browsable catalogue, and is the artefact that makes it a true design system rather than just well-organised files.

Verdict

The features of SASS are easy; the architecture is what makes them pay off. Anchor everything in design tokens, organise components into a clear folder structure, keep selectors flat and predictable with BEM, and think in reusable components rather than pages. Do that and your stylesheet stops growing linearly with your site and starts behaving like a system — one where adding a feature means composing existing parts, not bolting on another thousand lines. That is the line between CSS as a liability and CSS as an asset, and in 2015, with design systems becoming the way serious teams work, it is exactly the discipline worth building now.