Back

SASS or Native CSS? A Comparison

SASS or Native CSS? A Comparison

CSS today is not the same as in 1996, and Sass has also grown since its creation — which one is to be preferred today? This article discusses the pros and cons of both options so you can decide!

CSS has come a long way since its introduction in 1996. Earlier in the language’s history, developers had to use preprocessors like Sass, which made working with the language easier. Some of these features included variables, functions, loops, and conditionals.

Today, CSS is very different from what we saw in 1996. With the introduction of features like variables, media queries, and nested styling, developers no longer need to rely on Sass and other preprocessors like they did in the past.

Sass is a CSS prepossessor that has been around since 2006. Many developers around the world use it and powers websites of all sizes. It builds on top of regular (or native) CSS by adding features like mixins, functions, and loops. So, let’s take a look at the syntax and how it compares to native CSS today.

Nested Styles

Nesting is a feature that has been a staple of preprocessors like Sass. Some developers switched to Sass solely for its support for nested styling. Not only does nesting make styling easier, but it can also feel more “natural” to write. To illustrate, let’s say you have the following HTML:

<aside>
  <h3>Page Contents</h3>
  <ul>
    <li>Introduction</li>
    <li>Using Loops in JavaScript</li>
    <li>Writing Conditional statements</li>
  </ul>
</aside>

If you want to style the <li> elements within the <ul> element, since Sass has support for nesting, you can do the following:

ul
    background-color: red
    
    li
        list-style: none

Native CSS was previously incapable of doing this, and you had to do the following instead:

ul {
  background-color: red;
}

ul li {
  list-style: none;
}

With nesting now supported by Native CSS, here’s what the code should look like:

ul {
  background-color: red;

  li {
    list-style: none;
  }
}

When parsing the code above, the browser automatically adds whitespace between ul and li.

This works well, but what about situations where you want to style a nested pseudo-class? The following code would not work.

div {
  background-color: red;

  :hover {
    text-decoration: underline;
  }
}

Because the browser automatically adds whitespace between a nested selector and its parent, The parsed selector will look like this: div :hover. To prevent this behavior, add a & symbol before the nested selector.

div {
  background-color: red;

  &:hover {
    text-decoration: underline;
  }
}

The & symbol is called a ‘nesting selector’, defining a direct relationship between a child element and its ancestor.

Functions and Mixins in Sass

Functions are one of Sass’s main selling points. Many Sass functions are available in built-in modules that must be loaded before they can be used. Loading a function can be done using the @use rule.

Some of these modules include the sass:math module, which provides for manipulating numbers, the sass:string module for working with strings, and the sass:color module, which can be used to work with color. Not all functions are contained in modules, though. Some functions like if are available globally.

For example, here’s how you can manipulate strings using the sass:string module.

@use "sass:string"

div:after
  display: block
  content: string.quote(Hello)

In the code block above, we are loading the sass:string module with the @use directive, then after loading the module; we apply styling to a div:after pseudo-element. The content property is assigned the result of the string.quote(Hello) expression from the loaded module.

This will result in the following:

div:after {
  display: block;
  content: "Hello";
}

Before exploring a more complex example, let’s look at how to define a custom function in Sass. You define custom functions by writing @function and specifying a function name.

You can return a value from a function using the @return rule. Let’s define a custom function that calculates the area of a square based on its side length. The side length is the value passed into the function as a parameter.

@function calculate-square-area($side-length)
  @return $side-length * $side-length

div
  width: calculate-square-area(40)
  height: calculate-square-area(40)

The following code results in the following:

div {
  width: 1600;
  height: 1600;
}

Now, let’s look at how you can create something more complex with functions and mixins in Sass. Mixins are defined with the @mixin directive. Just like functions, they can take in parameters, but unlike functions, they do not return a value.

Instead, mixins come in handy when you want to group related style rules. Take a look at the following code and see if you can guess what is going on:

@use "sass:string"


// Function to generate button class based on the button type
@function generate-button-class($type)
  @return string.quote("button-" + $type)


// Define button styles
@mixin button-style($type)
  .#{generate-button-class($type)}
    padding: 10px
    border: 1px solid #333
    background-color: if($type == "primary", #3498db, #e74c3c)
    color: #fff
    text-decoration: none
    display: block
    cursor: pointer

    &:hover
      background-color: darken(if($type == "primary", #3498db, #e74c3c), 10%)

// Apply button styles for different button types
@include button-style("primary")
@include button-style("secondary")

Quite a few things are happening with the code above, so let’s break it down. First, a function generate-button-class takes in the argument $type and concatenates it to the string “button-”.

Next, a mixin button-style takes in parameter $type, and uses the interpolation .#{generate-button-class($type)} to insert the result of the generate-button-class function into a string.

The interpolation syntax (#{<expression>}) is particularly useful when you want to dynamically generate class names or property values within a string.

The if() global function checks if the $type parameter is “primary” and sets the background-color to #3498db if the condition is true, and #e74c3c otherwise.

Lastly, the @include directive is used to apply the styles defined in the mixin. The code block above should result in the following:

.button-primary {
  padding: 10px;
  border: 1px solid #333;
  background-color: #3498db;
  color: #fff;
  text-decoration: none;
  display: block;
  cursor: pointer;
}
.button-primary:hover {
  background-color: #217dbb;
}

.button-secondary {
  padding: 10px;
  border: 1px solid #333;
  background-color: #e74c3c;
  color: #fff;
  text-decoration: none;
  display: block;
  cursor: pointer;
}
.button-secondary:hover {
  background-color: #d62c1a;
}

Functions and At-rules in Native CSS

Native CSS has a set of built-in functions that provide additional functionality for styling elements. While these functions are powerful, they are somewhat limited compared to the extensive capabilities offered by preprocessors like Sass.

Notable native CSS functions include linear-gradient for creating gradient backgrounds, calc for performing calculations within property values, and functions like min and max for handling minimum and maximum values. Unlike Sass, there is no way to define a custom function in CSS.

CSS does include support for at-rules, which are special instructions preceded by an ”@” symbol that modifies how CSS behaves. Some of the most popular at-rules you’d come across in a modern code base include: @media, @font-face, @keyframes, @supports, and @import. Here’s how you can use each of them:

  • @media: is used to apply styles based on device characteristics or the user’s preferences, such as screen size or color capabilities.
@media screen and (max-width: 600px) {
  body {
    font-size: 14px;
  }
}
  • @font-face: defines custom fonts that may not be available on the user’s system.
@font-face {
  font-family: 'MyCustomFont';
  src: url('my-custom-font.woff2') format('woff');
  font-weight: normal;
  font-style: normal;
}
  • @keyframes: Defines animations using a series of keyframes.
@keyframes slide-in {
  from {
    transform: translateX(-100px);
  }
  to {
    transform: translateX(0px);
  }
}

.slide-in-animation {
  animation: slide-in 1s ease-in-out;
}
  • @supports: Checks if the browser supports a certain feature or property before applying styles.
@supports (display: grid) {
  .grid-container {
    display: grid;
  }
}
  • @import: Imports an external stylesheet into the current stylesheet.
@import url("external-stylesheet.css");

Build Tools And Workflow

To use Sass in a modern JavaScript code base you need to have Node.js and the Node Package Manager (NPM) installed on your machine. With NPM, installing Sass is as easy as running the following command in the terminal:

npm install -g sass

Once you’ve installed Sass successfully, you must use the Sass compiler to convert the .sass file into the .css file that the browser can parse. The common syntax for doing so looks like this:

sass <path-to-sass-file> <css-destination>

So if you want to convert styles.sass into styles.css, you do the following:

sass styles/styles.sass styles/styles.css

Using Sass with build tools like Webpack requires loaders like css-loader and sass-loader and the MiniCssExtractPlugin loader.

CSS, unlike Sass, does not require an extra build step. CSS is a native styling language understood by web browsers, and it doesn’t need to be compiled or processed before being interpreted by the browser.

Weighing the Pros and Cons: Sass vs. Native CSS

Let’s sum up things.

Advantages of Sass

  • Powerful Features: Sass introduces powerful features that enhance the capabilities of CSS. Variables, mixins, and nesting provide a more organized and maintainable structure to stylesheets. This modularity helps in efficiently managing and updating styles across a project.

  • Extensive Community: Sass has been around for over 10 years, and in that time, it has garnered a large and active community of developers. This community support contributes to the availability of resources, documentation, and third-party tools that facilitate Sass usage. It’s easier for you and other developers to find solutions and share best practices within this community.

  • Established Workflow: The Sass preprocessor follows a well-established workflow. With features like partials and imports, developers can divide their stylesheets into manageable components, making it easier to maintain and scale styles across large projects.

Disadvantages of Sass

  • Additional Build Step: One of the main drawbacks of using Sass is the need for an additional build step. Before deploying styles to a production environment, Sass code must be compiled into standard CSS. This introduces complexity and requires additional tools and configurations in the development workflow.

  • Learning Curve: For developers new to Sass, a learning curve is involved in understanding and using its features effectively. While the benefits are substantial, you might perceive the early investment into learning Sass as a disadvantage, especially if you’re more accustomed to traditional CSS.

Advantages of Native CSS

  • Straightforward Syntax: Native CSS has a straightforward and easy-to-understand syntax. This simplicity makes it accessible to developers of all skill levels. Writing styles directly in CSS requires no preprocessing step, allowing for a more immediate implementation.

  • Seamless Integration: Styles written in native CSS seamlessly integrate with HTML. There’s no need for an additional build or compilation step. This straightforward integration simplifies the development process and allows for quick testing and implementation of styles.

Potential Drawbacks with Native CSS

  • Limitations in Features: While native CSS covers the basics, it lacks some of the advanced features provided by preprocessors like Sass. The absence of variables, mixins, and nesting can make stylesheets less modular and harder to maintain in larger projects.

  • Browser Support for Advanced Features: Certain advanced features, particularly those associated with preprocessors, may have varying levels of browser support in native CSS. You need to be aware of these limitations when choosing native CSS over more feature-rich alternatives. For example, although most modern browsers support native nested styling, not all users have the latest versions of their browsers. Thus, the parser may not interpret nested styles properly.

Sass vs. Native CSS: What To Consider Before Transitioning From One To The Other

  • Project Complexity: Since Sass offers a robust set of features like mixins, custom functions, and nesting, it often shines brightly in larger projects that require a high degree of organization and scalability. The ability to break down styles into modular components becomes increasingly advantageous as the complexity of a project grows.

  • Team Expertise: Assessing your team’s current skillset can help you decide what tool to add to your project. If your team is well versed in Sass, leveraging its features can highly improve productivity and code quality. But, if native CSS aligns closely with your team’s expertise or if there are time constraints that might limit the learning curve, then sticking to native CSS might be the best choice.

Conclusion

Deciding between Sass and native CSS is all about considering the nuances of your project and the expertise within your team. CSS has come a long way, and for most of your use cases, you might find that reaching for a preprocessor like Sass isn’t necessary. Remember, you can always experiment with both tools to see which one fits you or your project the most. It’s about finding what works best for you and your unique situation.

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay