In this article, we are going to learn about a new world of CSS that can be tagged as the realm of CSS superpowers. We are going to learn how to programmatically perform CSS tasks like you'd have done with JavaScript, write functions, create loops, create modular and re-usable code with Sass mixins, dynamically generate styles, and conditionally render styles in CSS in our style sheet.
If this sounds interesting to you, stick to the end and unravel a new mystery of CSS!
Are you ready? Let's dive right in🛩 🛩
What is Sass
The full meaning of Sass is Syntactically Awesome Styles Sheet. It is the oldest and most popular CSS preprocessor in the history of the web. CSS preprocessors are scripting languages that extend the default capabilities of CSS. They enable developers to use logic in their CSS code. Other popular CSS preprocessor are LESS, Stylus. It was initially released in 2006 and It was written in Ruby.
The tagline used on the Sass official website was:
CSS with Superpowers.
This tagline summarises the core use of Sass. It simply gives more capability to CSS. Sass is mostly used in large projects because of its easy-to-read syntax and added capabilities which include writing less code for more code and dynamic styling. It allows developers to triple their work by writing lesser code and give room for flexibility and complex styling logic in CSS.
If you have ever used bootstrap or tailwind CSS, you might have wondered how these thousands of CSS classes were made available for use. Would it have been that someone wrote the thousands of lines of code? Hell No! That would be a deadly thing to do. The secret behind these CSS frameworks is a CSS preprocessor like Sass. Both Bootstrap and Tailwind CSS were written in Sass. This gives the creator the ability to generate different classes and repetitive styles with less code.
Sass Basics.
Several concepts would be nice to understand before delving into the core features of Sass. Partials, Nesting, and how code compiles are some of them.
Partials
A partial is a Sass file that contains modularized Sass code that can be imported or used by other files. It contains little snippets of Sass codes, mostly variables, mixins, functions, and other generic and shared Sass codes. A partial filename is always prefixed with an underscore (_
) that makes it distinct from other files. This file naming convention signals the compiler not to compile the file into a separate CSS file, only the snippet used from the partial would be compiled along with the file in which it has been used.
Furthermore, partials allow one to keep the CSS folder structure modular and maintainable as the projects become bigger and larger over time. With partials, the CSS code can be broken down into separate files based on different components available in the codebase. Thereafter, the different partials can then be combined into a single sass file which would be compiled into CSS.
For example, we can have a file structure that looks like this:
|– base/
|-- _reset.scss // Partials
|-- _variables.scss |
|-- _functions.scss |
|-- _mixins.scss |
|– components/
|-- _buttons.scss |
|-- _forms.sass |
|–- _navigation.scss |
|--_dropdown.scss. |
|- app.scss
Then, we can import all of these partials into a single file Sass file by using the @import
rule as follows:
/* base */
@import "base/reset";
@import "base/variables";
@import "base/functions";
@import "base/mixins";
/* components */
@import "components/buttons";
@import "components/forms";
@import "components/navigation";
@import "components/dropdown";
Another way to import and use partials is using the @use
rule. Contrary to @import
which makes the imported files and modules available on the global scope, @use
only makes variables, functions, and mixins available within the scope of the current file. See more on @use
here.
Nesting
Nesting is the act of housing code within other blocks of code. Just as we have it in HTML where related pieces of codes are nested within their respective parent element, Sass allows us to do the same while writing our styles. One of the benefits of this is that it makes life easier and reduces redundancy in our code. It also helps in making our styles more organized and more readable. For example:
// HTML STRUCTURE
<div>
<p>
<span>Text 1</span>
</p>
<p>
<span>Text 2</span>
</p>
<button>Continue </button>
</div>
// SASS CODE
div {
p {
color: blue;
span {
font-size: 12px;
}
}
button {
background-color: green;
color: white;
}
}
// COMPILED CSS OUTPUT
div p {
color: blue;
}
div p span {
font-size: 12px;
}
div button {
background-color: green;
color: white;
}
In the above, comparing the SCSS code and CSS code, we can see the redundancy and repetition we avoided by using Sass.
In addition, the ampersand &
represents the parent selector in Sass. This special selector makes it possible to re-use the outer selector seamlessly, reducing redundancy.
// HTML STRUCTURE
<form class="form">
<header class="form__header"> Sign Up </header>
<input class="form__input" type="text" placeholder="Enter your name" />
<input class="form__submit form__submit--disabled" type="submit" />
</form>
/// SASS CODE
.form {
background: #181818;
padding: 1rem;
&__header {
color: white;
margin-bottom: 1rem;
font-weight: 600;
font-size: 1.3rem;
}
&__input {
padding: 1rem;
border:none;
border-radius: 10px;
font-size: 1rem;
}
&__submit {
display: block;
padding: 1rem 2rem;
border-radius: 10px;
margin-top: 10px;
background-color: blue;
color: white;
border:none;
font-family: inherit;
}
}
// COMPILED CSS OUTPUT
.form {
background: #181818;
padding: 1rem;
}
.form__header {
color: white;
margin-bottom: 1rem;
font-weight: 600;
font-size: 1.3rem;
}
.form__input {
padding: 1rem;
border: none;
border-radius: 10px;
font-size: 1rem;
}
.form__submit {
display: block;
padding: 1rem 2rem;
border-radius: 10px;
margin-top: 10px;
background-color: blue;
color: white;
border: none;
font-family: inherit;
}
Another way to utilize the nesting capability of Sass is using Sass Namespaces. This is an effective way of writing CSS properties with the same name. The common name is called a namespace. For example, font-family
, font-size
, font-weight
all belongs to the font
namespace. In Sass, we can define them like this while keeping our code DRY.
// SASS CODE
.text {
font: {
family: 'Poppins';
size: 1.5rem;
weight: 600;
}
}
// COMPILED CSS OUTPUT
.text {
font-family: 'Poppins';
font-size: 1.5rem;
font-weight: 600;
}
Inheritance
Another interesting feature in Sass that I found very useful is the inheritance feature. This is done by using the @extend
rule to extend some pre-defined styles from one class to another. For example, if there are three types of buttons on a website and they all shared certain styles, we can define the basic style in a class and extend it to other classes as shown below:
// COMMON STYLES TO ALL THE BUTTON.
.button {
border: none;
border-radius: 7px;
padding: 12px 25px;
text-align: center;
font-size: 16px;
cursor: pointer;
}
// A VARIANT
.button-danger {
@extend .button;
background-color: red;
}
// A VARIANT
.button-warning {
@extend .button;
background-color: green;
color: white;
}
// A VARIANT
.button-success {
@extend .button;
background-color: green;
color: white;
}
Variables
Variables are ways of storing up values that are constantly used in different parts of the application. For example colors, primary color, secondary color, dark color, light color, default font family, screen width, and other generic values that are shared by different style files in the application.
The convention for defining a variable in Sass is to precede the variable name with a $
. This way, the compiler knows this is a variable and it would be treated as such.
A standard convention is to create a file to store all the variables. Nevertheless, the variable can be scoped into a single file, a single code block, etc. The various ways to create a variable are shown below:
/////// Global: variables.scss file ///////////
$font: "Poppins", sans-serif;
$primary-color: red;
$secondary-color: green;
$light-color: #f2f2f2;
$text-font: 1rem;
$screen-width: 1200px;
// Used as follows:
@import "variables.scss"
.card {
background-color: $secondary-color;
color: $light-color;
font-family: $font;
}
/////////// Local: button.scss file ///////////
$font: "Montserrat", sans-serif;
$primary-color: red;
button {
font-family: $font;
}
/////////// Scoped Inside a Code block: ///////////
.cta {
$font-color: #ddd;
.heading {
color: $font-color;
}
.sub__heading {
color: $font-color;
}
}
Mixins
Mixin is one of the things I admire a lot with Sass. It gives modularity, simplicity, and code re-usability you'd love as a CSS developer. It can take zero or as many arguments as you want. It does a similar thing to what we explained earlier with using @extend
for inheritance when it takes no argument. The thumbs rule here is that when there is no need for passing in an argument, use @extend
, but when there are need to pass in arguments, then using mixins is great. It is defined using the @mixin
rule and used anywhere else using the @include
rule. Without much talk, let's demonstrate its usage with the following examples:
// BASIC EXAMPLE: NO ARGUMENTS
@mixin createButton {
border: none;
border-radius: 7px;
padding: 12px 25px;
text-align: center;
font-size: 16px;
cursor: pointer;
}
.white-button {
@include createButton;
background-color: white;
color: black;
}
// MIXIN WITH ARGUMENT
@mixin generateButton($bg, $textColor) {
@include createButton;
background-color: $bg;
color: $textColor;
}
.primary-button {
@include generateButton($bg: blue, $textColor: white);
}
.secondary-button {
@include generateButton($bg: purple, $textColor: white);
}
In the above example, you'd notice how we use a mixin inside another mixin, that's so beautiful. Also, you notice that after creating the generateButton
mixin we can generate any button we like by passing in the background color and the text color.
Functions
Like any other programming language, a function is a piece of code that takes an input and returns an output. You pass in some argument (i.e inputs), perform some processing on it, and return the result. Functions in Sass are defined using the @function
rule followed by the function name and then the code block. This is useful in performing mathematical manipulations in your CSS code. For example, calculating the font size based on the container width or converting from px
to rem
, or performing other complex operations on SassScript values that you can re-use throughout your stylesheet. Below is an example of a function that converts px
value to rem
:
@function px-to-rem($pxVal) {
@return ($pxVal/16) + 0rem;
}
// SASS CODE
.text {
font-size: px-to-rem(32);
}
// CSS OUTPUT
.text {
font-size: 2rem;
}
Data Types (maps, lists, boolean, strings, numbers)
There are quite a several data types used in Sass. These different data types allow for storage and efficiently working with values in Sass in different ways. Below are the data types we have in Sass and details on how to work with them:
- Strings: Strings in Sass may have quotes around them and may not. In most cases, strings are used without the quotes around them. For example:
font-weight: bold;
,display: flex
,text-align: center
, etc. - Numbers: Numbers are may have units or not depending on the property. For example, setting
font-size
requires the use of a specific unit because there are several units that can be used e.gfont-size: 2rem
. Whereas, setting afont-weight
does not require a unit e.gfont-weight: 400;
. - Booleans: This is a true/false data type. A variable can be set to either true or false which can then be used for conditional rendering of styles depending on the boolean value assigned.
- Colors: Color is another data type available in both CSS and Sass. It can be referred to by its hex representation e.g
#f2f2f2
or by its name e.gred
or returned from a function e.grgb(137, 103, 117)
. Lists: A list is a Sass data structure type that holds a sequence of unordered items. It is similar to Arrays in JavaScript. Its items are can be separated with either comma or space. It requires no parathesis around it and it is 1-based indexing and not 0-based like in most programming languages. Index 1 indicates the first element of the list. A list is useful for holding an index of related values. For example, below is a sizes list that holds different sizes:
$sizes: 40px, 50px, 80px;
One way to work with a list is by iterating over it using the
@each
rule. For example, below, we can create different variants of classavatar
based on the sizes in the list.// SASS $sizes: 40px, 50px, 80px; @each $size in $sizes { .avatar-#{$size} { font-size: $size; height: $size; width: $size; } } // CSS OUTPUT .avatar-40px { font-size: 40px; height: 40px; width: 40px; } .avatar-50px { font-size: 50px; height: 50px; width: 50px; } .avatar-80px { font-size: 80px; height: 80px; width: 80px; }
Maps: A map is a list of key-value pairs. It is similar to objects in other programming languages. The maps must be written with parentheses around them as opposed to lists. The key must be unique and cannot be duplicated but the same value can be assigned to the same key without throwing an error. A map is written in this format:
(<key>: <value>, <key>: <value>)
.@each
rule can also be used to iterate over maps just like lists. The key and value can be assigned to variables so they can be easily used in the loop.// SASS CODE $icons: ("eye": "\f112", "start": "\f12e", "stop": "\f12f"); @each $name, $glyph in $icons { .icon-#{$name}:before { display: inline-block; font-family: "Icon Font"; content: $glyph; } } // CSS OUTPUT .icon-eye:before { display: inline-block; font-family: "Icon Font"; content: ""; } .icon-start:before { display: inline-block; font-family: "Icon Font"; content: ""; } .icon-stop:before { display: inline-block; font-family: "Icon Font"; content: ""; }
Control Flow
Control flow is the order in which a statement is executed in a script. Conditionals and Loops are two major things that control the flow of code execution in a script.
Conditionals
In Sass, the @if
and @else
rules are used to execute a certain block of code conditionally. For example, if we have a mixin that houses the stylings of a card that can be rendered in a dark or light mode, we can make use of @if
to define how this works as shown below:
// SASS CODE
@mixin card($isDark: false) {
// light mode
width: 300px;
aspect-ratio: 1 /1;
@if ($isDark) {
background-color: #181818;
color: white;
} @else {
background-color: #f2f2f2;
color: black;
}
}
.white_card {
@include card($isDark: false);
}
.dark_card {
@include card($isDark: true);
}
Loops
Loops help in executing one or more statements up to a desired number of times. There are two popular ways to create a loop in Sass, using the @for
and @each
rules. @each
is suitable for lists and maps and @for
loops over a range of values. The usage of @each
has been seen above while explaining lists. The following is an example for @for
:
// SASS CODE
@for $i from 1 through 3 {
.m-#{$i}) {
margin: #{$i}px;
}
}
// CSS OUTPUT
.m-1 {
margin: 1px;
}
.m-2 {
margin: 2px;
}
.m-3 {
margin: 3px;
}
Conclusion
In this article, we have discussed Sass at length, below are the summary/takeaway tips from the above:
- Sass helps CSS developers to write modular and more organized code.
- With partials, we can break down our styles into different files that can be imported into other files. Partials are prefixed by an underscore.
- We can nest elements the same way elements are nested in HTML.
@extend
is used to inherit styles from one class to another.- Variables hold reusable values and are prefixed by
$
- Mixins are great for creating reusable and modular code. It is defined with the
@mixin
rule and used with the@include
rule. - Functions take in an input and return an output. It is good for performing mathematical operations.
- There are different data types in Sass. They are strings, numbers, booleans, colors, lists, and maps.
- Lastly, control flow is the order of code execution and they are affected by loops and conditionals.
@for
and@each
are mostly used for loops and@if
and@else
cater for the conditionals.
All code used above can be found here on Codepen. Feel free to play around with it and fork.
And that's all for this article, I hope you gain something from this piece. If you have a question or comment, I'd be glad to attend to it in the comment section.
Thanks for reading!