SASS Best Practices & Performance
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
}
_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 { }
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;
}
@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:
- Install stylelint and stylelint-config-standard-scss
- Create a
.stylelintrc.jsonconfiguration file - Add rules for: max nesting depth (3), no !important, class naming pattern (kebab-case), and no missing placeholder extends
- Add npm scripts for linting and auto-fixing
- Run the linter on your SASS files and fix any violations