Preprocessors
A preprocessor takes the source file that you write and translates it into an output file, which is a standard CSS stylesheet. A preprocessor doesn’t add features to CSS, but it makes it easier to write.
- SCSS uses bracket syntax and the
.scssextension
Links
Installation and setup
npm init -y # create new npm project
npm install --save-dev sass # install sass and add to package.json
mkdir sass dist # create two dirs
touch sass/index.scss # create main scss file
touch index.html # create html file in root dir, link dist/styles.css
cp $HOME/Development/scss-partials/reset.scss sass/ # copy reset into your files
echo '@use "reset";' > sass/index.scss # include reset partial
vim package.json # edit generated scripts
{
...
"scripts": {
"start": "sass --watch sass/index.scss dist/styles.css",
"build": "sass sass/index.scss dist/styles.css"
},
...
}
npm run start # starts a dev server for sass - updates when changes to files
npm run build # run build script, [over]write dist/styles.css
When you run npm run [start|build], npm does the following:
- Reads
sass/index.scss - Creates
build/styleandbuild/styles.css.map. The.mapfile maps code in the.cssoutput file to the.scsssource file so the browser knows where to find the styles
Features
Variables
Variables are one of the reasons that CSS preprocessors became popular. They use the following syntax:
$primary: value;
Sass variables do not understand the DOM, and they do not use cascade or inheritance. They are block-scoped, so be sure to declare them globally unless you only want to use them within a single ruleset.
You can redefine the same variable within the same .scss file. If you change a variable value, rules defined before the change use the initial value, while rules defined after the change use the newer value.
Inline arithmetic
You can use +, -, *, /, and % in a declaration to compute values. This feature is what inspired the calc() function in CSS:
$padding-left: 3em;
.note-body {
padding-left: $padding-left * 2;
}
/* output.css */
.note-body {
padding-left: 6em;
}
Nested selectors
You can nest selectors inside declaration blocks to group related code. Nesting increases specificity, so be avoid extensive nesting.
The preprocessor prepends the top-level selector to the nested selector with a space between (.parent .nested) to create a descendant selector. To omit the space and create a compound selector (.parent.nested), use an &:
.site-nav {
display: flex;
> li {
margin-top: 0;
&.is-active {
display: block;
}
}
}
/* output.css */
.site-nav {
display: flex;
}
.site-nav > li {
margin-top: 0;
}
.site-nav > li.is-active {
display: block;
}
Here’s an example that uses pseudo-classes and the & operator. The & is like a stand in for the top level selector–in this case, the a selector:
a {
font-weight: 800;
&:link,
&:visited {
color: $primary;
text-decoration-style: dotted;
}
&:hover {
text-decoration-style: dashed;
}
&:focus {
text-decoration-style: solid;
outline: none;
}
}
You can also nest @media queries. This is helpful when changing selectors–you don’t have to change the selector in the media query, too:
html {
font-size: 1rem;
@media (min-width: 45em) {
font-size: 1.25rem;
}
}
/* output.css */
html {
font-size: 1rem;
}
@media (min-width: 45em) {
html {
font-size: 1.25rem;
}
}
Partials (@use)
Partials let you split your code into separate files for better code organization. The preprocessor will concatenate partial files together so the browser only needs to request one file.
To create a partial, start the file name with an underscore. For example, _file.scss. Then, import the partial at the beginning of your main .scss file with the @use at-rule. For more information about how you can use the @use at-rule, see the Sass docs.
Here is a simple example:
/* _button.scss */
.button {
padding: 1em 1.25em;
background-color: #265559;
color: #333;
}
/* index.scss */
@use "button";
html {
font-size: 1rem;
@media (min-width: 45em) {
font-size: 1.25rem;
}
}
/* output.css */
.button {
padding: 1em 1.25em;
background-color: #265559;
color: #333;
}
html {
font-size: 1rem;
}
@media (min-width: 45em) {
html {
font-size: 1.25rem;
}
}
Mixins
Mixins generate code, but extend adds selectors to the base.
A mixin is a small, reusable chunk of CSS. Use this if you have some commonly repeated rules, or a font style that you need to repeat in multiple places throughout the stylesheet.
Because mixins duplicate code, they compress well with gzip, which is how you should compress all network traffic.
Define a mixin with the @mixin at-rule, and use it with an @include at-rule. The @include at-rule tells the preprocessor to generate code:
@mixin box { /* mixin defition */
border-radius: 5px;
box-shadow: 5px 5px 10px rgb(0 0 0 /0.1);
}
.main-tile {
@include box; /* apply mixin */
color: #333;
}
/* output.css */
.main-tile {
border-radius: 5px;
box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.1);
color: #333;
}
Mixins can also accept parameters—like functions—and return styles. Parameters begin with a $, just like variables:
@mixin alert-variant($color, $bg-color) {
padding: 0.3em 0.5em;
border: 1px solid $color;
color: $color;
background-color: $bg-color;
}
.alert-info {
@include alert-variant(blue, lightblue);
}
.alert-danger {
@include alert-variant(red, pink);
}
/* output.css */
.alert-info {
padding: 0.3em 0.5em;
border: 1px solid blue;
color: blue;
background-color: lightblue;
}
.alert-danger {
padding: 0.3em 0.5em;
border: 1px solid red;
color: red;
background-color: pink;
}
This mixing uses interpolation, which lets you parameterize a parameter:
@mixin handle-image($border-radius, $position, $side) {
border-radius: $border-radius;
object-position: $position;
float: $side;
margin-#{$side}: 0; /* interpolation */
}
To use the mixin, use the @include keyword, followed by the mixin name and arguments. In the stylesheet, the @mixin declaration must be before the rule that uses it:
img:first-of-type {
@extend .image-base;
@include handle-img(20px 100px 10px 20px, center, left);
}
Extend
Mixins generate code, but extend adds selectors to the base.
The @extend at-rule is similar to a mixin, but it doesn’t copy declarations–it groups selectors and places them at an earlier location in the stylesheet. Because @extend moves the selector to an earlier location in the stylesheet, it might affect the cascade:
%message {
padding: 0.3em 0.5em;
border-radius: 0.5em;
}
.message-info {
@extend %message;
color: blue;
background-color: lightblue;
}
.message-danger {
@extend %message;
color: red;
background-color: pink;
}
/* output.css */
.message-danger, .message-info {
padding: 0.3em 0.5em;
border-radius: 0.5em;
}
.message-info {
color: blue;
background-color: lightblue;
}
.message-danger {
color: red;
background-color: pink;
}
@each
The @each at-rule iterates over all items in a list or map in order.
First, you have to create a map, which is a list of key/value pairs that define what you want to apply to elements. Here is a map named $callouts that applies color variables and a basic .callout class:
.callout {
border: 1px solid;
border-radius: 4px;
padding: 0.5rem 1rem;
}
$callouts: (
success: $success,
warning: $warning,
error: $error,
);
Next, create the loop with @each:
@each $type, $color in $callouts {
@debug $type, $color;
}
Here is the basic structure and a description of the key elements:
$type: the key in the map. For example,success$color: value of the key. For example,$success$callouts: map that you want to iterate over@debug: prints the$typeand$colorvalues to the console so we can make sure our loop is working correctly:sass/index.scss:81 Debug: success, #747d10 sass/index.scss:81 Debug: warning, #fc9d03 sass/index.scss:81 Debug: error, #940a0a
Finally, complete the loop. Here is the SASS input and truncated CSS output. This creates a rule named for each key in the map that does the following:
- adds the basic
.calloutsruleset with@extends - sets the border-color with the map value
- nests a pseudo-element using the map key as part of the content, and capitalizes the map key
/* SASS */
@each $type, $color in $callouts {
@debug $type, $color;
.#{$type} {
@extend .callout;
border-color: $color;
&::before {
content: "#{$type}: ";
text-transform: capitalize;
}
}
}
/* CSS output */
.callout, .error, .warning, .success {
border: 1px solid;
border-radius: 4px;
padding: 0.5rem 1rem;
}
.success {
border-color: #747d10;
}
.success::before {
content: "success: ";
text-transform: capitalize;
}
@if and @else conditionals
You can add conditional statements to your styles, much like a programming language like Java. Here, we reuse the map iteration example from @each to do the following:
- if the map key equals “error”, then change the font weight to
800 - otherwise, change the font weight to
500
@each $type, $color in $callouts {
@debug $type, $color;
.#{$type} {
@extend .callout;
background-color: scale-color($color, $lightness: +86%);
border-color: $color;
&::before {
content: "#{$type}: ";
text-transform: capitalize;
@if $type == "error" { /* conditionals */
font-weight: 800;
} @else {
font-weight: 500;
}
}
}
}
SCSS supports the following operators:
==: equals>: greater than<: less thanand: combinatoror: combinator
Color manipulation
The color.adjust() function lets you manipulate colors:
@use "sass:color";
.class {
property: color.adjust($var, <property>: <val>)
}
Use $lightness, $saturation, and $hue as properties to adjust. If you want to make something darker, use a negative value for $lightness, and to desaturate a color, use a negative value with $saturation:
@use "sass:color";
$green: #63a35c;
$green-dark: color.adjust($green, $lightness: -10%); /* darken */
$green-light: color.adjust($green, $lightness: 10%);
$green-vivid: color.adjust($green, $saturation: 20%);
$green-dull: color.adjust($green, $saturation: -20%); /* desaturate */
$purple: color.adjust($green, $hue: 180deg);
$yellow: color.adjust($green, $hue: -70deg);
/* use directly in a property */
.class {
background-color: color.adjust($green, $hue: -70deg);
}
scale-color
scale-color is a function that you can use to change the amount of red, green, blue, saturation, opacity, darkness, or lightness of a color. It works with either HSL or RGB, and you cannot mix the two.
This will come in handy when creating your color palette.
Here, we use the @each declaration we created earlier to add a background that increases the original color’s lightness by 86%:
@each $type, $color in $callouts {
@debug $type, $color;
.#{$type} {
@extend .callout;
background-color: scale-color($color, $lightness: +86%); /* increase lightness */
border-color: $color;
&::before {
content: "#{$type}: ";
text-transform: capitalize;
}
}
}
Interpolation
Interpolation lets you parameterize a parameter. For example:
padding-#{$direction}: 10rem;
In the previous example, you can define a $direction variable that takes the place of the # character. This is similar to template strings in Javascript or formatted strings in Python.
Loops
You can iterate over a value to produce a slight variation. You can use the $index literally by escaping it with the #{$index} syntax. This is called interpolation:
@for $index from 2 to 5 {
.nav-links > li:nth-child(#{$index}) {
transition-delay: (0.1s * $index) - 0.1s;
}
}
/* output */
.nav-links > li:nth-child(2) {
transition-delay: 0.1s;
}
.nav-links > li:nth-child(3) {
transition-delay: 0.2s;
}
.nav-links > li:nth-child(4) {
transition-delay: 0.3s;
}
PostCSS
PostCSS parses a source file and outputs a processed CSS file, but it is plugin-based. These plugins run sequentially, so make sure you understand which order you want to run them through.
Here is the Webpack documentation.
Steps
Install the dependencies:
npm install postcss postcss-cli autoprefixer cssnano sass --save-devThis command installed these packages:
postcss: The core PostCSS processor.postcss-cli:Command-line tool to run PostCSS.autoprefixer: Adds vendor prefixes for better browser support.cssnano: Minifies the final CSS.sass: Compiles SCSS to CSS.
Create a postcss.config.js file in the root of the project:
module.exports = { plugins: [ require("autoprefixer"), // Automatically adds vendor prefixes require("cssnano")({ preset: "default" }) // Minifies the CSS ] };Add the build scripts to your
package.jsonfile:... "scripts": { "build:scss": "sass sass/index.scss build/styles.css", "build:css": "postcss build/styles.css --use autoprefixer --use cssnano -o build/styles.min.css", "build": "npm run build:scss && npm run build:css" }, ...
Example
If you complete the setup in the previous steps, it adds vendor prefixes to your styles and minifies it in build/styles.min.css:
/* input */
.example {
backdrop-filter: blur(5px);
mask-image: url(star-mask.png);
}
/* styles.min.css */
.example{-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px);-webkit-mask-image:url(star-mask.png);mask-image:url(star-mask.png)}
/*# sourceMappingURL=data:application/json;base64,... */