Sass @extend, Functions and Loops
This chapter covers the last big Sass tools: sharing styles with @extend, calculating values with functions, and creating many rules at once with loops.
@extend and Placeholders
@extend lets one selector share the styles of another. A placeholder selector starts with %. It is never written to the CSS by itself; it only exists to be extended.
Example
%message {
padding: 12px;
border: 1px solid #ccc;
border-radius: 6px;
}
.success {
@extend %message;
border-color: green;
}
.error {
@extend %message;
border-color: red;
}.error, .success {
padding: 12px;
border: 1px solid #ccc;
border-radius: 6px;
}
.success {
border-color: green;
}
.error {
border-color: red;
}Instead of copying the styles twice (like a mixin would), Sass groups the selectors together into one rule.
Mixin or @extend?
Mixin (@include) | @extend |
|---|---|
| Copies the styles into each rule | Groups selectors into one shared rule |
| Can take arguments | Cannot take arguments |
| Works inside media queries from anywhere | Cannot extend across different media queries |
| Easy to understand | Can create surprising selector groups |
Many teams prefer mixins because they are more predictable. Use @extend only with placeholders, for elements that truly are "a kind of" the same thing.
Built-in Modules
Sass comes with modules full of useful functions. Load them with @use "sass:<name>".
| Module | Example functions |
|---|---|
sass:color | color.adjust(), color.scale(), color.mix(), color.complement() |
sass:math | math.div(), math.round(), math.max(), math.percentage() |
sass:string | string.to-upper-case(), string.length() |
sass:list | list.nth(), list.length(), list.append() |
sass:map | map.get(), map.has-key(), map.merge() |
Example
@use "sass:color";
@use "sass:math";
$brand: #cc6699;
.overlay {
background-color: color.adjust($brand, $alpha: -0.5);
}
.accent {
color: color.complement($brand);
}
.box {
width: math.round(33.7px);
height: math.max(40px, 64px, 52px);
}.overlay {
background-color: rgba(204, 102, 153, 0.5);
}
.accent {
color: #66cc99;
}
.box {
width: 34px;
height: 64px;
}color.adjust($brand, $lightness: -10%) makes a darker shade for hover states. In older code you will see darken() and lighten(), which are now deprecated.
Writing Your Own Function
A function takes values and returns a value with @return. Unlike a mixin, it does not output styles; you use it inside a declaration. This one changes pixels into rem:
Example
@use "sass:math";
@function rem($px, $base: 16px) {
@return math.div($px, $base) * 1rem;
}
h1 {
font-size: rem(32px);
}
.small-print {
font-size: rem(12px);
margin-top: rem(24px);
}h1 {
font-size: 2rem;
}
.small-print {
font-size: 0.75rem;
margin-top: 1.5rem;
}Conditions with @if and @else
Example
@mixin theme($dark: false) {
@if $dark {
background-color: #111827;
color: white;
} @else {
background-color: white;
color: #111827;
}
}
.page {
@include theme;
}
.night-page {
@include theme($dark: true);
}.page {
background-color: white;
color: #111827;
}
.night-page {
background-color: #111827;
color: white;
}Loops with @each and @for
Loops create many similar rules from a little code. #{...} is interpolation: it puts a variable's value into a selector or a name.
Example
$colors: (
"primary": #2563eb,
"success": #16a34a,
"danger": #dc2626
);
@each $name, $value in $colors {
.text-#{$name} {
color: $value;
}
}
@for $i from 1 through 3 {
.mt-#{$i} {
margin-top: $i * 8px;
}
}.text-primary {
color: #2563eb;
}
.text-success {
color: #16a34a;
}
.text-danger {
color: #dc2626;
}
.mt-1 {
margin-top: 8px;
}
.mt-2 {
margin-top: 16px;
}
.mt-3 {
margin-top: 24px;
}This is how frameworks like Bootstrap create hundreds of utility classes from a few maps.
Exercise
Exercise
What is the main difference between a Sass @function and a @mixin?
