SASS/SCSS

SASS Best Practices & Performance

20 min Lesson 27 of 30

Introduction to SASS Best Practices

Writing clean, maintainable, and performant SASS code is essential for long-term project success. In this comprehensive lesson, we'll explore industry best practices, naming conventions, performance optimization techniques, and common pitfalls to avoid. These guidelines will help you write SASS code that is easy to understand, maintain, and scales well in large projects.

Writing Maintainable SASS Code

Maintaiability is crucial for projects that evolve over time and involve multiple developers.

Keep Files Small and Focused

Each partial should have a single, clear responsibility:

Good Structure

// _buttons.scss - Only button styles
.btn {
  // Button base styles
}

.btn-primary {
  // Primary button variant
}

// _forms.scss - Only form styles
.form-group {
  // Form group styles
}

.form-control {
  // Form control styles
}
Warning: Avoid creating "kitchen sink" files that contain unrelated styles. A file named _utilities.scss that contains buttons, forms, and typography is hard to maintain.

Use Meaningful Comments

Comment your code to explain the "why" not the "what":

Good vs Bad Comments

// Bad: States the obvious
.btn {
  padding: 10px 20px; // Add padding
}

// Good: Explains reasoning
.btn {
  // Extra padding compensates for optical illusion
  // caused by rounded corners making buttons appear smaller
  padding: 10px 20px;
}

// Good: Documents complex calculations
$container-width: 1200px;
$column-count: 12;
$gutter-width: 30px;

// Calculate column width accounting for gutters
// Formula: (container - (gutters * (columns - 1))) / columns
$column-width: ($container-width - ($gutter-width * ($column-count - 1))) / $column-count;

Naming Conventions

Consistent naming conventions improve code readability and prevent naming conflicts.

Variable Naming

Variable Naming Patterns

// Use semantic names, not presentational
$brand-primary: #3498db;      // Good
$blue: #3498db;               // Avoid - what if the brand color changes?

// Use hierarchical naming for related values
$font-size-base: 16px;
$font-size-small: 14px;
$font-size-large: 18px;
$font-size-xlarge: 24px;

// Group related variables with prefixes
$spacing-xs: 4px;
$spacing-sm: 8px;
$spacing-md: 16px;
$spacing-lg: 24px;
$spacing-xl: 32px;

// Use descriptive names for z-index layers
$z-index-dropdown: 1000;
$z-index-modal: 1050;
$z-index-tooltip: 1070;

// Boolean variables should be questions
$has-border: true;
$is-responsive: true;
$should-animate: false;

Mixin and Function Naming

Mixin and Function Names

// Mixins: Use verbs describing what they do
@mixin center-content() { }       // Good
@mixin clearfix() { }             // Good
@mixin content-center() { }       // Less clear

// Functions: Name like functions in programming
@function calculate-rem($px) { }  // Good
@function get-color($name) { }    // Good
@function rem($px) { }            // Good (short utility functions)

// Avoid generic names
@mixin utility() { }              // Too vague
@function process($value) { }     // What does it process?

File Naming

File Naming Conventions

// Partials start with underscore
_variables.scss
_mixins.scss
_buttons.scss

// Main files have no underscore
main.scss
admin.scss

// Use kebab-case for multi-word files
_button-groups.scss
_dropdown-menus.scss
_form-validation.scss

// Organize with prefixes if needed
_component-card.scss
_component-modal.scss
_layout-header.scss
_layout-footer.scss

Maximum Nesting Depth

Deep nesting creates overly specific selectors that are hard to override and increase file size.

The 3-4 Level Rule

Nesting Depth Examples

// Bad: Too deep (5 levels)
.navigation {
  .menu {
    .item {
      .link {
        .icon {
          color: red; // Generates: .navigation .menu .item .link .icon
        }
      }
    }
  }
}

// Good: Maximum 3-4 levels
.navigation {
  .menu-item {
    // Use direct descendant when appropriate
    > .link {
      color: blue;

      .icon {
        margin-right: 8px;
      }
    }
  }
}

// Better: Use BEM to avoid deep nesting
.nav { }
.nav__menu { }
.nav__item { }
.nav__link { }
.nav__icon { }
Tip: If you find yourself nesting more than 3-4 levels, consider using BEM methodology or creating a new component.

When Nesting is Appropriate

Good Use Cases for Nesting

// 1. Pseudo-classes and pseudo-elements
.button {
  &:hover {
    background: blue;
  }

  &::before {
    content: "";
  }
}

// 2. Media queries
.card {
  width: 100%;

  @media (min-width: 768px) {
    width: 50%;
  }
}

// 3. State classes
.modal {
  display: none;

  &.is-open {
    display: block;
  }
}

// 4. Parent selectors with modifiers
.alert {
  padding: 16px;

  &--success {
    background: green;
  }

  &--error {
    background: red;
  }
}

Avoiding @extend Pitfalls

While @extend can be useful, it has several gotchas that can cause unexpected behavior.

Problems with @extend

@extend Issues

// Problem 1: Creates unexpected selector combinations
.error {
  color: red;
}

.serious-error {
  @extend .error;
  font-weight: bold;
}

// Generates:
// .error, .serious-error { color: red; }
// .serious-error { font-weight: bold; }

// But if you have:
.error.icon { }

// It also generates:
// .serious-error.icon { } <-- Unexpected!

// Problem 2: Can't extend across media queries
@media (min-width: 768px) {
  .button {
    @extend .btn; // Error! Can't extend outside media query
  }
}

// Problem 3: Cascade and specificity issues
.component {
  .message {
    color: blue;
  }
}

.alert {
  @extend .message; // Where does this extend to?
}

Alternatives to @extend

Better Approaches

// Alternative 1: Use mixins instead
@mixin error-styles {
  color: red;
  border: 1px solid red;
}

.error {
  @include error-styles;
}

.serious-error {
  @include error-styles;
  font-weight: bold;
}

// Alternative 2: Use utility classes in HTML
<div class="error font-bold"></div>

// Alternative 3: Use placeholder selectors (if you must extend)
%error-styles {
  color: red;
  border: 1px solid red;
}

.error {
  @extend %error-styles;
}

.serious-error {
  @extend %error-styles;
  font-weight: bold;
}
Warning: Many SASS experts recommend avoiding @extend entirely in favor of mixins, which are more predictable and don't have the same cascade issues.

Keeping Selectors Simple and Low-Specificity

High specificity makes styles hard to override and leads to !important abuse.

Specificity Examples

Reducing Specificity

// Bad: Overly specific (specificity: 0,0,4,0)
.sidebar .widget .title .link {
  color: blue;
}

// Good: Lower specificity (0,0,1,0)
.widget-link {
  color: blue;
}

// Bad: ID selectors have high specificity
#header .nav .item { } // specificity: 0,1,2,0

// Good: Use classes
.header-nav-item { }   // specificity: 0,0,1,0

// Bad: Combining too many selectors
nav.primary-nav ul.nav-list li.nav-item a.nav-link { }

// Good: Keep it simple
.nav-link { }

Avoiding !important

!important Alternatives

// Bad: Using !important as a quick fix
.button {
  background: blue;
}

.special-button {
  background: red !important; // Starts an !important war
}

// Good: Increase specificity properly or restructure
.button {
  background: blue;
}

.button.special {
  background: red; // More specific naturally
}

// Or refactor your selectors
.btn-primary {
  background: blue;
}

.btn-special {
  background: red; // Same specificity, source order wins
}

Output File Size Optimization

Optimizing your SASS code can significantly reduce the final CSS file size.

Techniques for Smaller Output

Optimization Techniques

// 1. Use shorthand properties
// Bad
.box {
  margin-top: 10px;
  margin-right: 20px;
  margin-bottom: 10px;
  margin-left: 20px;
}

// Good
.box {
  margin: 10px 20px;
}

// 2. Combine selectors with same properties
// Bad
.header {
  font-size: 16px;
}
.footer {
  font-size: 16px;
}

// Good
.header,
.footer {
  font-size: 16px;
}

// 3. Avoid generating unused styles
// Bad: Generates 12 classes you may not use
@for $i from 1 through 12 {
  .col-#{$i} {
    width: percentage($i / 12);
  }
}

// Good: Only generate what you need
$columns: (1, 2, 3, 4, 6, 12);
@each $col in $columns {
  .col-#{$col} {
    width: percentage($col / 12);
  }
}

// 4. Use CSS custom properties for runtime changes
// Bad: Generates separate class for each color
@each $name, $color in $colors {
  .bg-#{$name} {
    background: $color;
  }
}

// Better: Use CSS variables
:root {
  @each $name, $color in $colors {
    --color-#{$name}: #{$color};
  }
}

.bg-primary {
  background: var(--color-primary);
}

Avoiding Too Many Generated Selectors

Be cautious with loops and maps that generate large numbers of selectors.

Selector Generation

// Bad: Generates hundreds of classes
@for $i from 1 through 100 {
  .mt-#{$i} {
    margin-top: #{$i}px;
  }
  .mb-#{$i} {
    margin-bottom: #{$i}px;
  }
  // ... generates 200+ classes
}

// Good: Use a spacing scale
$spacings: (
  0: 0,
  1: 4px,
  2: 8px,
  3: 16px,
  4: 24px,
  5: 32px
);

@each $name, $value in $spacings {
  .mt-#{$name} { margin-top: $value; }
  .mb-#{$name} { margin-bottom: $value; }
}

// Even better: Consider CSS custom properties
:root {
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 16px;
}

.mt-3 {
  margin-top: var(--space-3);
}

Linting with stylelint

stylelint helps enforce consistent coding standards and catch errors automatically.

Installing stylelint

Installation

npm install --save-dev stylelint stylelint-config-standard-scss

stylelint Configuration

.stylelintrc.json

{
  "extends": "stylelint-config-standard-scss",
  "rules": {
    "max-nesting-depth": 3,
    "selector-max-compound-selectors": 4,
    "selector-max-specificity": "0,4,0",
    "declaration-no-important": true,
    "selector-no-qualifying-type": true,
    "selector-class-pattern": "^[a-z][a-z0-9]*(-[a-z0-9]+)*$",
    "color-named": "never",
    "scss/at-extend-no-missing-placeholder": true,
    "scss/selector-no-redundant-nesting-selector": true,
    "scss/at-import-partial-extension": "never"
  }
}

Running stylelint

package.json Scripts

{
  "scripts": {
    "lint:scss": "stylelint \"src/**/*.scss\"",
    "lint:scss:fix": "stylelint \"src/**/*.scss\" --fix",
    "prelint": "npm run lint:scss"
  }
}

Code Review Checklist for SASS

Use this checklist when reviewing SASS code:

SASS Code Review Checklist

□ Naming Conventions
  □ Variables use semantic names (not presentational)
  □ Mixins and functions have clear, descriptive names
  □ Files follow naming convention (_partial.scss)

□ Structure
  □ Files are focused and have single responsibility
  □ Nesting depth doesn't exceed 3-4 levels
  □ Proper use of @use/@forward (not @import)

□ Performance
  □ No excessive selector generation
  □ Loops and maps are necessary and efficient
  □ No unused code or commented-out blocks

□ Maintainability
  □ Code is DRY (Don't Repeat Yourself)
  □ Complex calculations are commented
  □ Magic numbers are replaced with variables

□ Best Practices
  □ Minimal use of @extend (prefer mixins)
  □ No overly specific selectors
  □ No !important declarations
  □ CSS output is optimized and minified

□ Compatibility
  □ Vendor prefixes handled by autoprefixer
  □ Browser support requirements met
  □ No SASS syntax that doesn't compile

Common Anti-Patterns to Avoid

Anti-Patterns

// 1. Color manipulation without variables
.button {
  background: lighten(#3498db, 10%); // Hard to track
}
// Better: Use semantic color variables
$btn-bg: $brand-primary;
$btn-bg-hover: lighten($btn-bg, 10%);

// 2. Magic numbers
.header {
  padding-top: 73px; // Why 73? Where did this come from?
}
// Better: Use meaningful variables
$header-padding: $spacing-lg + $spacing-sm; // Clear calculation

// 3. Repeating calculations
.col-6 { width: percentage(6 / 12); }
.col-4 { width: percentage(4 / 12); }
// Better: Use a function
@function col-width($cols) {
  @return percentage($cols / 12);
}

// 4. Mixing concerns in one file
// _utilities.scss containing buttons, forms, typography, etc.
// Better: Separate into focused files

// 5. Overly generic class names
.wrapper { }
.container { }
.item { }
// Better: Be specific about purpose
.card-wrapper { }
.article-container { }
.menu-item { }

Exercise 1: Refactor for Maintainability

Refactor this poorly written SASS code:

.nav {
  .list {
    .item {
      .link {
        .icon {
          color: #3498db !important;
        }
        .text {
          font-size: 14px;
        }
      }
    }
  }
}

.footer {
  .list {
    .item {
      .link {
        .icon {
          color: #3498db !important;
        }
        .text {
          font-size: 14px;
        }
      }
    }
  }
}

Apply best practices: reduce nesting, remove !important, use variables, eliminate duplication.

Exercise 2: Optimize Generated Selectors

Optimize this code that generates too many selectors:

@for $i from 1 through 50 {
  .text-#{$i} {
    font-size: #{$i}px;
  }
}

@each $color in (red, blue, green, yellow, orange, purple, pink, brown, gray, black, white) {
  .color-#{$color} {
    color: $color;
  }
  .bg-#{$color} {
    background: $color;
  }
}

Reduce the number of generated classes to only what's necessary, use a proper scale, and consider alternatives like CSS custom properties.

Exercise 3: Set Up stylelint

Configure stylelint for your project:

  1. Install stylelint and stylelint-config-standard-scss
  2. Create a .stylelintrc.json configuration file
  3. Add rules for: max nesting depth (3), no !important, class naming pattern (kebab-case), and no missing placeholder extends
  4. Add npm scripts for linting and auto-fixing
  5. Run the linter on your SASS files and fix any violations