Best Practices to Improve Your SCSS Workflow and Maintainability

Patric
13 min readApr 30, 2023

--

We will go through 10 common mistakes with code examples that developers may make when developing applications with SCSS.

Here is a brief overview of the examples we will go through:

  1. Not properly nesting selectors: SCSS allows for nesting selectors, but it’s important to make sure that they are nested properly to avoid issues with specificity and inheritance.
  2. Overusing variables: While using variables can help make code more maintainable and easier to update, overusing them can lead to bloated code and make it harder to understand the CSS output.
  3. Not using the built-in functions: SCSS provides built-in functions that can simplify calculations and other operations. Failing to use them can lead to more complex code.
  4. Ignoring inheritance: SCSS supports inheritance, which can reduce the amount of code needed to write. Ignoring inheritance can make your code less efficient.
  5. Not using mixins: Mixins allow you to reuse code snippets and make your code more modular. Not using mixins can lead to code duplication and make it harder to maintain.
  6. Not organizing code: Proper code organization is critical for maintaining your SCSS files. Failure to organize your code can make it harder to maintain and update.
  7. Using unnecessary prefixes: SCSS has a feature called autoprefixer, which automatically adds vendor prefixes. Adding unnecessary prefixes can lead to bloated code.
  8. Not using partials: SCSS allows you to break your code into smaller, more manageable files called partials. Failing to use partials can make your code harder to maintain.
  9. Overriding styles: While overriding styles can be necessary, overusing it can make your code more complex and harder to maintain.
  10. Overcomplicating selectors: Overly complex selectors can make your code harder to read and maintain. Keep your selectors as simple as possible to avoid confusion and improve readability.

1. Not properly nesting selectors

Nesting selectors is a powerful feature of SCSS, as it allows you to write more concise and readable CSS. However, it’s important to be aware of the potential issues that can arise when nesting selectors improperly.

One common mistake is to nest selectors too deeply, which can make your code harder to read and can also increase the specificity of your selectors. Here’s an example:

nav {
ul {
li {
a {
color: blue;
}
}
}
}

This code is perfectly valid SCSS, and it will compile to the following CSS:

nav ul li a {
color: blue;
}

However, it’s important to ask yourself whether this level of nesting is really necessary. In this example, it might be more appropriate to write the code like this:

nav > ul > li > a {
color: blue;
}

This code will produce the same output, but it avoids unnecessary nesting and makes the code easier to read. Nesting is in some cases not necessary for example if you don't add styling in the above case for nav, ul and li.

Another common mistake is to use the & symbol improperly when nesting selectors. The & symbol is used to refer to the parent selector, but it can be easy to use in a way that creates unexpected results.

Here's an example:

button {
&.small {
background-color: blue;
}

&:hover {
background-color: gray;
}
}

In this code, the & symbol is used to combine the button selector with the .small and :hover selectors. This will produce the following CSS:

button.small {
background-color: blue;
}

button:hover {
background-color: gray;
}

and matches to the following HTML:

<button class="small"></button>

However, if we had written the code like this instead:

button {
.small {
background-color: blue;
}

&:hover {
background-color: gray;
}
}

This would produce the following CSS:

button .small {
background-color: blue;
}

button:hover {
background-color: gray;
}

In this example, the & symbol was used improperly, which resulted in a different CSS output than intended.

And matches to the following HTML:

<button>
<span class="small"></span>
</button>

In summary, it’s important to be careful when nesting selectors in SCSS, and to make sure that you’re using the & symbol properly. By keeping your code organized and readable, you can avoid issues with specificity and inheritance and make your code easier to maintain in the long run.

2. Overusing variables

Let's say you have the following HTML:

<button class="button">Click me</button>
<button class="button button-small">Click me</button>
<button class="button button-large">Click me</button>

Here’s an example of overusing variables in SCSS:

$color-primary: blue;
$color-secondary: green;
$color-tertiary: red;

.button {
background-color: $color-primary;
color: $color-secondary;
border: 1px solid $color-tertiary;
}

.button-small {
background-color: $color-primary;
color: $color-secondary;
border: 1px solid $color-tertiary;
padding: 5px;
}

.button-large {
background-color: $color-primary;
color: $color-secondary;
border: 1px solid $color-tertiary;
padding: 10px;
}

In this example, the same variables $color-primary, $color-secondary, and $color-tertiary are being used repeatedly for different styles of buttons. While this can make it easier to update the color scheme in one place, it can also make the code harder to understand and maintain, especially as more styles are added.

To improve this code, you could use more specific variable names and only use variables for values that are likely to be updated frequently. Here’s an updated example:

$button-color: blue;
$button-border: 1px solid red;

.button {
background-color: $button-color;
color: white;
border: $button-border;
}

.button-small {
padding: 5px;
}

.button-large {
padding: 10px;
}

In this example, the variables are only used for the background color and border style, which are more likely to change frequently. The padding for different button sizes is defined directly in the button styles, which makes the code more concise and easier to understand.

3. Not using the built-in functions

Here’s an example of not using the built-in functions in SCSS:

.container {
width: 50% + 10px;
padding: 20px / 2;
font-size: 16px * 1.2;
}

In this example, the addition, division, and multiplication operations are being performed using plain CSS syntax instead of the built-in functions provided by SCSS. While this may work, it can make the code harder to read and potentially lead to errors if the calculations become more complex.

To improve this code, you could use the built-in functions provided by SCSS. Here’s an updated example:

.container {
width: calc(50% + 10px);
padding: divide(20px, 2);
font-size: multiply(16px, 1.2);
}

In this example, the calc(), divide(), and multiply() functions are used to perform the same calculations. This makes the code more readable and helps to prevent errors, especially as more complex calculations are introduced.

Here’s another example of not using the built-in functions in SCSS:

.container {
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
}

In this example, the rgba() function is being used for the box-shadow property, but the darken() function provided by SCSS could be used to simplify the code and make it more flexible.

Here’s an updated example using the darken() function:

.container {
box-shadow: 0 2px 4px darken(black, 20%);
}

In this example, the darken() function is used to define the shadow color for the box-shadow property, which is a more concise and flexible way to define the shadow color.

Here is a list of commonly used built-in functions that you could use:

  1. lighten(color, amount) - Lightens a color by a specified amount (0-100%)
  2. mix(color1, color2, weight) - Mixes two colors together at a specified weight (0-100%)
  3. adjust-hue(color, degrees) - Adjusts the hue of a color by a specified amount of degrees (0-360)
  4. saturate(color, amount) - Increases the saturation of a color by a specified amount (0-100%)
  5. desaturate(color, amount) - Decreases the saturation of a color by a specified amount (0-100%)
  6. invert(color) - Inverts the colors of a color value
  7. rgba(color, alpha) - Adds an alpha (transparency) value to a color
  8. round(number) - Rounds a number to the nearest integer
  9. ceil(number) - Rounds a number up to the nearest integer
  10. floor(number) - Rounds a number down to the nearest integer
  11. percentage(number) - Converts a number to a percentage value (e.g. 0.5 becomes 50%)
  12. unit(number, unit) - Converts a number to a specified unit (e.g. unit(20, px) becomes 20px)
  13. min(value1, value2, ...) - Returns the minimum value of a list of values
  14. max(value1, value2, ...) - Returns the maximum value of a list of values

4. Ignoring inheritance

Here’s an example of ignoring inheritance in SCSS:

.container {
width: 100%;
padding: 20px;
background-color: #e6e6e6;
}

.container__header {
width: 100%;
padding: 10px 20px;
background-color: #f2f2f2;
}

.container__body {
width: 100%;
padding: 10px 20px;
background-color: #fff;
}

In this example, the .container__header and .container__body classes have duplicated properties for width, padding, and background-color. This can make the code harder to maintain and update. Instead, the .container__header and .container__body classes could inherit properties from the .container class:

.container {
width: 100%;
padding: 20px;
background-color: #e6e6e6;

&__header, &__body {
width: 100%;
padding: 10px 20px;
}

&__header {
background-color: #f2f2f2;
}

&__body {
background-color: #fff;
}
}

In this updated example, the .container__header and .container__body classes are nested within the .container class using the & symbol, which refers to the parent selector. They are then given the width and padding properties from the .container class using inheritance. This reduces the amount of duplicated code and makes the code easier to maintain and update.

5. Not using mixins

Here’s an example of not using mixins:

.button {
display: inline-block;
padding: 10px 20px;
font-size: 14px;
color: #fff;
background-color: #007bff;
border-radius: 5px;

&__large {
padding: 15px 30px;
font-size: 18px;
}

&__small {
padding: 5px 10px;
font-size: 12px;
}
}

In this example, we have a .button class that has some common properties for all buttons. However, we also have .button--large and .button--small classes that duplicate some of the properties of the .button class. This can make the code harder to maintain and update. Instead, we could use mixins to avoid code duplication:

@mixin button($padding, $font-size) {
display: inline-block;
padding: $padding;
font-size: $font-size;
color: #fff;
background-color: #007bff;
border-radius: 5px;
}

.button {
@include button(10px 20px, 14px);

&__large {
@include button(15px 30px, 18px);
}

&__small {
@include button(5px 10px, 12px);
}
}

In this updated example, we have created a @mixin called button that includes the common properties for all buttons. We then use the @include directive to apply the mixin to the .button, .button--large, and .button--small classes with different values for padding and font-size. This reduces the amount of duplicated code and makes the code easier to maintain and update.

6. Not organizing code

Here’s an example of not organizing code:

.header {
background-color: #007bff;
color: #fff;
padding: 20px;
}

nav ul {
list-style: none;
margin: 0;
padding: 0;
}

nav ul li {
display: inline-block;
margin-right: 10px;
}

nav ul li a {
color: #fff;
text-decoration: none;
}

.hero {
background-image: url('../images/hero.jpg');
background-size: cover;
background-position: center;
height: 500px;
}

.section {
padding: 40px;
background-color: #f0f0f0;
}

In this example, all the CSS rules are placed in the same file without any organization or separation. This can make it difficult to maintain and update the code. Instead, we can organize the code using comments and separating it into logical sections:

/*== Header ==*/

.header {
background-color: #007bff;
color: #fff;
padding: 20px;
}

nav ul {
list-style: none;
margin: 0;
padding: 0;
}

nav ul li {
display: inline-block;
margin-right: 10px;
}

nav ul li a {
color: #fff;
text-decoration: none;
}

/*== Hero ==*/

.hero {
background-image: url('../images/hero.jpg');
background-size: cover;
background-position: center;
height: 500px;
}

/*== Sections ==*/

.section {
padding: 40px;
background-color: #f0f0f0;
}

In this updated example, we have separated the code into logical sections using comments. This makes it easier to understand and update the code because each section has a clear purpose.

7. Using unnecessary prefixes

Here’s an example:

// Unnecessary prefixes

div {
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
}

In this example, the border-radius property has been prefixed with both -webkit- and -moz-. However, this is unnecessary because modern browsers don't require these prefixes. Using autoprefixer in SCSS will automatically add the necessary prefixes based on your specified browser support, which helps keep your code clean and efficient.

Here’s the same code with unnecessary prefixes removed:

// Cleaned up code

div {
border-radius: 5px;
}

In this updated example, we’ve removed the unnecessary prefixes and kept the code clean and concise. This will make it easier to maintain and update the code in the future.

Here’s another example:

// Unnecessary prefixes

a {
-webkit-transition: all 0.3s ease;
-moz-transition: all 0.3s ease;
-ms-transition: all 0.3s ease;
-o-transition: all 0.3s ease;
transition: all 0.3s ease;
}

In this example, the transition property has been prefixed with -webkit-, -moz-, -ms-, and -o-. However, this is unnecessary because modern browsers support the unprefixed transition property. Using autoprefixer in SCSS will automatically add the necessary prefixes based on your specified browser support, which helps keep your code clean and efficient.

Here’s the same code with unnecessary prefixes removed:

// Cleaned up code

a {
transition: all 0.3s ease;
}

In this updated example, we’ve removed the unnecessary prefixes and kept the code clean and concise. This will make it easier to maintain and update the code in the future.

8. Not using partials

Here’s an example of how to use partials in SCSS:

Suppose you have a large SCSS file called main.scss that contains all of your styles. It might look something like this:

// main.scss

// Variables
$primary-color: #ff0000;

// Header styles
header {
background-color: $primary-color;
color: #fff;
padding: 20px;
}

// Navigation styles
nav {
ul {
list-style: none;
margin: 0;
padding: 0;

li {
display: inline-block;
margin-right: 10px;

a {
color: #fff;
text-decoration: none;
}
}
}
}

// Footer styles
footer {
background-color: #000;
color: #fff;
padding: 20px;
}

This file contains all of your styles, but it can be difficult to manage and update as it grows larger. To make it more manageable, you can break it up into smaller, more focused files called partials. To do this, you create new files that start with an underscore, like _variables.scss and _header.scss.

// _variables.scss

$primary-color: #ff0000;
// _header.scss

header {
background-color: $primary-color;
color: #fff;
padding: 20px;
}
// _footer.scss

footer {
background-color: #000;
color: #fff;
padding: 20px;
}

Then, in your main.scss file, you can include these partials using the @import rule:

// main.scss

@import 'variables';
@import 'header';
@import 'footer';

nav {
ul {
list-style: none;
margin: 0;
padding: 0;

li {
display: inline-block;
margin-right: 10px;

a {
color: #fff;
text-decoration: none;
}
}
}
}

In this updated example, we’ve broken up our code into separate files that are more focused and easier to manage. By using partials in this way, we can more easily maintain and update our styles as our project grows.

9. Overriding styles

Here’s an example:

// Bad practice: overriding styles
.button {
color: blue;
font-size: 16px;
}

.button.highlight {
color: red;
font-size: 18px;
}

In the above code example, the .button class defines a blue color and 16px font size. The .button.highlight class overrides the color to red and the font size to 18px. While this may be necessary in some cases, it can make the code more complex and harder to maintain. It may be better to create a new class for the highlighted button instead of overriding the original styles.

// Better practice: creating a new class
.button {
color: blue;
font-size: 16px;
}

.highlight {
color: red;
font-size: 18px;
}

In the above example, we create a new class .highlight to define the styles for a highlighted button. This makes the code more modular and easier to maintain.

Here’s an example of how you could use the above SCSS code in HTML:

<button class="button">Normal Button</button>
<button class="button highlight">Highlighted Button</button>

In the HTML code, we create two button elements, one with just the .button class, and the other with both the .button and .highlight classes. This applies the styles defined in the SCSS code for each class to the respective buttons.

When the page is rendered, the first button will have a blue color and 16px font size, while the second button will have a red color and 18px font size.

10. Overcomplicating selectors

Here are some examples of overcomplicating selectors:

Example 1:

#main > div:first-child > .content h2 + ul li > a {
color: red;
}

This selector is overly complicated and difficult to read. It targets a specific a element that is nested deep within the HTML structure, using a combination of child selectors, adjacent selectors, and ID and class selectors. A simpler and more readable alternative would be to use a class selector on the a element itself, or to use a parent class to target the entire list.

.list a {
color: red;
}

Example 2:

nav ul li:nth-child(odd) span + a {
color: blue;
}

This selector targets every other list item in a navigation menu, and then selects the a element that comes after a span element within that list item. While this may be useful in some cases, it can also be overly complex and difficult to understand. A simpler and more readable alternative would be to use a class selector on the a element itself, or to use a parent class to target the entire list.

.nav-link {
color: blue;
}

Example 3:

form > div > div:nth-child(2) > input[type="text"] {
border: 1px solid black;
}

This selector targets a specific input element that is nested several levels deep within a form, using a combination of child selectors and attribute selectors. A simpler and more readable alternative would be to use a class selector on the input element itself.

.form-input {
border: 1px solid black;
}

Overly complex selectors can have a negative impact on performance as it may take longer for the browser to parse and apply the styles. Additionally, if the selectors are too specific, it may override other styles or prevent them from being applied. It’s important to balance the readability and maintainability of your code with its performance.

Conclusion: SCSS is a powerful tool that can make writing and maintaining CSS easier, but there are some common mistakes that developers can make when working with it. These include not properly nesting selectors, overusing variables, ignoring inheritance, not using mixins, not organizing code, using unnecessary prefixes, not using partials, overriding styles, overcomplicating selectors, and not using the right tools.

To avoid these mistakes, it’s important to follow best practices such as keeping selectors as simple as possible, using built-in functions, organizing code into smaller, more manageable files called partials, and using the right tools for the job. By following these guidelines, you can write more efficient, maintainable SCSS code that will be easier to work with in the long run.

--

--

Patric

Loving web development and learning something new. Always curious about new tools and ideas.