In this file, you will learn about:
- What is SASS & Installation
- Working with SASS & SCSS
- SASS Features
- Mathematic in SASS
- Media Queries in SASS
- Inheritance in SASS
- Mixins
- Pseudo Classes in SASS
Sass is a superset of CSS and built on top of CSS. Sass stands for Syntactically Awesome Style Sheets. It does not run in the browser, instead it extends CSS during development only.
In SASS, we can use logical styles and then compile that style to normal CSS. Because browser does not support SASS and we can use it directly and we should use it only in development.
To work with tool, we need to download this app in https://sass-lang.com/install website.
Note: You can also use scout-app open source application to use it (in https://scout-app.io/ link).
Important: If you have Node.js app, you can use npm install -g sass
(for MAC, you need to add sudo
) command to install this tool.
We can see the version of SASS with sass --version
command.
Consider this example:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styling</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<div class="box"></div>
</div>
</body>
</html>
and
:root {
--black-box-border: black;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
}
.container {
margin: 160px auto;
width: 300px;
height: 300px;
border: 1px var(--black-box-border, black) solid;
}
.box {
width: 200px;
height: 100%;
background-color: var(--black-box-border, black);
animation: hamid 800ms steps(1000, start) infinite;
}
@keyframes hamid {
0% {
transform: translateX(-400px);
}
50% {
transform: translateX(0);
}
100% {
transform: translateX(800px);
}
}
We want transfer this style from normal CSS to SASS (Syntactically Awesome Style Sheets), to do that, we need to add a new file and name it style.sass
and paste our styles into that file.
The syntax of sass files is like:
// style.sass
:root
--black-box-border: black
*
box-sizing: border-box
body
margin: 0
padding: 0
.container
margin: 160px auto
width: 300px
height: 300px
border: 1px var(--black-box-border, black) solid
.box
width: 200px
height: 100%
background-color: var(--black-box-border, black)
animation: hamid 800ms steps(1000, start) infinite
@keyframes hamid
0%
transform: translateX(-400px)
50%
transform: translateX(0)
100%
transform: translateX(800px)
We removed all { }
curly brace pairs and ;
semi-colons in here. Because the syntax of sass
is like that and how it understands our styles.
Important: Indention is very important, it's like Python language which means that the box-sizing: border-box
rule belongs to the *
universal selector.
Note: If your editor not support the color, make sure you install the sass
extension in Vs Code.
This syntax is like pug
template engine and maybe someone don't like this syntax. So the greater way of using it, that is using scss
file extention, for example:
// style.scss
:root {
--black-box-border: black;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
}
.container {
margin: 160px auto;
width: 300px;
height: 300px;
border: 1px var(--black-box-border, black) solid;
}
.box {
width: 200px;
height: 100%;
background-color: var(--black-box-border, black);
animation: hamid 800ms steps(1000, start) infinite;
}
@keyframes hamid {
0% {
transform: translateX(-400px);
}
50% {
transform: translateX(0);
}
100% {
transform: translateX(800px);
}
}
This style is great, because it looks like a normal CSS and indeed we can add more features to this style.
All files with scss
we want work with them, let's describe the first feature in the SASS.
In SASS, we can have some nesting selectors, for example if we have this content:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styling</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header class="header">
<nav class="main-nav">
<ul>
<li><a href="">Test link</a></li>
</ul>
</nav>
</header>
</body>
</html>
Instead of writing styles like this:
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
}
.header {
background-color: #ccc;
height: 50px;
}
.main-nav {
padding-top: 2px;
height: 100%;
text-align: center;
}
ul {
list-style: none;
}
a {
text-decoration: none;
}
we can use nested selectors in SASS (i.e. style.scss
file) like:
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
}
.header {
background-color: #ccc;
height: 50px;
.main-nav {
padding-top: 2px;
height: 100%;
text-align: center;
ul {
list-style: none;
a {
text-decoration: none;
}
}
}
}
and it's very essy to understand. We can also write in different way like:
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
}
.header {
background-color: #ccc;
height: 50px;
.main-nav {
padding-top: 2px;
height: 100%;
text-align: center;
}
ul {
list-style: none;
}
a {
text-decoration: none;
}
}
because ul
and a
selectors are sill part of .main-nav
and it's no matter they should be nested or not.
We are applied our styles in there, now we need to compile our SASS code to normal CSS. To do that, we need to go to the terminal or command line and write:
sass target output
for example, sass style.scss style.css
command we want to use. The output of this compilation is:
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
}
.header {
background-color: #ccc;
height: 50px;
}
.header .main-nav {
padding-top: 2px;
height: 100%;
text-align: center;
}
.header ul {
list-style: none;
}
.header a {
text-decoration: none;
}
/*# sourceMappingURL=style.css.map */
There is another file that named style.css.map
which we cover it later.
Note: If we don't want to compile our code manually, we can compile our styles automatically with this command:
sass --watch target:output
for example, sass --watch style.scss:style.css
command we can use for automate compilations. Now when we change any style and save the file (i.e. style.scss
file), the style.css
file would be updated automatically.
If we have this content with style:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styling</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<div class="box"></div>
</div>
</body>
</html>
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
}
.container {
margin: 160px auto;
width: 600px;
height: 200px;
border: 1px black solid;
}
.box {
width: 200px;
height: 100%;
background-color: red;
animation-name: hamid;
animation-duration: 1s;
animation-timing-function: ease-in;
/* animation-timing-function: cubic-bezier(0.68, -0.6, 0.32, 1.6); */
/* animation-timing-function: linear; */
animation-delay: 1s;
animation-iteration-count: 5;
animation-direction: alternate;
animation-fill-mode: forwards;
animation-play-state: running;
}
@keyframes hamid {
from {
transform: translateX(0);
}
to {
transform: translateX(400px);
}
}
Instead of having the same property name, we can nesting them, for example we want to nesting the animation
property; so we can do that with {}
curly braces:
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
}
.container {
margin: 160px auto;
width: 600px;
height: 200px;
border: 1px black solid;
}
.box {
width: 200px;
height: 100%;
background-color: red;
// animation-name: hamid;
// animation-duration: 1s;
// animation-timing-function: ease-in;
// animation-delay: 1s;
// animation-iteration-count: 5;
// animation-direction: alternate;
// animation-fill-mode: forwards;
// animation-play-state: running;
animation: {
name: hamid;
duration: 1s;
timing-function: ease-in;
iteration-count: 5;
direction: alternate;
fill-mode: forwards;
play-state: running;
}
}
@keyframes hamid {
from {
transform: translateX(0);
}
to {
transform: translateX(400px);
}
}
Note: It is also useful for flexbox, grid, transform and transition.
In the previous file, we worked with CSS variables, and it's also available for SCSS. The benefit of using variables in SCSS, is that it compiles those variables which does not use CSS variables (i.e. var(value, fallback)
), so it will run in all browsers, even the older versions; and it uses the real value.
We define a variable with $
dollar sign (always should be a first character) and the name of the variable. For example:
$black-color: black;
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
}
.container {
margin: 160px auto;
width: 600px;
height: 200px;
border: 1px $black-color solid;
}
.box {
width: 200px;
height: 100%;
background-color: $black-color;
animation: {
name: hamid;
duration: 1s;
timing-function: ease-in;
iteration-count: 5;
direction: alternate;
fill-mode: forwards;
play-state: running;
}
}
@keyframes hamid {
from {
transform: translateX(0);
}
to {
transform: translateX(400px);
}
}
If we look at our CSS code, we see that we have this style:
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
}
.container {
margin: 160px auto;
width: 600px;
height: 200px;
border: 1px black solid;
}
.box {
width: 200px;
height: 100%;
background-color: black;
animation-name: hamid;
animation-duration: 1s;
animation-timing-function: ease-in;
animation-iteration-count: 5;
animation-direction: alternate;
animation-fill-mode: forwards;
animation-play-state: running;
}
@keyframes hamid {
from {
transform: translateX(0);
}
to {
transform: translateX(400px);
}
}
/*# sourceMappingURL=style.css.map */
We don't have variable in this file, instead we have the real value of variable in this file. For example background-color: black
in .box
and border: 1px black solid
in .container
selectors are the real value or black
as value.
We can hold more values in our variables, for example:
$black-color: black;
$simple-border: 1px solid green;
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
}
.container {
margin: 160px auto;
width: 600px;
height: 200px;
border: $simple-border;
}
These 1px solid green
values now named as list value which means one variable with multiple values. If we want use more structured variables or group our variables in one thing, we can use map instead.
A map constructed by adding ()
parentheses and this parentheses we define our key-value pairs, for example:
$colors: (dark: black, light: white, normal: #ccc);
We have three key-value pairs above and we can access them by get-map(variable, key)
function which is provided by SASS, for example:
$colors: (dark: black, light: white, normal: #ccc);
$simple-border: 1px solid green;
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
}
.container {
margin: 160px auto;
width: 600px;
height: 200px;
border: $simple-border;
}
.box {
width: 200px;
height: 100%;
background-color: map-get($map: $colors, $key: normal);
animation: {
name: hamid;
duration: 1s;
timing-function: ease-in;
iteration-count: 5;
direction: alternate;
fill-mode: forwards;
play-state: running;
}
}
@keyframes hamid {
from {
transform: translateX(0);
}
to {
transform: translateX(400px);
}
}
We used background-color: map-get($map: $colors, $key: normal)
in the .box
selector.
Note1: We just worked with one property, the map will shines when we want to work with multiple values.
Note2: We can also use background-color: map-get($colors, normal);
shorter arguments.
SASS has many built-in functions, for example one them is lighten()
function which is useful for lightning our colors, for example:
$colors: (
dark: black,
light: white,
normal: #ccc
);
$simple-border: 1px solid green;
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
}
.container {
margin: 160px auto;
width: 600px;
height: 200px;
border: $simple-border;
}
.box {
width: 200px;
height: 100%;
background-color: lighten(map-get($colors, dark), 50%);
animation: {
name: hamid;
duration: 1s;
timing-function: ease-in;
iteration-count: 5;
direction: alternate;
fill-mode: forwards;
play-state: running;
}
}
@keyframes hamid {
from {
transform: translateX(0);
}
to {
transform: translateX(400px);
}
}
We choose dark (i.e. black
) color in there. When we use 50%
amount of lighening, our color would be converted to the gray
color.
We can also use darken()
, whiteness()
and other functions.
Note: Learn more about built-in functions in https://sass-lang.com/documentation/modules.
We can use addition, subtraction, multiplication and division in SAS. For example if we have some styles like this:
$colors: (
dark: black,
light: white,
normal: #ccc
);
$simple-border: 1px solid green;
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
}
.container {
margin: 160px auto;
width: 600px;
height: 200px;
border: $simple-border;
}
.box {
width: 200px;
height: 100%;
background-color: lighten(map-get($colors, dark), 50%);
animation: {
name: hamid;
duration: 1s;
timing-function: ease-in;
iteration-count: 5;
direction: alternate;
fill-mode: forwards;
play-state: running;
}
}
@keyframes hamid {
from {
transform: translateX(0);
}
to {
transform: translateX(400px);
}
}
We can write better styles with arithmetics:
$colors: (
dark: black,
light: white,
normal: #ccc
);
$simple-border: 1px solid green;
$main-size: 100px;
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
}
.container {
margin: $main-size * 1.6 auto;
width: $main-size * 6;
height: $main-size * 2;
border: $simple-border;
}
.box {
width: $main-size * 2;
height: 100%;
background-color: lighten(map-get($colors, dark), 50%);
animation: {
name: hamid;
duration: 1s;
timing-function: ease-in;
iteration-count: 5;
direction: alternate;
fill-mode: forwards;
play-state: running;
}
}
@keyframes hamid {
from {
transform: translateX(0);
}
to {
transform: translateX($main-size * 4);
}
}
If we want to import some styles from other files like:
/* test.css - export */
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
}
// style.scss - import
@import url(test.css);
If we see the Network tab in browser developer tools, we see that there are three requests; index.html
, style.scss
and test.css
files.
We can improve the website/web app performance with partials. Partial is way of importing in SASS; to do that, we need to add a new file which is named _var
or whatever we want to naming:
// style.scss - import
@import "./_var.scss";
.container {
margin: $main-size * 1.6 auto;
width: $main-size * 6;
height: $main-size * 2;
border: $simple-border;
}
@keyframes hamid {
from {
transform: translateX(0);
}
to {
transform: translateX($main-size * 4);
}
}
// _var.scss - export
$colors: (
dark: black,
light: white,
normal: #ccc
);
$simple-border: 1px solid green;
$main-size: 100px;
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
}
.box {
width: $main-size * 2;
height: 100%;
background-color: lighten(map-get($colors, dark), 50%);
animation: {
name: hamid;
duration: 1s;
timing-function: ease-in;
iteration-count: 5;
direction: alternate;
fill-mode: forwards;
play-state: running;
}
}
Note: We can also use @import "var";
, because SASS can understands files with _
prefix and scss
extension.
Now if we go to the Network tab in browser developer tools, we see that there are just two requests; index.html
, style.scss
and it means our users download less data when they are visiting our page.
We can have media queries like:
@media (max-width: 300px) {
body {
background-color: yellow;
}
}
But we can write more easier with nesting this keyword, for example:
$colors: (
dark: black,
light: white,
normal: #ccc
);
$simple-border: 1px solid green;
$main-size: 100px;
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
@media (max-width: 300px) {
background-color: yellow;
}
}
.container {
margin: $main-size * 1.6 auto;
width: $main-size * 6;
height: $main-size * 2;
border: $simple-border;
}
.box {
width: $main-size * 2;
height: 100%;
background-color: lighten(map-get($colors, dark), 50%);
animation: {
name: hamid;
duration: 1s;
timing-function: ease-in;
iteration-count: 5;
direction: alternate;
fill-mode: forwards;
play-state: running;
}
}
@keyframes hamid {
from {
transform: translateX(0);
}
to {
transform: translateX($main-size * 4);
}
}
We wrote the media query inside of the body
selector:
body {
margin: 0;
padding: 0;
@media (max-width: 300px) {
background-color: yellow;
}
}
That's a great feature that we can use for our websites or web applications.
Consider this HTML content
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styling</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<section class="main-section">
<div class="first-element"></div>
<div class="second-element"></div>
<div class="third-element"></div>
</section>
</body>
</html>
We can style this content with some methods, for example:
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
}
.first-element {
width: 300px;
height: 200px;
background-color: green;
}
If we want to apply this properties to another selector, we have three more ways to do that, the first way is copy and pasting the same properties like:
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
@media (max-width: 300px) {
background-color: yellow;
}
}
.first-element {
width: 300px;
height: 200px;
background-color: green;
}
.second-element {
width: 300px;
height: 200px;
background-color: green;
}
.third-element {
width: 300px;
height: 200px;
background-color: green;
}
The second way is better, we can write those selectors in one rule:
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
@media (max-width: 300px) {
background-color: yellow;
}
}
.first-element,
.second-element,
.third-element {
width: 300px;
height: 200px;
background-color: green;
}
The third way is that we can choose first-element
for all elements and that's not recommended by us.
But with these three ways, we can't control the other values, for example we want to keep those elements' size and change their colors. To do that, we use inheritance; for that, we use @extend
keyword which is provided by the SASS:
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
@media (max-width: 300px) {
background-color: yellow;
}
}
.main-style {
width: 300px;
height: 200px;
}
.first-element {
@extend .main-style;
background-color: red;
}
.second-element {
@extend .main-style;
background-color: green;
}
.third-element {
@extend .main-style;
background-color: blue;
}
We defined a .main-style
selector which is not exist in our HTML code. Then we put the width
and height
into the .main-style
selector.
Thereafter, all our three elements inherited the width
and height
from the .main-style
selector, which means that all those elements have width: 300px; height: 200px;
properties. Now we can easily change or add another properties to these selectors/elements.
If we want to style of web page for both modern and older browsers, we need to reuse some properties, for example:
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
}
.main-section {
display: -ms-flexbox;
display: -webkit-flex;
display: -webkit-box;
display: flex;
justify-content: center;
align-items: center;
}
.main-style {
width: 300px;
height: 200px;
}
.first-element {
@extend .main-style;
background-color: red;
}
.second-element {
@extend .main-style;
background-color: green;
}
.third-element {
@extend .main-style;
background-color: blue;
}
We used four display
properties with different values. We can mix them up in one thing with mixin. To do that, we need to use @mixin
directive/keyword and write our mixin like a normal function in programming languages:
@mixin display-test {
display: -ms-flexbox;
display: -webkit-flex;
display: -webkit-box;
display: flex;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
}
.main-section {
// display: flex;
// display: -ms-flexbox;
// display: -webkit-flex;
// display: -webkit-box;
@include display-test();
justify-content: center;
align-items: center;
}
.main-style {
width: 300px;
height: 200px;
}
.first-element {
@extend .main-style;
background-color: red;
}
.second-element {
@extend .main-style;
background-color: green;
}
.third-element {
@extend .main-style;
background-color: blue;
}
We used @include
keyword for applying that mixin. There is one problem in here, if we want to use media queries in mixins, we we get an error and we need to use a tricky way to solve that issue. For example:
@mixin display-test {
display: -ms-flexbox;
display: -webkit-flex;
display: -webkit-box;
display: flex;
}
@mixin mq {
@media (max-width: 300px) {
background-color: yellow;
}
}
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
@include mq();
}
.main-section {
@include display-test();
justify-content: center;
align-items: center;
}
.main-style {
width: 300px;
height: 200px;
}
.first-element {
@extend .main-style;
background-color: red;
}
.second-element {
@extend .main-style;
background-color: green;
}
.third-element {
@extend .main-style;
background-color: blue;
}
We don't have this issue in this example, because we used media query just for one time. If we have 3 or 4 times, their value must be changed and we don't want to all of them have max-width: 300px
as screen.
We can add ()
parentheses after the mixin name and put parameters in there, for example we want have one more media query, so we need to write:
@mixin display-test {
display: -ms-flexbox;
display: -webkit-flex;
display: -webkit-box;
display: flex;
}
@mixin mq($width) {
@media (max-width: $width) {
border-radius: 50%;
}
}
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
@include mq(300px);
}
.main-section {
@include display-test();
justify-content: center;
align-items: center;
}
.main-style {
@include mq(400px);
width: 300px;
height: 200px;
}
.first-element {
@extend .main-style;
background-color: red;
}
.second-element {
@extend .main-style;
background-color: green;
}
.third-element {
@extend .main-style;
background-color: blue;
}
We used variable in function's parameter and use that function in two places (body
and .main-style
selectors).
We can have dynamic content in our media query with @content
keyword. That would be our content in our media query, for example:
@mixin display-test {
display: -ms-flexbox;
display: -webkit-flex;
display: -webkit-box;
display: flex;
}
@mixin mq($width) {
@media (max-width: $width) {
@content;
}
}
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
@include mq(300px) {
background-color: yellow;
}
}
.main-section {
@include display-test();
justify-content: center;
align-items: center;
}
.main-style {
width: 300px;
height: 200px;
}
.first-element {
@extend .main-style;
@include mq(400px) {
order: 3;
}
background-color: red;
}
.second-element {
@extend .main-style;
@include mq(400px) {
order: 2;
}
background-color: green;
}
.third-element {
@extend .main-style;
@include mq(400px) {
order: 1;
}
background-color: blue;
}
As you can see, we have fully dynamic values in there.
Consider this example:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styling</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<form action="">
<input type="text">
<input type="password">
<button type="submit">Send</button>
</form>
</body>
</html>
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
}
form {
button {
outline: none;
border-radius: 10px;
}
}
If we want to use hover
and active
pseuso classes in button like this:
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
}
form {
button {
outline: none;
border-radius: 10px;
:hover,
:active {
background-color: lightcoral;
}
}
}
and when we want to hover on button, we can't see anything, because the output of CSS is:
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
}
form button {
outline: none;
border-radius: 10px;
}
form button :hover,
form button :active {
background-color: lightcoral;
}
/*# sourceMappingURL=style.css.map */
As you can see, they are part of combination of form button. To avoid that happes we can add &
ampersand operator to tell the CSS these selectors must would be add to button directly:
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
}
form {
button {
outline: none;
border-radius: 10px;
&:hover,
&:active {
background-color: lightcoral;
}
}
}
Wow, it just works very well, we do it finally. The output of CSS now is:
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
}
form button {
outline: none;
border-radius: 10px;
}
form button:hover, form button:active {
background-color: lightcoral;
}
/*# sourceMappingURL=style.css.map */
and we see that form button:hover, form button:active
put in currect place.
Note: You can dive deeper in SASS with this https://sass-lang.com/guide link.