Skip to content
Muhammet Şafak
tr
Interface 4 min read

Writing Maintainable CSS with Sass

How I bring structure to growing stylesheets using Sass variables, nested rules, and file partials.


CSS is manageable when it lives in a single file. As the project grows, that file balloons — you’re searching for color codes, and every time you touch one thing, something else breaks. At that point, thinking “there has to be a better way” becomes inevitable.

Sass (Syntactically Awesome Style Sheets) is a preprocessor that adds variables, nesting, mixins, and modularity on top of standard CSS. The .scss files you write are compiled into plain .css — the browser only ever sees CSS.

In this post I’ll walk through the Sass features that have made the biggest difference in my projects.

Installation

With Node.js installed, the node-sass package does the job:

npm install --save-dev node-sass

I add a build script to package.json:

{
    "scripts": {
        "sass": "node-sass src/scss/main.scss public/css/main.css --watch"
    }
}

The --watch flag monitors files and recompiles automatically on every change.

Variables

Keeping color, typography, and spacing values in variables means a single change propagates everywhere:

// _degiskenler.scss

$renk-birincil:   #3498db;
$renk-ikincil:    #2c3e50;
$renk-tehlike:    #e74c3c;
$renk-basari:     #27ae60;

$font-boyutu-kucuk:   12px;
$font-boyutu-normal:  16px;
$font-boyutu-buyuk:   20px;

$bosluk-kucuk:   8px;
$bosluk-orta:    16px;
$bosluk-buyuk:   32px;

$kenar-yari:     4px;

I import this file everywhere. When I need to change the primary color, I update $renk-birincil in one place and it reflects across the entire UI automatically.

On one project a client changed their brand color mid-development. Without Sass variables that would have taken hours; a single value update rippled through the whole interface. Since that experience I’ve made it a habit to define variables broadly from the start of every new project.

Nested Rules

In plain CSS you have to repeatedly write related selectors like .card, .card-title, .card-content. With Sass I can nest them:

.kart {
    background: #fff;
    border: 1px solid #ddd;
    border-radius: $kenar-yari;
    padding: $bosluk-orta;

    .kart-baslik {
        font-size: $font-boyutu-buyuk;
        font-weight: bold;
        margin-bottom: $bosluk-kucuk;
        color: $renk-ikincil;
    }

    .kart-icerik {
        font-size: $font-boyutu-normal;
        color: #555;
    }

    &:hover {
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    }
}

The & symbol refers to the parent selector — it compiles to .kart:hover. The output CSS is identical to what you’d write by hand, but the source file is far more readable.

One thing I watch out for: don’t go overboard with nesting. Four or five levels deep makes the generated CSS complex and leads to specificity problems. When I find myself going beyond three levels I usually step back and reconsider the component’s structure — deep nesting is often a sign that you’ve lost sight of the underlying HTML.

Grouping Repeated Rules with Mixins

A mixin lets you encapsulate a repeating block of CSS under a single definition. Here’s one I reach for constantly with media queries:

@mixin mobil {
    @media (max-width: 768px) {
        @content;
    }
}

@mixin tablet {
    @media (min-width: 769px) and (max-width: 1024px) {
        @content;
    }
}

// Usage
.kutu {
    width: 300px;

    @include mobil {
        width: 100%;
    }

    @include tablet {
        width: 50%;
    }
}

This lets me keep each component’s responsive behavior right next to its styles, instead of hunting through a separate media-query block at the bottom of the file.

Splitting Files into Partials

Instead of one monolithic main.scss I move each concern into its own file. Files prefixed with an underscore (partials) are never compiled on their own — they’re only included via @import:

scss/
├── _degiskenler.scss
├── _sifirla.scss
├── _tipografi.scss
├── _butonlar.scss
├── _formlar.scss
├── _kart.scss
├── _navigasyon.scss
└── main.scss

main.scss assembles them all:

@import 'degiskenler';
@import 'sifirla';
@import 'tipografi';
@import 'butonlar';
@import 'formlar';
@import 'kart';
@import 'navigasyon';

The build still outputs a single main.css, so there’s no increase in HTTP requests. But the source files are organized and easy to find.

Reducing Duplication with Extend

When two selectors need to share the same set of properties, @extend comes in handy:

%bildirim-temel {
    padding: $bosluk-kucuk $bosluk-orta;
    border-radius: $kenar-yari;
    font-size: $font-boyutu-kucuk;
}

.bildirim-bilgi {
    @extend %bildirim-temel;
    background: lighten($renk-birincil, 40%);
    color: $renk-birincil;
}

.bildirim-tehlike {
    @extend %bildirim-temel;
    background: lighten($renk-tehlike, 40%);
    color: $renk-tehlike;
}

A placeholder selector defined with % produces no CSS output on its own — it only takes effect when pulled in via @extend.

When deciding between @extend and a mixin I ask myself: will these shared styles always be used together? If yes, @extend makes sense. If I need parametric variation with different values, a mixin is the better fit. Because @extend merges selectors in the generated CSS, using it carelessly can produce unexpected selector combinations.


Once I started using Sass I never wanted to go back to plain CSS. Variables and a modular structure alone are a massive win. Using mixins and extend well takes a bit of practice, but once you internalize the principles, the CSS you write becomes dramatically easier to maintain.

Tags: #CSS#Sass
Share:

Comments

Sign in with your GitHub account to join the discussion. Comments are stored in GitHub Discussions.

Related Posts

Search the site

Start typing to search posts, projects and pages.

Esc to close Powered by Pagefind