SASS
Basics Compiled
@use
Used to include a file in the code.
The following shows how the file style.scss includes the files foundation/_code.scss and foundation/_lists.scss.
SCSS
foundation/_code.scss
code { padding: .25em; line-height: 0; }
foundation/_lists.scss
ul, ol { text-align: left; & & { padding: { bottom: 0; left: 0; } } }
style.scss
@use 'foundation/code'; @use 'foundation/lists';
CSS
style.css
code { padding: .25em; line-height: 0; } ul, ol { text-align: left; } ul ul, ol ol { padding-bottom: 0; padding-left: 0; }
Loading Members
The following shows how to utilize a mixin from an included file.
In the file style.scss, the file src/_corners.scss is included. The file src/_corners.scss has a mixin with the name rounded which is referenced in the definition of the class button
SCSS
src/_corners.scss
$radius: 3px; @mixin rounded { border-radius: $radius; }
style.scss
@use "src/corners"; .button { @include corners.rounded; padding: 5px + corners.$radius; }
CSS
style.css
.button { border-radius: 3px; padding: 8px; }
Namespace
The following is the same as the code above except it uses a namespace in the code of style.scss when it declares that corners can be referenced as c.
SCSS
src/_corners.scss
$radius: 3px; @mixin rounded { border-radius: $radius; }
style.scss
@use "src/corners" as c; .button { @include c.rounded; padding: 5px + c.$radius; }
CSS
style.css
.button { border-radius: 3px; padding: 8px; }
Defaults
Notice that the variables in the file _library.scss end with the text !default. In the file style.scss a couple of the variables are overwritten.
SCSS
_library.scss
$black: #000 !default; $border-radius: 0.25rem !default; $box-shadow: 0 0.5rem 1rem rgba($black, 0.15) !default; code { border-radius: $border-radius; box-shadow: $box-shadow; }
style.scss
@use 'library' with ( $black: #222, $border-radius: 0.1rem );
CSS
style.css
code { border-radius: 0.1rem; box-shadow: 0 0.5rem 1rem rgba(34, 34, 34, 0.15); }
Partials
As a convention, Sass files that are only meant to be loaded as modules, not compiled on their own, begin with _ (as in _code.scss). These are called partials, and they tell Sass tools not to try to compile those files on their own. You can leave off the _ when importing a partial.
Index Files
If you write an _index.scss or _index.sass in a folder, the index file will be loaded automatically when you load the URL for the folder itself.
Note in the following code that the file style.scss only loads the name of the folder where partials are stored. Because that folder contains an _index.scss file, it will automatically load. In this case the _index.scss file loads the partials _code.scss and _lists.scss.
SCSS
foundation/_code.scss
code { padding: .25em; line-height: 0; }
foundation/_lists.scss
ul, ol { text-align: left; & & { padding: { bottom: 0; left: 0; } } }
foundation/_index.scss
@use 'code'; @use 'lists';
style.scss
@use 'foundation';
CSS
style.css
code { padding: .25em; line-height: 0; } ul, ol { text-align: left; } ul ul, ol ol { padding-bottom: 0; padding-left: 0; }
Including A Plain CSS File
SCSS
code.css
code { padding: .25em; line-height: 0; }
style.scss
@use 'code';
CSS
style.css
code { padding: .25em; line-height: 0; }
@forward
SCSS
src/_list.scss
@mixin list-reset { margin: 0; padding: 0; list-style: none; }
bootstrap.scss
@forward "src/list";
styles.scss
@use "bootstrap"; li { @include bootstrap.list-reset; }
CSS
styles.css
li { margin: 0; padding: 0; list-style: none; }
Using A Prefix
SCSS
src/_list.scss
@mixin reset { margin: 0; padding: 0; list-style: none; }
bootstrap.scss
@forward "src/list" as list-*;
styles.scss
@use "bootstrap"; li { @include bootstrap.list-reset; }
CSS
styles.css
li { margin: 0; padding: 0; list-style: none; }
@mixin & @include
The following code declares two mixins reset-list and horizontal-list. The mixin reset-list is included in the mixin horizontal-list. At the bottom of the code the item nav ul includes the mixin horizontal-list.
SCSS
@mixin reset-list { margin: 0; padding: 0; list-style: none; } @mixin horizontal-list { @include reset-list; li { display: inline-block; margin: { left: -2px; right: 2em; } } } nav ul { @include horizontal-list; }
CSS
nav ul { margin: 0; padding: 0; list-style: none; } nav ul li { display: inline-block; margin-left: -2px; margin-right: 2em; }
Arguments
SCSS
@mixin rtl($property, $ltr-value, $rtl-value) { #{$property}: $ltr-value; [dir=rtl] & { #{$property}: $rtl-value; } } .sidebar { @include rtl(float, left, right); }
CSS
.sidebar { float: left; } [dir=rtl] .sidebar { float: right; }
Optional Arguments
SCSS
@mixin replace-text($image, $x: 50%, $y: 50%) { text-indent: -99999em; overflow: hidden; text-align: left; background: { image: $image; repeat: no-repeat; position: $x $y; } } .mail-icon { @include replace-text(url("/images/mail.svg"), 0); }
CSS
.mail-icon { text-indent: -99999em; overflow: hidden; text-align: left; background-image: url("/images/mail.svg"); background-repeat: no-repeat; background-position: 0 50%; }
Keyword Arguments
SCSS
@mixin square($size, $radius: 0) { width: $size; height: $size; @if $radius != 0 { border-radius: $radius; } } .avatar { @include square(100px, $radius: 4px); }
CSS
.avatar { width: 100px; height: 100px; border-radius: 4px; }
Argument List
SCSS
@mixin order($height, $selectors...) { @for $i from 0 to length($selectors) { #{nth($selectors, $i + 1)} { position: absolute; height: $height; margin-top: $i * $height; } } } @include order(150px, "input.name", "input.address", "input.zip");
CSS
input.name { position: absolute; height: 150px; margin-top: 0px; } input.address { position: absolute; height: 150px; margin-top: 150px; } input.zip { position: absolute; height: 150px; margin-top: 300px; }
Arbitrary Keyword Arguments
SCSS
@use "sass:meta"; @mixin syntax-colors($args...) { @debug meta.keywords($args); // (string: #080, comment: #800, variable: #60b) @each $name, $color in meta.keywords($args) { pre span.stx-#{$name} { color: $color; } } } @include syntax-colors( $string: #080, $comment: #800, $variable: #60b, )
CSS
pre span.stx-string { color: #080; } pre span.stx-comment { color: #800; } pre span.stx-variable { color: #60b; }
Passing Arbitrary Arguments
SCSS
$form-selectors: "input.name", "input.address", "input.zip" !default; @include order(150px, $form-selectors...);
Note: Because an argument list keeps track of both positional and keyword arguments, you use it to pass both at once to another mixin. That makes it super easy to define an alias for a mixin!
SCSS
@mixin btn($args...) { @warn "The btn() mixin is deprecated. Include button() instead."; @include button($args...); }
Content Blocks
SCSS
@mixin hover { &:not([disabled]):hover { @content; } } .button { border: 1px solid black; @include hover { border-width: 2px; } }
CSS
.button { border: 1px solid black; } .button:not([disabled]):hover { border-width: 2px; }
Passing Arguments to Content Blocks
SCSS
@mixin media($types...) { @each $type in $types { @media #{$type} { @content($type); } } } @include media(screen, print) using ($type) { h1 { font-size: 40px; @if $type == print { font-family: Calluna; } } }
CSS
@media screen { h1 { font-size: 40px; } } @media print { h1 { font-size: 40px; font-family: Calluna; } }
@function
SCSS
@function pow($base, $exponent) { $result: 1; @for $_ from 1 through $exponent { $result: $result * $base; } @return $result; } .sidebar { float: left; margin-left: pow(4, 3) * 1px; }
CSS
.sidebar { float: left; margin-left: 64px; }
Optional Arguments
SCSS
@function invert($color, $amount: 100%) { $inverse: change-color($color, $hue: hue($color) + 180); @return mix($inverse, $color, $amount); } $primary-color: #036; .header { background-color: invert($primary-color, 80%); }
CSS
.header { background-color: #523314; }
Keyword Arguments
SCSS
$primary-color: #036; .banner { background-color: $primary-color; color: scale-color($primary-color, $lightness: +40%); }
CSS
.banner { background-color: #036; color: #0a85ff; }
Taking Arbitrary Arguments
SCSS
@function sum($numbers...) { $sum: 0; @each $number in $numbers { $sum: $sum + $number; } @return $sum; } .micro { width: sum(50px, 30px, 100px); }
CSS
.micro { width: 180px; }
Passing Arbitrary Arguments
SCSS
$widths: 50px, 30px, 100px; .micro { width: min($widths...); }
CSS
.micro { width: 30px; }
@function fg($args...) { @warn "The fg() function is deprecated. Call foreground() instead."; @return foreground($args...); }
Interpolation
SCSS
@mixin corner-icon($name, $top-or-bottom, $left-or-right) { .icon-#{$name} { background-image: url("/icons/#{$name}.svg"); position: absolute; #{$top-or-bottom}: 0; #{$left-or-right}: 0; } } @include corner-icon("mail", top, left);
CSS
.icon-mail { background-image: url("/icons/mail.svg"); position: absolute; top: 0; left: 0; }
SCSS
@mixin inline-animation($duration) { $name: inline-#{unique-id()}; @keyframes #{$name} { @content; } animation-name: $name; animation-duration: $duration; animation-iteration-count: infinite; } .pulse { @include inline-animation(2s) { from { background-color: yellow } to { background-color: red } } }
CSS
.pulse { animation-name: inline-u6lc53cz3; animation-duration: 2s; animation-iteration-count: infinite; } @keyframes inline-u6lc53cz3 { from { background-color: yellow; } to { background-color: red; } }
Structure
Just like CSS, most Sass stylesheets are mainly made up of style rules that contain property declarations. But Sass stylesheets have many more features that can exist alongside these.
Statements
A Sass stylesheet is made up of a series of statements, which are evaluated in order to build the resulting CSS. Some statements may have blocks, defined using { and }, which contain other statements. For example, a style rule is a statement with a block. That block contains other statements, such as property declarations.
In SCSS, statements are separated by semicolons (which are optional if the statement uses a block). In the indented syntax, they're just separated by newlines.
Universal Statements
These types of statements can be used anywhere in a Sass stylesheet:
- Variable declarations, like $var: value.
- Flow control at-rules, like @if and @each.
- The @error, @warn, and @debug rules.
CSS Statements
These statements produce CSS. They can be used anywhere except within a @function:
- Style rules, like h1 { /* ... */ }.
- CSS at-rules, like @media and @font-face.
- Mixin uses using @include.
- The @at-root rule.
Top-Level Statements
These statements can only be used at the top level of a stylesheet, or nested within a CSS statement at the top level:
- Module loads, using @use.
- Imports, using @import.
- Mixin definitions using @mixin.
- Function definitions using @function.
Other Statements
- Property declarations like width: 100px may only be used within style rules and some CSS at-rules.
- The @extend rule may only be used within style rules.
Expressions
An expression is anything that goes on the right-hand side of a property or variable declaration. Each expression produces a value. Any valid CSS property value is also a Sass expression, but Sass expressions are much more powerful than plain CSS values. They're passed as arguments to mixins and functions, used for control flow with the @if rule, and manipulated using arithmetic. We call Sass's expression syntax SassScript.
Literals
The simplest expressions just represent static values:
- Numbers, which may or may not have units, like 12 or 100px.
- Strings, which may or may not have quotes, like "Helvetica Neue" or bold.
- Colors, which can be referred to by their hex representation or by name, like #c6538c or blue.
- The boolean literals true or false.
- The singleton null.
- Lists of values, which may be separated by spaces or commas and which may be enclosed in square brackets or no brackets at all, like 1.5em 1em 0 2em, Helvetica, Arial, sans-serif, or [col1-start].
- Maps that associate values with keys, like ("background": red, "foreground": pink).
Operations
Sass defines syntax for a number of operations:
- == and != are used to check if two values are the same.
- +, -, *, /, and % have their usual mathematical meaning for numbers, with special behaviors for units that matches the use of units in scientific math.
- <, <=, >, and >= check whether two numbers are greater or less than one another.
- and, or, and not have the usual boolean behavior. Sass considers every value “true” except for false and null.
- +, -, and / can be used to concatenate strings.
- ( and ) can be used to explicitly control the precedence order of operations.
Other Expressions
- Variables, like $var.
- Function calls, like nth($list, 1) or var(--main-bg-color), which may call Sass core library functions or user-defined functions, or which may be compiled directly to CSS.
- Special functions, like calc(1px + 100%) or url(http://myapp.com/assets/logo.png), that have their own unique parsing rules.
- The parent selector, &.
- The value !important, which is parsed as an unquoted string.
Comments
There are basically two types of SASS comments, those that are rendered in the CSS (Except in compressed mode), and those that are not rendered.
Unrendered comments are called silent comments. Silent comments are made with to successive forward slashes.
// This comment won't be included in the CSS. /* But this comment will, except in compressed mode. */ /* It can also contain interpolation: * 1 + 1 = #{1 + 1} */ /*! This comment will be included even in compressed mode. */ p /* Multi-line comments can be written anywhere * whitespace is allowed. */ .sans { font: Helvetica, // So can single-line commments. sans-serif; }
Variables
Sass variables are simple: you assign a value to a name that begins with $, and then you can refer to that name instead of the value itself.
A variable declaration looks a lot like a property declaration: it's written <variable>: <expression>. Unlike a property, which can only be declared in a style rule or at-rule, variables can be declared anywhere you want. To use a variable, just include it in a value.
SCSS
$base-color: #c6538c; $border-dark: rgba($base-color, 0.88); .alert { border: 1px solid $border-dark; }
CSS
.alert { border: 1px solid rgba(198, 83, 140, 0.88); }
Default Values
Normally when you assign a value to a variable, if that variable already had a value, its old value is overwritten. But if you're writing a Sass library, you might want to allow your users to configure your library's variables before you use them to generate CSS.
To make this possible, Sass provides the !default flag. This assigns a value to a variable only if that variable isn't defined or its value is null. Otherwise, the existing value will be used.
Variables defined with !default can be configured when loading a module with the @use rule. Sass libraries often use !default variables to allow their users to configure the library's CSS.
To load a module with configuration, write @use <url> with (<variable>: <value>, <variable>: <value>). The configured values will override the variables' default values. Only variables written at the top level of the stylesheet with a !default flag can be configured.
SCSS
// _library.scss $black: #000 !default; $border-radius: 0.25rem !default; $box-shadow: 0 0.5rem 1rem rgba($black, 0.15) !default; code { border-radius: $border-radius; box-shadow: $box-shadow; } // style.scss @use 'library' with ( $black: #222, $border-radius: 0.1rem );
CSS
code { border-radius: 0.1rem; box-shadow: 0 0.5rem 1rem rgba(34, 34, 34, 0.15); }
Scope
Variables declared at the top level of a stylesheet are global. This means that they can be accessed anywhere in their module after they've been declared. But that's not true for all variables. Those declared in blocks (curly braces in SCSS or indented code in Sass) are usually local, and can only be accessed within the block they were declared.
SCSS
$global-variable: global value; .content { $local-variable: local value; global: $global-variable; local: $local-variable; } .sidebar { global: $global-variable; // This would fail, because $local-variable isn't in scope: // local: $local-variable; }
CSS
.content { global: global value; local: local value; } .sidebar { global: global value; }
Shadowing
Local variables can even be declared with the same name as a global variable. If this happens, there are actually two different variables with the same name: one local and one global. This helps ensure that an author writing a local variable doesn't accidentally change the value of a global variable they aren't even aware of.
SCSS
$variable: global value; .content { $variable: local value; value: $variable; } .sidebar { value: $variable; }
CSS
.content { value: local value; } .sidebar { value: global value; }
If you need to set a global variable's value from within a local scope (such as in a mixin), you can use the !global flag. A variable declaration flagged as !global will always assign to the global scope.
SCSS
$variable: first global value; .content { $variable: second global value !global; value: $variable; } .sidebar { value: $variable; }
CSS
.content { value: second global value; } .sidebar { value: second global value; }
Flow Control Scope
Variables declared in flow control rules have special scoping rules: they don't shadow variables at the same level as the flow control rule. Instead, they just assign to those variables. This makes it much easier to conditionally assign a value to a variable, or build up a value as part of a loop.
SCSS
$dark-theme: true !default; $primary-color: #f8bbd0 !default; $accent-color: #6a1b9a !default; @if $dark-theme { $primary-color: darken($primary-color, 60%); $accent-color: lighten($accent-color, 60%); } .button { background-color: $primary-color; border: 1px solid $accent-color; border-radius: 3px; }
CSS
.button { background-color: #750c30; border: 1px solid #f5ebfc; border-radius: 3px; }
Interpolation
Interpolation can be used almost anywhere in a Sass stylesheet to embed the result of a SassScript expression into a chunk of CSS.
Just wrap an expression in #{} in any of the following places:
- Selectors in style rules
- Property names in declarations
- Custom property values
- CSS at-rules
- @extends
- Plain CSS @imports
- Quoted or unquoted strings
- Special functions
- Plain CSS function names
- Loud comments
SCSS
@mixin corner-icon($name, $top-or-bottom, $left-or-right) { .icon-#{$name} { background-image: url("/icons/#{$name}.svg"); position: absolute; #{$top-or-bottom}: 0; #{$left-or-right}: 0; } } @include corner-icon("mail", top, left);
CSS
.icon-mail { background-image: url("/icons/mail.svg"); position: absolute; top: 0; left: 0; }
In SassScript
Interpolation can be used in SassScript to inject SassScript into unquoted strings. This is particularly useful when dynamically generating names (for example for animations), or when using slash-separated values. Note that interpolation in SassScript always returns an unquoted string.
SCSS
@mixin inline-animation($duration) { $name: inline-#{unique-id()}; @keyframes #{$name} { @content; } animation-name: $name; animation-duration: $duration; animation-iteration-count: infinite; } .pulse { @include inline-animation(2s) { from { background-color: yellow } to { background-color: red } } }
CSS
.pulse { animation-name: inline-u6lc53cz3; animation-duration: 2s; animation-iteration-count: infinite; } @keyframes inline-u6lc53cz3 { from { background-color: yellow; } to { background-color: red; } }
At-Rules
Much of Sass's extra functionality comes in the form of new at-rules it adds on top of CSS:
@use |
The @use rule is intended to replace the old @import rule, but it's intentionally designed to work differently. It loads mixins, functions, and variables from other Sass stylesheets, and combines CSS from multiple stylesheets together. |
@forward | loads a Sass stylesheet and makes its mixins, functions, and variables available when your stylesheet is loaded with the @use rule. |
@import | Depreciated. Replaced by @use Extends the CSS at-rule to load styles, mixins, functions, and variables from other stylesheets. |
@mixin and @include |
Makes it easy to re-use chunks of styles. |
@function | Defines custom functions that can be used in SassScript expressions. |
@extend | Allows selectors to inherit styles from one another. |
@at-root | Puts styles within it at the root of the CSS document. |
@error | Causes compilation to fail with an error message. |
@warn | Prints a warning without stopping compilation entirely. |
@debug | Prints a message for debugging purposes. |
Flow control rules like @if, @each, @for, and @while control whether or how many times styles are emitted. |
Sass also has some special behavior for plain CSS at-rules: they can contain interpolation, and they can be nested in style rules. Some of them, like @media and @supports, also allow SassScript to be used directly in the rule itself without interpolation.
At Rule - @use
The @use rule loads mixins, functions, and variables from other Sass stylesheets, and combines CSS from multiple stylesheets together. Stylesheets loaded by @use are called "modules". Sass also provides built-in modules full of useful functions.
The simplest @use rule is written @use "<url>", which loads the module at the given URL. Any styles loaded this way will be included exactly once in the compiled CSS output, no matter how many times those styles are loaded.
Note: A stylesheet's @use rules must come before any rules other than @forward, including style rules. However, you can declare variables before @use rules to use when configuring modules.
SCSS
foundation/_code.scss
code { padding: .25em; line-height: 0; }
foundation/_lists.scss
ul, ol { text-align: left; & & { padding: { bottom: 0; left: 0; } } }
style.scss
@use 'foundation/code'; @use 'foundation/lists';
CSS
style.css
code { padding: .25em; line-height: 0; } ul, ol { text-align: left; } ul ul, ol ol { padding-bottom: 0; padding-left: 0; }
Loading Members
You can access variables, functions, and mixins from another module by writing <namespace>.<variable>, <namespace>.<function>(), or @include <namespace>.<mixin>(). By default, the namespace is just the last component of the module's URL.
Members (variables, functions, and mixins) loaded with @use are only visible in the stylesheet that loads them. Other stylesheets will need to write their own @use rules if they also want to access them. This helps make it easy to figure out exactly where each member is coming from. If you want to load members from many files at once, you can use the @forward rule to forward them all from one shared file.
Note: Because @use adds namespaces to member names, it's safe to choose very simple names like $radius or $width when writing a stylesheet. This is different from the old @import rule, which encouraged that users write long names like $mat-corner-radius to avoid conflicts with other libraries, and it helps keep your stylesheets clear and easy to read!
SCSS
src/_corners.scss
$radius: 3px; @mixin rounded { border-radius: $radius; }
style.scss
@use "src/corners"; .button { @include corners.rounded; padding: 5px + corners.$radius; }
CSS
style.css
.button { border-radius: 3px; padding: 8px; }
Choosing a Namespace
By default, a module's namespace is just the last component of its URL without a file extension. However, sometimes you might want to choose a different namespace—you might want to use a shorter name for a module you refer to a lot, or you might be loading multiple modules with the same filename. You can do this by writing @use "<url>" as <namespace>.
SCSS
src/_corners.scss
$radius: 3px; @mixin rounded { border-radius: $radius; }
style.scss
@use "src/corners" as c; .button { @include c.rounded; padding: 5px + c.$radius; }
CSS
style.css
.button { border-radius: 3px; padding: 8px; }
You can even load a module without a namespace by writing @use "<url>" as *. We recommend you only do this for stylesheets written by you, though; otherwise, they may introduce new members that cause name conflicts!
SCSS
src/_corners.scss
$radius: 3px; @mixin rounded { border-radius: $radius; }
style.scss
@use "src/corners" as *; .button { @include rounded; padding: 5px + $radius; }
CSS
style.css
.button { border-radius: 3px; padding: 8px; }
Private Members
As a stylesheet author, you may not want all the members you define to be available outside your stylesheet. Sass makes it easy to define a private member by starting its name with either - or _. These members will work just like normal within the stylesheet that defines them, but they won't be part of a module's public API. That means stylesheets that load your module can't see them!
Note: If you want to make a member private to an entire package rather than just a single module, just don't forward its module from any of your package's entrypoints (the stylesheets you tell your users to load to use your package). You can even hide that member while forwarding the rest of its module!
src/_corners.scss
$-radius: 3px; @mixin rounded { border-radius: $-radius; }
style.scss
@use "src/corners"; .button { @include corners.rounded; // This is an error! $-radius isn't visible outside of '_corners.scss'. padding: 5px + corners.$-radius; }
Configuration
A stylesheet can define variables with the !default flag to make them configurable. To load a module with configuration, write @use <url> with (<variable>: <value>, <variable>: <value>). The configured values will override the variables' default values.
SCSS
_library.scss
$black: #000 !default; $border-radius: 0.25rem !default; $box-shadow: 0 0.5rem 1rem rgba($black, 0.15) !default; code { border-radius: $border-radius; box-shadow: $box-shadow; }
style.scss
@use 'library' with ( $black: #222, $border-radius: 0.1rem );
CSS
style.css
code { border-radius: 0.1rem; box-shadow: 0 0.5rem 1rem rgba(34, 34, 34, 0.15); }
With Mixins
Configuring modules with @use ... with can be very handy, especially when using libraries that were originally written to work with the @import rule. But it's not particularly flexible, and we don't recommend it for more advanced use-cases. If you find yourself wanting to configure many variables at once, pass maps as configuration, or update the configuration after the module is loaded, consider writing a mixin to set your variables instead and another mixin to inject your styles.
_library.scss
$-black: #000; $-border-radius: 0.25rem; $-box-shadow: null; /// If the user has configured '$-box-shadow', returns their configured value. /// Otherwise returns a value derived from '$-black'. @function -box-shadow() { @return $-box-shadow or (0 0.5rem 1rem rgba($-black, 0.15)); } @mixin configure($black: null, $border-radius: null, $box-shadow: null) { @if $black { $-black: $black !global; } @if $border-radius { $-border-radius: $border-radius !global; } @if $box-shadow { $-box-shadow: $box-shadow !global; } } @mixin styles { code { border-radius: $-border-radius; box-shadow: -box-shadow(); } }
style.scss
@use 'library'; @include library.configure( $black: #222, $border-radius: 0.1rem ); @include library.styles;
Reassigning Variables
After loading a module, you can reassign its variables.
_library.scss
$color: red;
_override.scss
@use 'library'; library.$color: blue;
style.scss
@use 'library'; @use 'override'; @debug library.$color; //=> blue
This even works if you import a module without a namespace using as *. Assigning to a variable name defined in that module will overwrite its value in that module.
Finding the Module
It wouldn't be any fun to write out absolute URLs for every stylesheet you load, so Sass's algorithm for finding a module makes it a little easier. For starters, you don't have to explicitly write out the extension of the file you want to load; @use "variables" will automatically load variables.scss, variables.sass, or variables.css.
Partials
As a convention, Sass files that are only meant to be loaded as modules, not compiled on their own, begin with _ (as in _code.scss). These are called partials, and they tell Sass tools not to try to compile those files on their own. You can leave off the _ when importing a partial.
Index Files
If you write an _index.scss or _index.sass in a folder, the index file will be loaded automatically when you load the URL for the folder itself.
SCSS
foundation/_code.scss
code { padding: .25em; line-height: 0; }
foundation/_lists.scss
ul, ol { text-align: left; & & { padding: { bottom: 0; left: 0; } } }
foundation/_index.scss
@use 'code'; @use 'lists';
style.scss
@use 'foundation';
CSS
style.css
code { padding: .25em; line-height: 0; } ul, ol { text-align: left; } ul ul, ol ol { padding-bottom: 0; padding-left: 0; }
SCSS
code.css
code { padding: .25em; line-height: 0; }
style.scss
@use 'code';
CSS
style.css
code { padding: .25em; line-height: 0; }
CSS files loaded as modules don't allow any special Sass features and so can't expose any Sass variables, functions, or mixins. In order to make sure authors don't accidentally write Sass in their CSS, all Sass features that aren't also valid CSS will produce errors. Otherwise, the CSS will be rendered as-is. It can even be extended!
Differences From @import
The @use rule is intended to replace the old @import rule, but it's intentionally designed to work differently. Here are some major differences between the two:
- @use only makes variables, functions, and mixins available within the scope of the current file. It never adds them to the global scope. This makes it easy to figure out where each name your Sass file references comes from, and means you can use shorter names without any risk of collision.
- @use only ever loads each file once. This ensures you don't end up accidentally duplicating your dependencies' CSS many times over.
- @use must appear at the beginning your file, and cannot be nested in style rules.
- Each @use rule can only have one URL.
- @use requires quotes around its URL, even when using the indented syntax.
At Rule - @forward
The @forward rule loads a Sass stylesheet and makes its mixins, functions, and variables available when your stylesheet is loaded with the @use rule.
It makes it possible to organize Sass libraries across many files, while allowing their users to load a single entrypoint file.
The rule is written @forward "<url>". It loads the module at the given URL just like @use, but it makes the public members of the loaded module available to users of your module as though they were defined directly in your module. Those members aren't available in your module, though—if you want that, you'll need to write a @use rule as well. Don't worry, it'll only load the module once!
If you do write both a @forward and a @use for the same module in the same file, it's always a good idea to write the @forward first. That way, if your users want to configure the forwarded module, that configuration will be applied to the @forward before your @use loads it without any configuration.
Note: The @forward rule acts just like @use when it comes to a module's CSS.
Styles from a forwarded module will be included in the compiled CSS output, and the module with the @forward can extend it, even if it isn't also @used.
SCSS
src/_list.scss
@mixin list-reset { margin: 0; padding: 0; list-style: none; }
bootstrap.scss
@forward "src/list";
styles.scss
@use "bootstrap"; li { @include bootstrap.list-reset; }
CSS
styles.css
li { margin: 0; padding: 0; list-style: none; }
Adding a Prefix
Because module members are usually used with a namespace, short and simple names are usually the most readable option. But those names might not make sense outside the module they're defined in, so @forward has the option of adding an extra prefix to all the members it forwards.
This is written @forward "<url>" as <prefix>-*, and it adds the given prefix to the beginning of every mixin, function, and variable name forwarded by the module. For example, if the module defines a member named reset and it's forwarded as list-*, downstream stylesheets will refer to it as list-reset.
SCSS
src/_list.scss
@mixin reset { margin: 0; padding: 0; list-style: none; }
bootstrap.scss
@forward "src/list" as list-*;
styles.scss
@use "bootstrap"; li { @include bootstrap.list-reset; }
CSS
styles.css
li { margin: 0; padding: 0; list-style: none; }
Controlling Visibility
Sometimes, you don't want to forward every member from a module. You may want to keep some members private so that only your package can use them, or you may want to require your users to load some members a different way. You can control exactly which members get forwarded by writing @forward "<url>" hide <members...> or @forward "<url>" show <members...>.
The hide form means that the listed members shouldn't be forwarded, but everything else should. The show form means that only the named members should be forwarded. In both forms, you list the names of mixins, functions, or variables (including the $).
src/_list.scss
$horizontal-list-gap: 2em; @mixin list-reset { margin: 0; padding: 0; list-style: none; } @mixin list-horizontal { @include list-reset; li { display: inline-block; margin: { left: -2px; right: $horizontal-list-gap; } } }
bootstrap.scss
@forward "src/list" hide list-reset, $horizontal-list-gap;
Configuring Modules
The @forward rule can also load a module with configuration. This mostly works the same as it does for @use, with one addition: a @forward rule's configuration can use the !default flag in its configuration. This allows a module to change the defaults of an upstream stylesheet while still allowing downstream stylesheets to override them.
_library.scss
$black: #000 !default; $border-radius: 0.25rem !default; $box-shadow: 0 0.5rem 1rem rgba($black, 0.15) !default; code { border-radius: $border-radius; box-shadow: $box-shadow; }
_opinionated.scss
@forward 'library' with ( $black: #222 !default, $border-radius: 0.1rem !default );
style.scss
@use 'opinionated' with ($black: #333);
At Rule - @import
This rule is being depreciated. No need to define it.
At Rule - @mixin & @include
Mixins allow you to define styles that can be re-used throughout your stylesheet. They make it easy to avoid using non-semantic classes like .float-left, and to distribute collections of styles in libraries.
Mixins are defined using the @mixin at-rule, which is written @mixin <name> { ... } or @mixin name(<arguments...>) { ... }. A mixin's name can be any Sass identifier, and it can contain any statement other than top-level statements. They can be used to encapsulate styles that can be dropped into a single style rule; they can contain style rules of their own that can be nested in other rules or included at the top level of the stylesheet; or they can just serve to modify variables.
Mixins are included into the current context using the @include at-rule, which is written @include <name> or @include <name>(<arguments...>), with the name of the mixin being included.
SCSS
@mixin reset-list { margin: 0; padding: 0; list-style: none; } @mixin horizontal-list { @include reset-list; li { display: inline-block; margin: { left: -2px; right: 2em; } } } nav ul { @include horizontal-list; }
CSS
nav ul { margin: 0; padding: 0; list-style: none; } nav ul li { display: inline-block; margin-left: -2px; margin-right: 2em; }
Note: Mixin names, like all Sass identifiers, treat hyphens and underscores as identical. This means that reset-list and reset_list both refer to the same mixin. This is a historical holdover from the very early days of Sass, when it only allowed underscores in identifier names. Once Sass added support for hyphens to match CSS's syntax, the two were made equivalent to make migration easier.
Arguments
Mixins can also take arguments, which allows their behavior to be customized each time they're called. The arguments are specified in the @mixin rule after the mixin's name, as a list of variable names surrounded by parentheses. The mixin must then be included with the same number of arguments in the form of SassScript expressions. The values of these expression are available within the mixin's body as the corresponding variables.
SCSS
@mixin rtl($property, $ltr-value, $rtl-value) { #{$property}: $ltr-value; [dir=rtl] & { #{$property}: $rtl-value; } } .sidebar { @include rtl(float, left, right); }
CSS
.sidebar { float: left; } [dir=rtl] .sidebar { float: right; }
Optional Arguments
Normally, every argument a mixin declares must be passed when that mixin is included. However, you can make an argument optional by defining a default value which will be used if that argument isn't passed. Default values use the same syntax as variable declarations: the variable name, followed by a colon and a SassScript expression. This makes it easy to define flexible mixin APIs that can be used in simple or complex ways.
SCSS
@mixin replace-text($image, $x: 50%, $y: 50%) { text-indent: -99999em; overflow: hidden; text-align: left; background: { image: $image; repeat: no-repeat; position: $x $y; } } .mail-icon { @include replace-text(url("/images/mail.svg"), 0); }
CSS
.mail-icon { text-indent: -99999em; overflow: hidden; text-align: left; background-image: url("/images/mail.svg"); background-repeat: no-repeat; background-position: 0 50%; }
Keyword Arguments
When a mixin is included, arguments can be passed by name in addition to passing them by their position in the argument list. This is especially useful for mixins with multiple optional arguments, or with boolean arguments whose meanings aren't obvious without a name to go with them. Keyword arguments use the same syntax as variable declarations and optional arguments.
SCSS
@mixin square($size, $radius: 0) { width: $size; height: $size; @if $radius != 0 { border-radius: $radius; } } .avatar { @include square(100px, $radius: 4px); }
CSS
.avatar { width: 100px; height: 100px; border-radius: 4px; }
Taking Arbitrary Arguments
Sometimes it's useful for a mixin to be able to take any number of arguments. If the last argument in a @mixin declaration ends in ..., then all extra arguments to that mixin are passed to that argument as a list. This argument is known as an argument list.
SCSS
@mixin order($height, $selectors...) { @for $i from 0 to length($selectors) { #{nth($selectors, $i + 1)} { position: absolute; height: $height; margin-top: $i * $height; } } } @include order(150px, "input.name", "input.address", "input.zip");
CSS
input.name { position: absolute; height: 150px; margin-top: 0px; } input.address { position: absolute; height: 150px; margin-top: 150px; } input.zip { position: absolute; height: 150px; margin-top: 300px; }
Taking Arbitrary Keyword Arguments
Argument lists can also be used to take arbitrary keyword arguments. The meta.keywords() function takes an argument list and returns any extra keywords that were passed to the mixin as a map from argument names (not including $) to those arguments' values.
SCSS
@use "sass:meta"; @mixin syntax-colors($args...) { @debug meta.keywords($args); // (string: #080, comment: #800, variable: #60b) @each $name, $color in meta.keywords($args) { pre span.stx-#{$name} { color: $color; } } } @include syntax-colors( $string: #080, $comment: #800, $variable: #60b, )
CSS
pre span.stx-string { color: #080; } pre span.stx-comment { color: #800; } pre span.stx-variable { color: #60b; }
Note: If you don't ever pass an argument list to the meta.keywords() function, that argument list won't allow extra keyword arguments. This helps callers of your mixin make sure they haven't accidentally misspelled any argument names.
Passing Arbitrary Arguments
Just like argument lists allow mixins to take arbitrary positional or keyword arguments, the same syntax can be used to pass positional and keyword arguments to a mixin. If you pass a list followed by ... as the last argument of an include, its elements will be treated as additional positional arguments. Similarly, a map followed by ... will be treated as additional keyword arguments. You can even pass both at once!
SCSS
$form-selectors: "input.name", "input.address", "input.zip" !default; @include order(150px, $form-selectors...);
Note: Because an argument list keeps track of both positional and keyword arguments, you use it to pass both at once to another mixin. That makes it super easy to define an alias for a mixin!
SCSS
@mixin btn($args...) { @warn "The btn() mixin is deprecated. Include button() instead."; @include button($args...); }
Content Blocks
In addition to taking arguments, a mixin can take an entire block of styles, known as a content block. A mixin can declare that it takes a content block by including the @content at-rule in its body. The content block is passed in using curly braces like any other block in Sass, and it's injected in place of the @content rule.
SCSS
@mixin hover { &:not([disabled]):hover { @content; } } .button { border: 1px solid black; @include hover { border-width: 2px; } }
CSS
.button { border: 1px solid black; } .button:not([disabled]):hover { border-width: 2px; }
Passing Arguments to Content Blocks
A mixin can pass arguments to its content block the same way it would pass arguments to another mixin by writing @content(<arguments...>). The user writing the content block can accept arguments by writing @include <name> using (<arguments...>). The argument list for a content block works just like a mixin's argument list, and the arguments passed to it by @content work just like passing arguments to a mixin.
Note: If a mixin passes arguments to its content block, that content block must declare that it accepts those arguments. This means that it's a good idea to only pass arguments by position (rather than by name), and it means that passing more arguments is a breaking change.
If you want to be flexible in what information you pass to a content block, consider passing it a map that contains information it may need!
SCSS
@mixin media($types...) { @each $type in $types { @media #{$type} { @content($type); } } } @include media(screen, print) using ($type) { h1 { font-size: 40px; @if $type == print { font-family: Calluna; } } }
CSS
@media screen { h1 { font-size: 40px; } } @media print { h1 { font-size: 40px; font-family: Calluna; } }
At Rule - @function
Functions allow you to define complex operations on SassScript values that you can re-use throughout your stylesheet. They make it easy to abstract out common formulas and behaviors in a readable way.
Functions are defined using the @function at-rule, which is written @function <name>(<arguments...>) { ... }. A function's name can be any Sass identifier. It can only contain universal statements, as well as the @return at-rule which indicates the value to use as the result of the function call. Functions are called using the normal CSS function syntax.
SCSS
@function pow($base, $exponent) { $result: 1; @for $_ from 1 through $exponent { $result: $result * $base; } @return $result; } .sidebar { float: left; margin-left: pow(4, 3) * 1px; }
CSS
.sidebar { float: left; margin-left: 64px; }
Note: Function names, like all Sass identifiers, treat hyphens and underscores as identical. This means that scale-color and scale_color both refer to the same function. This is a historical holdover from the very early days of Sass, when it only allowed underscores in identifier names. Once Sass added support for hyphens to match CSS's syntax, the two were made equivalent to make migration easier.
Arguments
Arguments allow functions' behavior to be customized each time they're called. The arguments are specified in the @function rule after the function's name, as a list of variable names surrounded by parentheses. The function must be called with the same number of arguments in the form of SassScript expressions. The values of these expression are available within the function's body as the corresponding variables.
Note: Argument lists can also have trailing commas! This makes it easier to avoid syntax errors when refactoring your stylesheets.
Optional Arguments
Normally, every argument a function declares must be passed when that function is included. However, you can make an argument optional by defining a default value which will be used if that arguments isn't passed. Default values use the same syntax as variable declarations: the variable name, followed by a colon and a SassScript expression. This makes it easy to define flexible function APIs that can be used in simple or complex ways.
SCSS
@function invert($color, $amount: 100%) { $inverse: change-color($color, $hue: hue($color) + 180); @return mix($inverse, $color, $amount); } $primary-color: #036; .header { background-color: invert($primary-color, 80%); }
CSS
.header { background-color: #523314; }
Note: Default values can be any SassScript expression, and they can even refer to earlier arguments!
Keyword Arguments
When a function is called, arguments can be passed by name in addition to passing them by their position in the argument list. This is especially useful for functions with multiple optional arguments, or with boolean arguments whose meanings aren't obvious without a name to go with them. Keyword arguments use the same syntax as variable declarations and optional arguments.
SCSS
$primary-color: #036; .banner { background-color: $primary-color; color: scale-color($primary-color, $lightness: +40%); }
CSS
.banner { background-color: #036; color: #0a85ff; }
Note: Because any argument can be passed by name, be careful when renaming a function's arguments... it might break your users! It can be helpful to keep the old name around as an optional argument for a while and printing a warning if anyone passes it, so they know to migrate to the new argument.
Taking Arbitrary Arguments
Sometimes it's useful for a function to be able to take any number of arguments. If the last argument in a @function declaration ends in ..., then all extra arguments to that function are passed to that argument as a list. This argument is known as an argument list.
SCSS
@function sum($numbers...) { $sum: 0; @each $number in $numbers { $sum: $sum + $number; } @return $sum; } .micro { width: sum(50px, 30px, 100px); }
CSS
.micro { width: 180px; }
Taking Arbitrary Keyword Arguments
Argument lists can also be used to take arbitrary keyword arguments. The meta.keywords() function takes an argument list and returns any extra keywords that were passed to the function as a map from argument names (not including $) to those arguments' values.
Note: If you don't ever pass an argument list to the meta.keywords() function, that argument list won't allow extra keyword arguments. This helps callers of your function make sure they haven't accidentally misspelled any argument names.
Passing Arbitrary Arguments
Just like argument lists allow functions to take arbitrary positional or keyword arguments, the same syntax can be used to pass positional and keyword arguments to a function. If you pass a list followed by ... as the last argument of a function call, its elements will be treated as additional positional arguments. Similarly, a map followed by ... will be treated as additional keyword arguments. You can even pass both at once!
SCSS
$widths: 50px, 30px, 100px; .micro { width: min($widths...); }
CSS
.micro { width: 30px; }
Note: Because an argument list keeps track of both positional and keyword arguments, you use it to pass both at once to another function. That makes it super easy to define an alias for a function!
SCSS
@function fg($args...) { @warn "The fg() function is deprecated. Call foreground() instead."; @return foreground($args...); }
@return
The @return at-rule indicates the value to use as the result of calling a function. It's only allowed within a @function body, and each @function must end with a @return.
When a @return is encountered, it immediately ends the function and returns its result. Returning early can be useful for handling edge-cases or cases where a more efficient algorithm is available without wrapping the entire function in an @else block.
SCSS
@use "sass:string"; @function str-insert($string, $insert, $index) { // Avoid making new strings if we don't need to. @if string.length($string) == 0 { @return $insert; } $before: string.slice($string, 0, $index); $after: string.slice($string, $index); @return $before + $insert + $after; }
Other Functions
In addition to user-defined function, Sass provides a substantial core library of built-in functions that are always available to use. Sass implementations also make it possible to define custom functions in the host language. And of course, you can always call plain CSS functions (even ones with weird syntax).
Plain CSS Functions
Any function call that's not either a user-defined or built-in function is compiled to a plain CSS function (unless it uses Sass argument syntax). The arguments will be compiled to CSS and included as-is in the function call. This ensures that Sass supports all CSS functions without needing to release new versions every time a new one is added.
SCSS
@debug var(--main-bg-color); // var(--main-bg-color) $primary: #f2ece4; $accent: #e1d7d2; @debug radial-gradient($primary, $accent); // radial-gradient(#f2ece4, #e1d7d2)
Some CSS functions, like calc() and element() have unusual syntax. Sass parses these functions specially as unquoted strings.
At Rule - @extend
There are often cases when designing a page when one class should have all the styles of another class, as well as its own specific styles. For example, the BEM methodology encourages modifier classes that go on the same elements as block or element classes. But this can create cluttered HTML, it's prone to errors from forgetting to include both classes, and it can bring non-semantic style concerns into your markup.
<div class="error error--serious"> Oh no! You've been hacked! </div>
.error { border: 1px #f00; background-color: #fdd; } .error--serious { border-width: 3px; }
Sass's @extend rule solves this. It's written @extend <selector>, and it tells Sass that one selector should inherit the styles of another.
SCSS
.error { border: 1px #f00; background-color: #fdd; &--serious { @extend .error; border-width: 3px; } }
CSS
.error, .error--serious { border: 1px #f00; background-color: #fdd; } .error--serious { border-width: 3px; }
When one class extends another, Sass styles all elements that match the extender as though they also match the class being extended. When one class selector extends another, it works exactly as though you added the extended class to every element in your HTML that already had the extending class. You can just write class="error--serious", and Sass will make sure it's styled as though it had class="error" as well.
Of course, selectors aren't just used on their own in style rules. Sass knows to extend everywhere the selector is used. This ensures that your elements are styled exactly as if they matched the extended selector.
SCSS
.error:hover { background-color: #fee; } .error--serious { @extend .error; border-width: 3px; }
CSS
.error:hover, .error--serious:hover { background-color: #fee; } .error--serious { border-width: 3px; }
Note: Extends are resolved after the rest of your stylesheet is compiled. In particular, it happens after parent selectors are resolved. This means that if you @extend .error, it won't affect the inner selector in .error { &__icon { ... } }. It also means that parent selectors in SassScript can't see the results of extend.
How It Works
Unlike mixins, which copy styles into the current style rule, @extend updates style rules that contain the extended selector so that they contain the extending selector as well. When extending selectors, Sass does intelligent unification:
- It never generates selectors like #main#footer that can't possibly match any elements.
- It ensures that complex selectors are interleaved so that they work no matter which order the HTML elements are nested.
- It trims redundant selectors as much as possible, while still ensuring that the specificity is greater than or equal to that of the extender.
- It knows when one selector matches everything another does, and can combine them together.
- It intelligently handles combinators, universal selectors, and pseudo-classes that contain selectors.
.content nav.sidebar { @extend .info; } // This won't be extended, because `p` is incompatible with `nav`. p.info { background-color: #dee9fc; } // There's no way to know whether '<div class="guide">' will be inside or // outside '<div class="content">', so Sass generates both to be safe. .guide .info { border: 1px solid rgba(#000, 0.8); border-radius: 2px; } // Sass knows that every element matching "main.content" also matches ".content" // and avoids generating unnecessary interleaved selectors. main.content .info { font-size: 0.8em; }
Note: You can directly access Sass's intelligent unification using selector functions! The selector.unify() function returns a selector that matches the intersection of two selectors, while the selector.extend() function works just like @extend, but on a single selector.
Note: Because @extend updates style rules that contain the extended selector, their styles have precedence in the cascade based on where the extended selector's style rules appear, not based on where the @extend appears. This can be confusing, but just remember: this is the same precedence those rules would have if you added the extended class to your HTML!
Placeholder Selectors
Sometimes you want to write a style rule that's only intended to be extended. In that case, you can use placeholder selectors, which look like class selectors that start with % instead of .. Any selectors that include placeholders aren't included in the CSS output, but selectors that extend them are.
SCSS
.alert:hover, %strong-alert { font-weight: bold; } %strong-alert:hover { color: red; }
CSS
.alert:hover { font-weight: bold; }
Private Placeholders
Like module members, a placeholder selector can be marked private by starting its name with either - or _. A private placeholder selector can only be extended within the stylesheet that defines it. To any other stylesheets, it will look as though that selector doesn't exist.
Extension Scope
When one stylesheet extends a selector, that extension will only affect style rules written in upstream modules—that is, modules that are loaded by that stylesheet using the @use rule or the @forward rule, modules loaded by those modules, and so on. This helps make your @extend rules more predictable, ensuring that they affect only the styles you were aware of when you wrote them.
Note: Extensions aren't scoped at all if you're using the @import rule. Not only will they affect every stylesheet you import, they'll affect every stylesheet that imports your stylesheet, everything else those stylesheets import, and so on. Without @use, extensions are global.
Mandatory and Optional Extends
Normally, if an @extend doesn't match any selectors in the stylesheet, Sass will produce an error. This helps protect from typos or from renaming a selector without renaming the selectors that inherit from it. Extends that require that the extended selector exists are mandatory.
This may not always be what you want, though. If you want the @extend to do nothing if the extended selector doesn't exist, just add !optional to the end.
Extends or Mixins?
Extends and mixins are both ways of encapsulating and re-using styles in Sass, which naturally raises the question of when to use which one. Mixins are obviously necessary when you need to configure the styles using arguments, but what if they're just a chunk of styles?
As a rule of thumb, extends are the best option when you're expressing a relationship between semantic classes (or other semantic selectors). Because an element with class .error--serious is an error, it makes sense for it to extend .error. But for non-semantic collections of styles, writing a mixin can avoid cascade headaches and make it easier to configure down the line.
Note: Most web servers compress the CSS they serve using an algorithm that's very good at handling repeated chunks of identical text. This means that, although mixins may produce more CSS than extends, they probably won't substantially increase the amount your users need to download. So choose the feature that makes the most sense for your use-case, not the one that generates the least CSS!
At Rule - @error
When writing mixins and functions that take arguments, you usually want to ensure that those arguments have the types and formats your API expects. If they aren't, the user needs to be notified and your mixin/function needs to stop running.
Sass makes this easy with the @error rule, which is written @error <expression>. It prints the value of the expression (usually a string) along with a stack trace indicating how the current mixin or function was called. Once the error is printed, Sass stops compiling the stylesheet and tells whatever system is running it that an error occurred.
@mixin reflexive-position($property, $value) { @if $property != left and $property != right { @error "Property #{$property} must be either left or right."; } $left-value: if($property == right, initial, $value); $right-value: if($property == right, $value, initial); left: $left-value; right: $right-value; [dir=rtl] & { left: $right-value; right: $left-value; } } .sidebar { @include reflexive-position(top, 12px); // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // Error: Property top must be either left or right. }
The exact format of the error and stack trace varies from implementation to implementation, and can also depend on your build system. This is what it looks like in Dart Sass when run from the command line:
Error: "Property top must be either left or right." ╷ 3 │ @error "Property #{$property} must be either left or right."; │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ╵ example.scss 3:5 reflexive-position() example.scss 19:3 root stylesheet
At Rule - @warn
When writing mixins and functions, you may want to discourage users from passing certain arguments or certain values. They may be passing legacy arguments that are now deprecated, or they may be calling your API in a way that's not quite optimal.
The @warn rule is designed just for that. It's written @warn <expression> and it prints the value of the expression (usually a string) for the user, along with a stack trace indicating how the current mixin or function was called. Unlike the @error rule, though, it doesn't stop Sass entirely.
SCSS
$known-prefixes: webkit, moz, ms, o; @mixin prefix($property, $value, $prefixes) { @each $prefix in $prefixes { @if not index($known-prefixes, $prefix) { @warn "Unknown prefix #{$prefix}."; } -#{$prefix}-#{$property}: $value; } #{$property}: $value; } .tilt { // Oops, we typo'd "webkit" as "wekbit"! @include prefix(transform, rotate(15deg), wekbit ms); }
CSS
.tilt { -wekbit-transform: rotate(15deg); -ms-transform: rotate(15deg); transform: rotate(15deg); }
The exact format of the warning and stack trace varies from implementation to implementation. This is what it looks like in Dart Sass:
Warning: Unknown prefix wekbit. example.scss 6:7 prefix() example.scss 16:3 root stylesheet
At Rule - @debug
Sometimes it's useful to see the value of a variable or expression while you're developing your stylesheet. That's what the @debug rule is for: it's written @debug <expression>, and it prints the value of that expression, along with the filename and line number.
@mixin inset-divider-offset($offset, $padding) { $divider-offset: (2 * $padding) + $offset; @debug "divider offset: #{$divider-offset}"; margin-left: $divider-offset; width: calc(100% - #{$divider-offset}); }
The exact format of the debug message varies from implementation to implementation. This is what it looks like in Dart Sass:
test.scss:3 Debug: divider offset: 132px
Note: You can pass any value to @debug, not just a string! It prints the same representation of that value as the meta.inspect() function.
At Rule - @at-root
The @at-root rule is usually written @at-root <selector> { ... } and causes everything within it to be emitted at the root of the document instead of using the normal nesting. It's most often used when doing advanced nesting with the SassScript parent selector and selector functions.
For example, suppose you want to write a selector that matches the outer selector and an element selector. You could write a mixin like this one that uses the selector.unify() function to combine & with a user's selector.
SCSS
@use "sass:selector"; @mixin unify-parent($child) { @at-root #{selector.unify(&, $child)} { @content; } } .wrapper .field { @include unify-parent("input") { /* ... */ } @include unify-parent("select") { /* ... */ } }
CSS
.wrapper input.field { /* ... */ } .wrapper select.field { /* ... */ }
The @at-root rule is necessary here because Sass doesn't know what interpolation was used to generate a selector when it's performing selector nesting. This means it will automatically add the outer selector to the inner selector even if you used & as a SassScript expression. The @at-root explicitly tells Sass not to include the outer selector.
Note: The @at-root rule can also be written @at-root { ... } to put multiple style rules at the root of the document. In fact, @at-root <selector> { ... } is just a shorthand for @at-root { <selector> { ... } }!
Beyond Style Rules
On its own, @at-root only gets rid of style rules. Any at-rules like @media or @supports will be left in. If this isn't what you want, though, you can control exactly what it includes or includes using syntax like media query features, written @at-root (with: <rules...>) { ... } or @at-root (without: <rules...>) { ... }. The (without: ...) query tells Sass which rules should be excluded; the (with: ...) query excludes all rules except those that are listed.
SCSS
@media print { .page { width: 8in; @at-root (without: media) { color: #111; } @at-root (with: rule) { font-size: 1.2em; } } }
CSS
@media print { .page { width: 8in; } } .page { color: #111; } .page { font-size: 1.2em; }
In addition to the names of at-rules, there are two special values that can be used in queries:
- rule refers to style rules. For example, @at-root (with: rule) excludes all at-rules but preserves style rules.
- all refers to all at-rules and style rules should be excluded.
Flow Control
Sass provides a number of at-rules that make it possible to control whether styles get emitted, or to emit them multiple times with small variations. They can also be used in mixins and functions to write small algorithms to make writing your Sass easier. Sass supports four flow control rules:- @if controls whether or not a block is evaluated.
- @each evaluates a block for each element in a list or each pair in a map.
- @for evaluates a block a certain number of times.
- @while evaluates a block until a certain condition is met.
Flow Control - @if
The @if rule is written @if <expression> { ... }, and it controls whether or not its block gets evaluated (including emitting any styles as CSS). The expression usually returns either true or false—if the expression returns true, the block is evaluated, and if the expression returns false it's not.
SCSS
@mixin avatar($size, $circle: false) { width: $size; height: $size; @if $circle { border-radius: $size / 2; } } .square-av { @include avatar(100px, $circle: false); } .circle-av { @include avatar(100px, $circle: true); }
CSS
.square-av { width: 100px; height: 100px; } .circle-av { width: 100px; height: 100px; border-radius: 50px; }
@else
An @if rule can optionally be followed by an @else rule, written @else { ... }. This rule's block is evaluated if the @if expression returns false.
SCSS
$light-background: #f2ece4; $light-text: #036; $dark-background: #6b717f; $dark-text: #d2e1dd; @mixin theme-colors($light-theme: true) { @if $light-theme { background-color: $light-background; color: $light-text; } @else { background-color: $dark-background; color: $dark-text; } } .banner { @include theme-colors($light-theme: true); body.dark & { @include theme-colors($light-theme: false); } }
CSS
.banner { background-color: #f2ece4; color: #036; } body.dark .banner { background-color: #6b717f; color: #d2e1dd; }
Conditional expressions may contain boolean operators (and, or, not).
@else if
You can also choose whether to evaluate an @else rule's block by writing it @else if <expression> { ... }. If you do, the block is evaluated only if the preceding @if's expression returns false and the @else if's expression returns true.
In fact, you can chain as many @else ifs as you want after an @if. The first block in the chain whose expression returns true will be evaluated, and no others. If there's a plain @else at the end of the chain, its block will be evaluated if every other block fails.
SCSS
@use "sass:math"; @mixin triangle($size, $color, $direction) { height: 0; width: 0; border-color: transparent; border-style: solid; border-width: math.div($size, 2); @if $direction == up { border-bottom-color: $color; } @else if $direction == right { border-left-color: $color; } @else if $direction == down { border-top-color: $color; } @else if $direction == left { border-right-color: $color; } @else { @error "Unknown direction #{$direction}."; } } .next { @include triangle(5px, black, right); }
CSS
.next { height: 0; width: 0; border-color: transparent; border-style: solid; border-width: 2.5px; border-left-color: black; }
Truthiness and Falsiness
Anywhere true or false are allowed, you can use other values as well. The values false and null are falsey, which means Sass considers them to indicate falsehood and cause conditions to fail. Every other value is considered truthy, so Sass considers them to work like true and cause conditions to succeed.
For example, if you want to check if a string contains a space, you can just write string.index($string, " "). The string.index() function returns null if the string isn't found and a number otherwise.
Note: Some languages consider more values falsey than just false and null. Sass isn't one of those languages! Empty strings, empty lists, and the number 0 are all truthy in Sass.
Flow Control - @each
The @each rule makes it easy to emit styles or evaluate code for each element of a list or each pair in a map. It's great for repetitive styles that only have a few variations between them. It's usually written @each <variable> in <expression> { ... }, where the expression returns a list. The block is evaluated for each element of the list in turn, which is assigned to the given variable name.
SCSS
$sizes: 40px, 50px, 80px; @each $size in $sizes { .icon-#{$size} { font-size: $size; height: $size; width: $size; } }
CSS
.icon-40px { font-size: 40px; height: 40px; width: 40px; } .icon-50px { font-size: 50px; height: 50px; width: 50px; } .icon-80px { font-size: 80px; height: 80px; width: 80px; }
With Maps
You can also use @each to iterate over every key/value pair in a map by writing it @each <variable>, <variable> in <expression> { ... }. The key is assigned to the first variable name, and the element is assigned to the second.
SCSS
$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
@charset "UTF-8"; .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: ""; }
Destructuring
If you have a list of lists, you can use @each to automatically assign variables to each of the values from the inner lists by writing it @each <variable...> in <expression> { ... }. This is known as destructuring, since the variables match the structure of the inner lists. Each variable name is assigned to the value at the corresponding position in the list, or null if the list doesn't have enough values.
SCSS
$icons: "eye" "\f112" 12px, "start" "\f12e" 16px, "stop" "\f12f" 10px; @each $name, $glyph, $size in $icons { .icon-#{$name}:before { display: inline-block; font-family: "Icon Font"; content: $glyph; font-size: $size; } }
CSS
@charset "UTF-8"; .icon-eye:before { display: inline-block; font-family: "Icon Font"; content: ""; font-size: 12px; } .icon-start:before { display: inline-block; font-family: "Icon Font"; content: ""; font-size: 16px; } .icon-stop:before { display: inline-block; font-family: "Icon Font"; content: ""; font-size: 10px; }
Note: Because @each supports destructuring and maps count as lists of lists, @each's map support works without needing special support for maps in particular.
Flow Control - @for
The @for rule, written @for <variable> from <expression> to <expression> { ... } or @for <variable> from <expression> through <expression> { ... }, counts up or down from one number (the result of the first expression) to another (the result of the second) and evaluates a block for each number in between. Each number along the way is assigned to the given variable name. If to is used, the final number is excluded; if through is used, it's included.
SCSS
$base-color: #036; @for $i from 1 through 3 { ul:nth-child(3n + #{$i}) { background-color: lighten($base-color, $i * 5%); } }
CSS
ul:nth-child(3n + 1) { background-color: #004080; } ul:nth-child(3n + 2) { background-color: #004d99; } ul:nth-child(3n + 3) { background-color: #0059b3; }
Flow Control - @while
The @while rule, written @while <expression> { ... }, evaluates its block if its expression returns true. Then, if its expression still returns true, it evaluates its block again. This continues until the expression finally returns false.
SCSS
@use "sass:math"; /// Divides `$value` by `$ratio` until it's below `$base`. @function scale-below($value, $base, $ratio: 1.618) { @while $value > $base { $value: math.div($value, $ratio); } @return $value; } $normal-font-size: 16px; sup { font-size: scale-below(20px, 16px); }
CSS
sup { font-size: 12.36094px; }
Note: Although @while is necessary for a few particularly complex stylesheets, you're usually better of using either @each or @for if either of them will work. They're clearer for the reader, and often faster to compile as well.
Truthiness and Falsiness
Anywhere true or false are allowed, you can use other values as well. The values false and null are falsey, which means Sass considers them to indicate falsehood and cause conditions to fail. Every other value is considered truthy, so Sass considers them to work like true and cause conditions to succeed.
For example, if you want to check if a string contains a space, you can just write string.index($string, " "). The string.index() function returns null if the string isn't found and a number otherwise.
Note: Some languages consider more values falsey than just false and null. Sass isn't one of those languages! Empty strings, empty lists, and the number 0 are all truthy in Sass.
Nesting
When writing HTML you've probably noticed that it has a clear nested and visual hierarchy. CSS, on the other hand, doesn't.
Sass will let you nest your CSS selectors in a way that follows the same visual hierarchy of your HTML. Be aware that overly nested rules will result in over-qualified CSS that could prove hard to maintain and is generally considered bad practice.
With that in mind, here's an example of some typical styles for a site's navigation:
SCSS
nav { ul { margin: 0; padding: 0; list-style: none; } li { display: inline-block; } a { display: block; padding: 6px 12px; text-decoration: none; } }
CSS
nav ul { margin: 0; padding: 0; list-style: none; } nav li { display: inline-block; } nav a { display: block; padding: 6px 12px; text-decoration: none; }
You'll notice that the ul, li, and a selectors are nested inside the nav selector. This is a great way to organize your CSS and make it more readable.
Partials
Partials
You can create partial Sass files that contain little snippets of CSS that you can include in other Sass files. This is a great way to modularize your CSS and help keep things easier to maintain. A partial is a Sass file named with a leading underscore. You might name it something like _partial.scss. The underscore lets Sass know that the file is only a partial file and that it should not be generated into a CSS file. Sass partials are used with the @use rule.
Modules
Modules
You don't have to write all your Sass in a single file. You can split it up however you want with the @use rule. This rule loads another Sass file as a module, which means you can refer to its variables, mixins, and functions in your Sass file with a namespace based on the filename. Using a file will also include the CSS it generates in your compiled output!
SCSS
_base.scss
$font-stack: Helvetica, sans-serif; $primary-color: #333; body { font: 100% $font-stack; color: $primary-color; }
styles.scss
@use 'base'; .inverse { background-color: base.$primary-color; color: white; }
CSS
body { font: 100% Helvetica, sans-serif; color: #333; } .inverse { background-color: #333; color: white; }
Notice we're using @use 'base'; in the styles.scss file. When you use a file you don't need to include the file extension. Sass is smart and will figure it out for you.
Mixins
Some things in CSS are a bit tedious to write, especially with CSS3 and the many vendor prefixes that exist. A mixin lets you make groups of CSS declarations that you want to reuse throughout your site. It helps keep your Sass very DRY. You can even pass in values to make your mixin more flexible. Here's an example for theme.
SCSS
@mixin theme($theme: DarkGray) { background: $theme; box-shadow: 0 0 1px rgba($theme, .25); color: #fff; } .info { @include theme; } .alert { @include theme($theme: DarkRed); } .success { @include theme($theme: DarkGreen); }
CSS
.info { background: DarkGray; box-shadow: 0 0 1px rgba(169, 169, 169, 0.25); color: #fff; } .alert { background: DarkRed; box-shadow: 0 0 1px rgba(139, 0, 0, 0.25); color: #fff; } .success { background: DarkGreen; box-shadow: 0 0 1px rgba(0, 100, 0, 0.25); color: #fff; }
To create a mixin you use the @mixin directive and give it a name. We've named our mixin theme. We're also using the variable $theme inside the parentheses so we can pass in a theme of whatever we want. After you create your mixin, you can then use it as a CSS declaration starting with @include followed by the name of the mixin.
Extend//Inheritance
Extend/Inheritance
Using @extend lets you share a set of CSS properties from one selector to another. In our example we're going to create a simple series of messaging for errors, warnings and successes using another feature which goes hand in hand with extend, placeholder classes. A placeholder class is a special type of class that only prints when it is extended, and can help keep your compiled CSS neat and clean.
/* This CSS will print because %message-shared is extended. */ %message-shared { border: 1px solid #ccc; padding: 10px; color: #333; } // This CSS won't print because %equal-heights is never extended. %equal-heights { display: flex; flex-wrap: wrap; } .message { @extend %message-shared; } .success { @extend %message-shared; border-color: green; } .error { @extend %message-shared; border-color: red; } .warning { @extend %message-shared; border-color: yellow; }
What the above code does is tells .message, .success, .error, and .warning to behave just like %message-shared. That means anywhere that %message-shared shows up, .message, .success, .error, & .warning will too. The magic happens in the generated CSS, where each of these classes will get the same CSS properties as %message-shared. This helps you avoid having to write multiple class names on HTML elements.
You can extend most simple CSS selectors in addition to placeholder classes in Sass, but using placeholders is the easiest way to make sure you aren't extending a class that's nested elsewhere in your styles, which can result in unintended selectors in your CSS.
Note that the CSS in %equal-heights isn't generated, because %equal-heights is never extended.
Operators
Doing math in your CSS is very helpful. Sass has a handful of standard math operators like +, -, *, math.div(), and %. In our example we're going to do some simple math to calculate widths for an article and aside.
SCSS
@use "sass:math"; .container { display: flex; } article[role="main"] { width: math.div(600px, 960px) * 100%; } aside[role="complementary"] { width: math.div(300px, 960px) * 100%; margin-left: auto; }
CSS
.container { display: flex; } article[role="main"] { width: 62.5%; } aside[role="complementary"] { width: 31.25%; margin-left: auto; }
We've created a very simple fluid grid, based on 960px. Operations in Sass let us do something like take pixel values and convert them to percentages without much hassle.
Functions
CSS defines many functions, and most of them work just fine with Sass's normal function syntax. They're parsed as function calls, resolved to plain CSS functions, and compiled as-is to CSS. There are a few exceptions, though, which have special syntax that can't just be parsed as a SassScript expression. All special function calls return unquoted strings.
url()
The url() function is commonly used in CSS, but its syntax is different than other functions: it can take either a quoted or unquoted URL. Because an unquoted URL isn't a valid SassScript expression, Sass needs special logic to parse it.
If the url()'s argument is a valid unquoted URL, Sass parses it as-is, although interpolation may also be used to inject SassScript values.
If it's not a valid unquoted URL—for example, if it contains variables or function calls—it's parsed as a normal plain CSS function call.
$roboto-font-path: "../fonts/roboto"; @font-face { // This is parsed as a normal function call that takes a quoted string. src: url("#{$roboto-font-path}/Roboto-Thin.woff2") format("woff2"); font-family: "Roboto"; font-weight: 100; } @font-face { // This is parsed as a normal function call that takes an arithmetic // expression. src: url($roboto-font-path + "/Roboto-Light.woff2") format("woff2"); font-family: "Roboto"; font-weight: 300; } @font-face { // This is parsed as an interpolated special function. src: url(#{$roboto-font-path}/Roboto-Regular.woff2) format("woff2"); font-family: "Roboto"; font-weight: 400; }
The element() function is defined in the CSS spec, and because its IDs could be parsed as colors, they need special parsing.
expression() and functions beginning with progid: are legacy Internet Explorer features that use non-standard syntax. Although they're no longer supported by recent browsers, Sass continues to parse them for backwards compatibility.
Sass allows any text in these function calls, including nested parentheses. Nothing is interpreted as a SassScript expression, with the exception that interpolation can be used to inject dynamic values.
SCSS
$logo-element: logo-bg; .logo { background: element(##{$logo-element}); }
CSS
.logo { background: element(#logo-bg); }