diff --git a/eleventy.config.js b/eleventy.config.js
index 11a24dfe7..c38ed5a9b 100644
--- a/eleventy.config.js
+++ b/eleventy.config.js
@@ -16,6 +16,7 @@ const datesPlugin = require('./source/helpers/dates.ts').default;
const { liquidEngine, markdownEngine } = require('./source/helpers/engines.ts');
const pagesPlugin = require('./source/helpers/pages.ts').default;
const typePlugin = require('./source/helpers/type.ts').default;
+const functionPlugin = require('./source/helpers/function.ts').default;
/** @param {import('@11ty/eleventy').UserConfig} eleventyConfig */
module.exports = (eleventyConfig) => {
@@ -43,6 +44,7 @@ module.exports = (eleventyConfig) => {
eleventyConfig.addPlugin(datesPlugin);
eleventyConfig.addPlugin(pagesPlugin);
eleventyConfig.addPlugin(typePlugin);
+ eleventyConfig.addPlugin(functionPlugin);
// rss plugin
eleventyConfig.addLiquidFilter('absoluteUrl', absoluteUrl);
diff --git a/source/code-snippets/example-advanced-nesting.liquid b/source/_includes/code_snippets/example-advanced-nesting.liquid
similarity index 100%
rename from source/code-snippets/example-advanced-nesting.liquid
rename to source/_includes/code_snippets/example-advanced-nesting.liquid
diff --git a/source/code-snippets/example-each-list.liquid b/source/_includes/code_snippets/example-each-list.liquid
similarity index 100%
rename from source/code-snippets/example-each-list.liquid
rename to source/_includes/code_snippets/example-each-list.liquid
diff --git a/source/code-snippets/example-each-map.liquid b/source/_includes/code_snippets/example-each-map.liquid
similarity index 100%
rename from source/code-snippets/example-each-map.liquid
rename to source/_includes/code_snippets/example-each-map.liquid
diff --git a/source/_includes/code_snippets/example-first-class-function.liquid b/source/_includes/code_snippets/example-first-class-function.liquid
new file mode 100644
index 000000000..2681d856a
--- /dev/null
+++ b/source/_includes/code_snippets/example-first-class-function.liquid
@@ -0,0 +1,59 @@
+{% comment -%}
+ TODO(nweiz): auto-generate this CSS once we're compiling with Dart Sass
+{%- endcomment -%}
+{% codeExample 'first-class-function' %}
+ @use "sass:list";
+ @use "sass:meta";
+ @use "sass:string";
+
+ /// Return a copy of $list with all elements for which $condition returns `true`
+ /// removed.
+ @function remove-where($list, $condition) {
+ $new-list: ();
+ $separator: list.separator($list);
+ @each $element in $list {
+ @if not meta.call($condition, $element) {
+ $new-list: list.append($new-list, $element, $separator: $separator);
+ }
+ }
+ @return $new-list;
+ }
+
+ $fonts: Tahoma, Geneva, "Helvetica Neue", Helvetica, Arial, sans-serif;
+
+ content {
+ @function contains-helvetica($string) {
+ @return string.index($string, "Helvetica");
+ }
+ font-family: remove-where($fonts, meta.get-function("contains-helvetica"));
+ }
+ ===
+ @use "sass:list"
+ @use "sass:meta"
+ @use "sass:string"
+
+ /// Return a copy of $list with all elements for which $condition returns `true`
+ /// removed.
+ @function remove-where($list, $condition)
+ $new-list: ()
+ $separator: list.separator($list)
+ @each $element in $list
+ @if not meta.call($condition, $element)
+ $new-list: list.append($new-list, $element, $separator: $separator)
+
+
+ @return $new-list
+
+
+ $fonts: Tahoma, Geneva, "Helvetica Neue", Helvetica, Arial, sans-serif
+
+ .content
+ @function contains-helvetica($string)
+ @return string.index($string, "Helvetica")
+
+ font-family: remove-where($fonts, meta.get-function("contains-helvetica"))
+ ===
+ .content {
+ font-family: Tahoma, Geneva, Arial, sans-serif;
+ }
+{% endcodeExample %}
diff --git a/source/code-snippets/example-if-parent-selector.liquid b/source/_includes/code_snippets/example-if-parent-selector.liquid
similarity index 100%
rename from source/code-snippets/example-if-parent-selector.liquid
rename to source/_includes/code_snippets/example-if-parent-selector.liquid
diff --git a/source/code-snippets/example-if.liquid b/source/_includes/code_snippets/example-if.liquid
similarity index 100%
rename from source/code-snippets/example-if.liquid
rename to source/_includes/code_snippets/example-if.liquid
diff --git a/source/_includes/code_snippets/example-list-index.liquid b/source/_includes/code_snippets/example-list-index.liquid
new file mode 100644
index 000000000..01bbc168d
--- /dev/null
+++ b/source/_includes/code_snippets/example-list-index.liquid
@@ -0,0 +1,9 @@
+{% codeExample 'example-list-index', false %}
+ @debug list.index(1px solid red, 1px); // 1
+ @debug list.index(1px solid red, solid); // 2
+ @debug list.index(1px solid red, dashed); // null
+ ===
+ @debug list.index(1px solid red, 1px) // 1
+ @debug list.index(1px solid red, solid) // 2
+ @debug list.index(1px solid red, dashed) // null
+{% endcodeExample %}
diff --git a/source/_includes/code_snippets/example-list-nth.liquid b/source/_includes/code_snippets/example-list-nth.liquid
new file mode 100644
index 000000000..5c404864b
--- /dev/null
+++ b/source/_includes/code_snippets/example-list-nth.liquid
@@ -0,0 +1,7 @@
+{% codeExample 'example-list-nth', false %}
+ @debug list.nth(10px 12px 16px, 2); // 12px
+ @debug list.nth([line1, line2, line3], -1); // line3
+ ===
+ @debug list.nth(10px 12px 16px, 2) // 12px
+ @debug list.nth([line1, line2, line3], -1) // line3
+{% endcodeExample %}
diff --git a/source/_includes/code_snippets/example-map-get.liquid b/source/_includes/code_snippets/example-map-get.liquid
new file mode 100644
index 000000000..6d3d17097
--- /dev/null
+++ b/source/_includes/code_snippets/example-map-get.liquid
@@ -0,0 +1,11 @@
+{% codeExample 'map-get', false %}
+ $font-weights: ("regular": 400, "medium": 500, "bold": 700);
+
+ @debug map.get($font-weights, "medium"); // 500
+ @debug map.get($font-weights, "extra-bold"); // null
+ ===
+ $font-weights: ("regular": 400, "medium": 500, "bold": 700)
+
+ @debug map.get($font-weights, "medium") // 500
+ @debug map.get($font-weights, "extra-bold") // null
+{% endcodeExample %}
diff --git a/source/code-snippets/example-mixin-arbitrary-keyword-arguments.liquid b/source/_includes/code_snippets/example-mixin-arbitrary-keyword-arguments.liquid
similarity index 95%
rename from source/code-snippets/example-mixin-arbitrary-keyword-arguments.liquid
rename to source/_includes/code_snippets/example-mixin-arbitrary-keyword-arguments.liquid
index f42a1ee65..987359b1f 100644
--- a/source/code-snippets/example-mixin-arbitrary-keyword-arguments.liquid
+++ b/source/_includes/code_snippets/example-mixin-arbitrary-keyword-arguments.liquid
@@ -1,4 +1,4 @@
-{% codeExample 'mixin-arbitrary-keyword' %}
+{% codeExample 'mixin-arbitrary-kwargs' %}
@use "sass:meta";
@mixin syntax-colors($args...) {
diff --git a/source/code-snippets/example-module-migrator.liquid b/source/_includes/code_snippets/example-module-migrator.liquid
similarity index 100%
rename from source/code-snippets/example-module-migrator.liquid
rename to source/_includes/code_snippets/example-module-migrator.liquid
diff --git a/source/code-snippets/example-nesting.liquid b/source/_includes/code_snippets/example-nesting.liquid
similarity index 100%
rename from source/code-snippets/example-nesting.liquid
rename to source/_includes/code_snippets/example-nesting.liquid
diff --git a/source/code-snippets/example-placeholder.liquid b/source/_includes/code_snippets/example-placeholder.liquid
similarity index 100%
rename from source/code-snippets/example-placeholder.liquid
rename to source/_includes/code_snippets/example-placeholder.liquid
diff --git a/source/code-snippets/example-use-with.liquid b/source/_includes/code_snippets/example-use-with.liquid
similarity index 100%
rename from source/code-snippets/example-use-with.liquid
rename to source/_includes/code_snippets/example-use-with.liquid
diff --git a/source/_includes/doc_snippets/built-in-module-status.liquid b/source/_includes/doc_snippets/built-in-module-status.liquid
new file mode 100644
index 000000000..e7bfb1c17
--- /dev/null
+++ b/source/_includes/doc_snippets/built-in-module-status.liquid
@@ -0,0 +1,4 @@
+{% compatibility 'dart: "1.23.0"', 'libsass: false', 'ruby: false' %}
+ Only Dart Sass currently supports loading built-in modules with `@use`. Users
+ of other implementations must call functions using their global names instead.
+{% endcompatibility %}
diff --git a/source/_includes/doc_snippets/call-impl-status.liquid b/source/_includes/doc_snippets/call-impl-status.liquid
new file mode 100644
index 000000000..c716ce472
--- /dev/null
+++ b/source/_includes/doc_snippets/call-impl-status.liquid
@@ -0,0 +1,11 @@
+{% compatibility 'dart: true', 'libsass: "3.5.0"', 'ruby: "3.5.0"', 'feature: "Argument Type"' %}
+ In older versions of LibSass and Ruby Sass, the [`call()` function][] took a
+ string representing a function's name. This was changed to take a function
+ value instead in preparation for a new module system where functions are no
+ longer global and so a given name may not always refer to the same function.
+
+ [`call()` function]: /documentation/modules/meta#call
+
+ Passing a string to `call()` still works in all implementations, but it's
+ deprecated and will be disallowed in future versions.
+{% endcompatibility %}
diff --git a/source/_includes/doc_snippets/module-system-function-status.liquid b/source/_includes/doc_snippets/module-system-function-status.liquid
new file mode 100644
index 000000000..c7ef22abc
--- /dev/null
+++ b/source/_includes/doc_snippets/module-system-function-status.liquid
@@ -0,0 +1,3 @@
+{% compatibility 'dart: "1.23.0"', 'libsass: false', 'ruby: false' %}
+ Only Dart Sass currently supports this function.
+{% endcompatibility %}
diff --git a/source/documentation/snippets/module-system-status.liquid b/source/_includes/doc_snippets/module-system-status.liquid
similarity index 100%
rename from source/documentation/snippets/module-system-status.liquid
rename to source/_includes/doc_snippets/module-system-status.liquid
diff --git a/source/documentation/snippets/operator-list.liquid b/source/_includes/doc_snippets/operator-list.liquid
similarity index 100%
rename from source/documentation/snippets/operator-list.liquid
rename to source/_includes/doc_snippets/operator-list.liquid
diff --git a/source/documentation/snippets/silence-deprecations.liquid b/source/_includes/doc_snippets/silence-deprecations.liquid
similarity index 100%
rename from source/documentation/snippets/silence-deprecations.liquid
rename to source/_includes/doc_snippets/silence-deprecations.liquid
diff --git a/source/documentation/snippets/source-maps.liquid b/source/_includes/doc_snippets/source-maps.liquid
similarity index 100%
rename from source/documentation/snippets/source-maps.liquid
rename to source/_includes/doc_snippets/source-maps.liquid
diff --git a/source/documentation/snippets/truthiness-and-falsiness.liquid b/source/_includes/doc_snippets/truthiness-and-falsiness.liquid
similarity index 100%
rename from source/documentation/snippets/truthiness-and-falsiness.liquid
rename to source/_includes/doc_snippets/truthiness-and-falsiness.liquid
diff --git a/source/_includes/function.liquid b/source/_includes/function.liquid
new file mode 100644
index 000000000..264025584
--- /dev/null
+++ b/source/_includes/function.liquid
@@ -0,0 +1,5 @@
+{% for name in names offset: 1 %}
{% endfor -%}
+
{{ signatures }}{% if returns %} {% endif %}
+ {{ content }}
+
+{%- for name in names offset: 1 %}
{% endfor %}
diff --git a/source/documentation/at-rules/at-root.liquid b/source/documentation/at-rules/at-root.liquid
index 05f2827f9..7dadf17e6 100644
--- a/source/documentation/at-rules/at-root.liquid
+++ b/source/documentation/at-rules/at-root.liquid
@@ -10,7 +10,7 @@ introduction: >
[selector functions](/documentation/modules/selector).
---
-{% render 'code-snippets/example-advanced-nesting' %}
+{% render 'code_snippets/example-advanced-nesting' %}
{% markdown %}
The `@at-root` rule is necessary here because Sass doesn't know what
diff --git a/source/documentation/at-rules/control/each.liquid b/source/documentation/at-rules/control/each.liquid
index 0957b176e..d31f65a8a 100644
--- a/source/documentation/at-rules/control/each.liquid
+++ b/source/documentation/at-rules/control/each.liquid
@@ -11,7 +11,7 @@ introduction: >
the given variable name.
---
-{% render 'code-snippets/example-each-list' %}
+{% render 'code_snippets/example-each-list' %}
{% markdown %}
## With Maps
@@ -22,7 +22,7 @@ introduction: >
second.
{% endmarkdown %}
-{% render 'code-snippets/example-each-map' %}
+{% render 'code_snippets/example-each-map' %}
{% markdown %}
## Destructuring
diff --git a/source/documentation/at-rules/control/if.liquid b/source/documentation/at-rules/control/if.liquid
index e921eb336..9ca2bf9d8 100644
--- a/source/documentation/at-rules/control/if.liquid
+++ b/source/documentation/at-rules/control/if.liquid
@@ -10,7 +10,7 @@ introduction: >
it’s not.
---
-{% render 'code-snippets/example-if' %}
+{% render 'code_snippets/example-if' %}
{% markdown %}
## `@else`
@@ -146,4 +146,4 @@ introduction: >
}
{% endcodeExample %}
-{% render 'documentation/snippets/truthiness-and-falsiness' %}
+{% render 'doc_snippets/truthiness-and-falsiness' %}
diff --git a/source/documentation/at-rules/control/while.liquid b/source/documentation/at-rules/control/while.liquid
index 8fe572482..0778329f6 100644
--- a/source/documentation/at-rules/control/while.liquid
+++ b/source/documentation/at-rules/control/while.liquid
@@ -52,4 +52,4 @@ introduction: >
[`@for`]: /documentation/at-rules/control/for
{% endheadsUp %}
-{% render 'documentation/snippets/truthiness-and-falsiness' %}
+{% render 'doc_snippets/truthiness-and-falsiness' %}
diff --git a/source/documentation/at-rules/extend.liquid b/source/documentation/at-rules/extend.liquid
index 2910f2147..20a9af74d 100644
--- a/source/documentation/at-rules/extend.liquid
+++ b/source/documentation/at-rules/extend.liquid
@@ -204,7 +204,7 @@ introduction: >
[placeholder selectors]: /documentation/style-rules/placeholder-selectors
{% endmarkdown %}
-{% render 'code-snippets/example-placeholder' %}
+{% render 'code_snippets/example-placeholder' %}
{% markdown %}
### Private Placeholders
diff --git a/source/documentation/at-rules/import.liquid b/source/documentation/at-rules/import.liquid
index f60495929..543d17188 100644
--- a/source/documentation/at-rules/import.liquid
+++ b/source/documentation/at-rules/import.liquid
@@ -484,7 +484,7 @@ introduction: >
{% markdown %}
## Import and Modules
- {% render 'documentation/snippets/module-system-status' %}
+ {% render 'doc_snippets/module-system-status' %}
Sass's [module system][] integrates seamlessly with `@import`, whether you're
importing a file that contains `@use` rules or loading a file that contains
diff --git a/source/documentation/at-rules/mixin.liquid b/source/documentation/at-rules/mixin.liquid
index 9a2502b49..b7ef1905d 100644
--- a/source/documentation/at-rules/mixin.liquid
+++ b/source/documentation/at-rules/mixin.liquid
@@ -277,11 +277,12 @@ introduction: >
[`meta.keywords()` function]: /documentation/modules/meta#keywords
[map]: /documentation/values/maps
-
-
{% endmarkdown %}
-{% render 'code-snippets/example-mixin-arbitrary-keyword-arguments' %}
+{% comment -%}
+ TODO(nweiz): auto-generate this CSS once we're compiling with Dart Sass
+{%- endcomment -%}
+{% render 'code_snippets/example-mixin-arbitrary-keyword-arguments' %}
{% funFact %}
If you don't ever pass an argument list to the [`meta.keywords()` function][],
@@ -408,7 +409,9 @@ introduction: >
[map]: /documentation/values/maps
{% endheadsUp %}
-
+{% comment -%}
+ TODO(nweiz): auto-generate this CSS once we're compiling with Dart Sass
+{%- endcomment -%}
{% codeExample 'passing-arguments-to-content-blocks', false %}
@mixin media($types...) {
@each $type in $types {
diff --git a/source/documentation/at-rules/use.liquid b/source/documentation/at-rules/use.liquid
index d41f71f82..ac1a115b2 100644
--- a/source/documentation/at-rules/use.liquid
+++ b/source/documentation/at-rules/use.liquid
@@ -3,7 +3,7 @@ title: "@use"
table_of_contents: true
eleventyComputed:
before_introduction: >
- {% render 'documentation/snippets/module-system-status' %}
+ {% render 'doc_snippets/module-system-status' %}
introduction: >
The `@use` rule loads [mixins](/documentation/at-rules/mixin),
[functions](/documentation/at-rules/function), and
@@ -298,7 +298,7 @@ introduction: >
[`!default` flag]: /documentation/variables#default-values
{% endmarkdown %}
-{% render 'code-snippets/example-use-with' %}
+{% render 'code_snippets/example-use-with' %}
{% markdown %}
### With Mixins
diff --git a/source/documentation/breaking-changes/bogus-combinators.liquid b/source/documentation/breaking-changes/bogus-combinators.liquid
index a037f137f..37cd2ce79 100644
--- a/source/documentation/breaking-changes/bogus-combinators.liquid
+++ b/source/documentation/breaking-changes/bogus-combinators.liquid
@@ -57,7 +57,7 @@ introduction: >
leading or trailing combinators that end up in selectors after nesting is
resolved.
- {% render 'documentation/snippets/silence-deprecations' %}
+ {% render 'doc_snippets/silence-deprecations' %}
In addition, we'll immediately start omitting selectors that we know to be
invalid CSS from the compiled CSS, with one exception: we _won't_ omit
diff --git a/source/documentation/breaking-changes/slash-div.liquid b/source/documentation/breaking-changes/slash-div.liquid
index 611d42882..c6d359c06 100644
--- a/source/documentation/breaking-changes/slash-div.liquid
+++ b/source/documentation/breaking-changes/slash-div.liquid
@@ -61,7 +61,7 @@ introduction: >
instead.
{% endmarkdown %}
-{% render 'documentation/snippets/silence-deprecations' %}
+{% render 'doc_snippets/silence-deprecations' %}
{% codeExample 'math-div', false %}
@use "sass:math";
diff --git a/source/documentation/breaking-changes/strict-unary.liquid b/source/documentation/breaking-changes/strict-unary.liquid
index 59b02c8d8..8776734e5 100644
--- a/source/documentation/breaking-changes/strict-unary.liquid
+++ b/source/documentation/breaking-changes/strict-unary.liquid
@@ -51,7 +51,7 @@ introduction: >
We'll make this an error in Dart Sass 2.0.0, but until then it'll just emit a
deprecation warning.
- {% render 'documentation/snippets/silence-deprecations' %}
+ {% render 'doc_snippets/silence-deprecations' %}
## Automatic Migration
diff --git a/source/documentation/cli/dart-sass.md b/source/documentation/cli/dart-sass.md
index 69385753a..7761fa443 100644
--- a/source/documentation/cli/dart-sass.md
+++ b/source/documentation/cli/dart-sass.md
@@ -223,7 +223,7 @@ Compiled themes/light.scss to public/css/light.css.
{% compatibility 'dart: "1.3.0"' %}{% endcompatibility %}
-{% render 'documentation/snippets/source-maps' %}
+{% render 'doc_snippets/source-maps' %}
Dart Sass generates source maps by default for every CSS file it emits.
diff --git a/source/documentation/cli/migrator.md b/source/documentation/cli/migrator.md
index 9d77497f9..58b98040d 100644
--- a/source/documentation/cli/migrator.md
+++ b/source/documentation/cli/migrator.md
@@ -33,7 +33,7 @@ see what changes will be made without actually saving them, you can pass
[--dry-run]: #dry-run
[--verbose]: #verbose
-{% render 'code-snippets/example-module-migrator' %}
+{% render 'code_snippets/example-module-migrator' %}
## Installation
@@ -259,7 +259,7 @@ before, including:
[`--migrate-deps` option]: #migrate-deps
{% endheadsUp %}
-{% render 'code-snippets/example-module-migrator' %}
+{% render 'code_snippets/example-module-migrator' %}
#### Loading Dependencies
diff --git a/source/documentation/modules/color.liquid b/source/documentation/modules/color.liquid
new file mode 100644
index 000000000..488e8ce85
--- /dev/null
+++ b/source/documentation/modules/color.liquid
@@ -0,0 +1,925 @@
+---
+title: sass:color
+---
+
+{% render 'doc_snippets/built-in-module-status' %}
+
+{% capture color_adjust %}
+ color.adjust($color,
+ $red: null, $green: null, $blue: null,
+ $hue: null, $saturation: null, $lightness: null,
+ $whiteness: null, $blackness: null,
+ $alpha: null)
+{% endcapture %}
+
+{% function color_adjust, 'adjust-color(...)', 'returns:color' %}
+ {% compatibility 'dart: "1.28.0"', 'libsass: false', 'ruby: false', 'feature: "$whiteness and $blackness"' %}{% endcompatibility %}
+
+ {% markdown %}
+ Increases or decreases one or more properties of `$color` by fixed amounts.
+
+ Adds the value passed for each keyword argument to the corresponding
+ property of the color, and returns the adjusted color. It's an error to
+ specify an RGB property (`$red`, `$green`, and/or `$blue`) at the same time
+ as an HSL property (`$hue`, `$saturation`, and/or `$lightness`), or either
+ of those at the same time as an [HWB][] property (`$hue`, `$whiteness`,
+ and/or `$blackness`).
+
+ [HWB]: https://en.wikipedia.org/wiki/HWB_color_model
+
+ All optional arguments must be numbers. The `$red`, `$green`, and `$blue`
+ arguments must be [unitless][] and between -255 and 255 (inclusive). The
+ `$hue` argument must have either the unit `deg` or no unit. The
+ `$saturation`, `$lightness`, `$whiteness`, and `$blackness` arguments must
+ be between `-100%` and `100%` (inclusive), and may not be unitless. The
+ `$alpha` argument must be unitless and between -1 and 1 (inclusive).
+
+ [unitless]: /documentation/values/numbers#units
+
+ See also:
+
+ * [`color.scale()`](#scale) for fluidly scaling a color's properties.
+ * [`color.change()`](#change) for setting a color's properties.
+ {% endmarkdown %}
+
+ {% codeExample 'adjust-color', false %}
+ @debug color.adjust(#6b717f, $red: 15); // #7a717f
+ @debug color.adjust(#d2e1dd, $red: -10, $blue: 10); // #c8e1e7
+ @debug color.adjust(#998099, $lightness: -30%, $alpha: -0.4); // rgba(71, 57, 71, 0.6)
+ ===
+ @debug color.adjust(#6b717f, $red: 15) // #7a717f
+ @debug color.adjust(#d2e1dd, $red: -10, $blue: 10) // #c8e1e7
+ @debug color.adjust(#998099, $lightness: -30%, $alpha: -0.4) // rgba(71, 57, 71, 0.6)
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'adjust-hue($color, $degrees)', 'returns:color' %}
+ {% markdown %}
+ Increases or decreases `$color`'s hue.
+
+ The `$hue` must be a number between `-360deg` and `360deg` (inclusive) to
+ add to `$color`'s hue. It may be [unitless][] but it may not have any unit
+ other than `deg`.
+
+ [unitless]: /documentation/values/numbers#units
+
+ See also [`color.adjust()`](#adjust), which can adjust any property of a
+ color.
+ {% endmarkdown %}
+
+ {% headsUp %}
+ Because `adjust-hue()` is redundant with [`adjust()`](#adjust), it's not
+ included directly in the new module system. Instead of `adjust-hue($color,
+ $amount)`, you can write [`color.adjust($color, $hue: $amount)`](#adjust).
+ {% endheadsUp %}
+
+ {% codeExample 'adjust-hue', false %}
+ // Hue 222deg becomes 282deg.
+ @debug adjust-hue(#6b717f, 60deg); // #796b7f
+
+ // Hue 164deg becomes 104deg.
+ @debug adjust-hue(#d2e1dd, -60deg); // #d6e1d2
+
+ // Hue 210deg becomes 255deg.
+ @debug adjust-hue(#036, 45); // #1a0066
+ ===
+ // Hue 222deg becomes 282deg.
+ @debug adjust-hue(#6b717f, 60deg) // #796b7f
+
+ // Hue 164deg becomes 104deg.
+ @debug adjust-hue(#d2e1dd, -60deg) // #d6e1d2
+
+ // Hue 210deg becomes 255deg.
+ @debug adjust-hue(#036, 45) // #1a0066
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'color.alpha($color)', 'alpha($color)', 'opacity($color)', 'returns:number' %}
+ {% markdown %}
+ Returns the alpha channel of `$color` as a number between 0 and 1.
+
+ As a special case, this supports the Internet Explorer syntax
+ `alpha(opacity=20)`, for which it returns an [unquoted string][].
+
+ [unquoted string]: /documentation/values/strings#unquoted
+
+ See also:
+
+ * [`color.red()`](#red) for getting a color's red channel.
+ * [`color.green()`](#green) for getting a color's green channel.
+ * [`color.blue()`](#blue) for getting a color's blue channel.
+ * [`color.hue()`](#hue) for getting a color's hue.
+ * [`color.saturation()`](#saturation) for getting a color's saturation.
+ * [`color.lightness()`](#lightness) for getting a color's lightness.
+ {% endmarkdown %}
+
+ {% codeExample 'color-alpha', false %}
+ @debug color.alpha(#e1d7d2); // 1
+ @debug color.opacity(rgb(210, 225, 221, 0.4)); // 0.4
+ @debug alpha(opacity=20); // alpha(opacity=20)
+ ===
+ @debug color.alpha(#e1d7d2) // 1
+ @debug color.opacity(rgb(210, 225, 221, 0.4)) // 0.4
+ @debug alpha(opacity=20) // alpha(opacity=20)
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'color.blackness($color)', 'returns:number' %}
+ {% compatibility 'dart: "1.28.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %}
+
+ {% markdown %}
+ Returns the [HWB][] blackness of `$color` as a number between `0%` and
+ `100%`.
+
+ [HWB]: https://en.wikipedia.org/wiki/HWB_color_model
+
+ See also:
+
+ * [`color.red()`](#red) for getting a color's red channel.
+ * [`color.green()`](#green) for getting a color's green channel.
+ * [`color.hue()`](#hue) for getting a color's hue.
+ * [`color.saturation()`](#saturation) for getting a color's saturation.
+ * [`color.lightness()`](#lightness) for getting a color's lightness.
+ * [`color.whiteness()`](#whiteness) for getting a color's whiteness.
+ * [`color.alpha()`](#alpha) for getting a color's alpha channel.
+ {% endmarkdown %}
+
+ {% codeExample 'color-blackness', false %}
+ @debug color.blackness(#e1d7d2); // 11.7647058824%
+ @debug color.blackness(white); // 0%
+ @debug color.blackness(black); // 100%
+ ===
+ @debug color.blackness(#e1d7d2) // 11.7647058824%
+ @debug color.blackness(white) // 0%
+ @debug color.blackness(black) // 100%
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'color.blue($color)', 'blue($color)', 'returns:number' %}
+ {% markdown %}
+ Returns the blue channel of `$color` as a number between 0 and 255.
+
+ See also:
+
+ * [`color.red()`](#red) for getting a color's red channel.
+ * [`color.green()`](#green) for getting a color's green channel.
+ * [`color.hue()`](#hue) for getting a color's hue.
+ * [`color.saturation()`](#saturation) for getting a color's saturation.
+ * [`color.lightness()`](#lightness) for getting a color's lightness.
+ * [`color.whiteness()`](#whiteness) for getting a color's whiteness.
+ * [`color.blackness()`](#blackness) for getting a color's blackness.
+ * [`color.alpha()`](#alpha) for getting a color's alpha channel.
+ {% endmarkdown %}
+
+ {% codeExample 'color-blue', false %}
+ @debug color.blue(#e1d7d2); // 210
+ @debug color.blue(white); // 255
+ @debug color.blue(black); // 0
+ ===
+ @debug color.blue(#e1d7d2) // 210
+ @debug color.blue(white) // 255
+ @debug color.blue(black) // 0
+ {% endcodeExample %}
+{% endfunction %}
+
+{% capture color_change %}
+ color.change($color,
+ $red: null, $green: null, $blue: null,
+ $hue: null, $saturation: null, $lightness: null,
+ $whiteness: null, $blackness: null,
+ $alpha: null)
+{% endcapture %}
+
+{% function color_change, 'change-color(...)', 'returns:color' %}
+ {% compatibility 'dart: "1.28.0"', 'libsass: false', 'ruby: false', 'feature: "$whiteness and $blackness"' %}{% endcompatibility %}
+
+ {% markdown %}
+ Sets one or more properties of a color to new values.
+
+ Uses the value passed for each keyword argument in place of the
+ corresponding property of the color, and returns the changed color. It's an
+ error to specify an RGB property (`$red`, `$green`, and/or `$blue`) at the
+ same time as an HSL property (`$hue`, `$saturation`, and/or `$lightness`),
+ or either of those at the same time as an [HWB][] property (`$hue`,
+ `$whiteness`, and/or `$blackness`).
+
+ [HWB]: https://en.wikipedia.org/wiki/HWB_color_model
+
+ All optional arguments must be numbers. The `$red`, `$green`, and `$blue`
+ arguments must be [unitless][] and between 0 and 255 (inclusive). The `$hue`
+ argument must have either the unit `deg` or no unit. The `$saturation`,
+ `$lightness`, `$whiteness`, and `$blackness` arguments must be between `0%`
+ and `100%` (inclusive), and may not be unitless. The `$alpha` argument must
+ be unitless and between 0 and 1 (inclusive).
+
+ [unitless]: /documentation/values/numbers#units
+
+ See also:
+
+ * [`color.scale()`](#scale) for fluidly scaling a color's properties.
+ * [`color.adjust()`](#adjust) for adjusting a color's properties by fixed
+ amounts.
+ {% endmarkdown %}
+
+ {% codeExample 'color-change', false %}
+ @debug color.change(#6b717f, $red: 100); // #64717f
+ @debug color.change(#d2e1dd, $red: 100, $blue: 50); // #64e132
+ @debug color.change(#998099, $lightness: 30%, $alpha: 0.5); // rgba(85, 68, 85, 0.5)
+ ===
+ @debug color.change(#6b717f, $red: 100) // #64717f
+ @debug color.change(#d2e1dd, $red: 100, $blue: 50) // #64e132
+ @debug color.change(#998099, $lightness: 30%, $alpha: 0.5) // rgba(85, 68, 85, 0.5)
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'color.complement($color)', 'complement($color)', 'returns:color' %}
+ {% markdown %}
+ Returns the RGB [complement][] of `$color`.
+
+ This is identical to [`color.adjust($color, $hue: 180deg)`](#adjust).
+
+ [complement]: https://en.wikipedia.org/wiki/Complementary_colors
+ {% endmarkdown %}
+
+ {% codeExample 'color-complement', false %}
+ // Hue 222deg becomes 42deg.
+ @debug color.complement(#6b717f); // #7f796b
+
+ // Hue 164deg becomes 344deg.
+ @debug color.complement(#d2e1dd); // #e1d2d6
+
+ // Hue 210deg becomes 30deg.
+ @debug color.complement(#036); // #663300
+ ===
+ // Hue 222deg becomes 42deg.
+ @debug color.complement(#6b717f) // #7f796b
+
+ // Hue 164deg becomes 344deg.
+ @debug color.complement(#d2e1dd) // #e1d2d6
+
+ // Hue 210deg becomes 30deg.
+ @debug color.complement(#036) // #663300
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'darken($color, $amount)', 'returns:color' %}
+ {% markdown %}
+ Makes `$color` darker.
+
+ The `$amount` must be a number between `0%` and `100%` (inclusive).
+ Decreases the HSL lightness of `$color` by that amount.
+ {% endmarkdown %}
+
+ {% headsUp false %}
+ {% markdown %}
+ The `darken()` function decreases lightness by a fixed amount, which is
+ often not the desired effect. To make a color a certain percentage darker
+ than it was before, use [`color.scale()`](#scale) instead.
+
+ Because `darken()` is usually not the best way to make a color darker,
+ it's not included directly in the new module system. However, if you have
+ to preserve the existing behavior, `darken($color, $amount)` can be
+ written [`color.adjust($color, $lightness: -$amount)`](#adjust).
+ {% endmarkdown %}
+
+ {% codeExample 'color-darken', false %}
+ // #036 has lightness 20%, so when darken() subtracts 30% it just returns black.
+ @debug darken(#036, 30%); // black
+
+ // scale() instead makes it 30% darker than it was originally.
+ @debug color.scale(#036, $lightness: -30%); // #002447
+ ===
+ // #036 has lightness 20%, so when darken() subtracts 30% it just returns black.
+ @debug darken(#036, 30%) // black
+
+ // scale() instead makes it 30% darker than it was originally.
+ @debug color.scale(#036, $lightness: -30%) // #002447
+ {% endcodeExample %}
+ {% endheadsUp %}
+
+ {% codeExample 'color-darken-2', false %}
+ // Lightness 92% becomes 72%.
+ @debug darken(#b37399, 20%); // #7c4465
+
+ // Lightness 85% becomes 45%.
+ @debug darken(#f2ece4, 40%); // #b08b5a
+
+ // Lightness 20% becomes 0%.
+ @debug darken(#036, 30%); // black
+ ===
+ // Lightness 92% becomes 72%.
+ @debug darken(#b37399, 20%) // #7c4465
+
+ // Lightness 85% becomes 45%.
+ @debug darken(#f2ece4, 40%) // #b08b5a
+
+ // Lightness 20% becomes 0%.
+ @debug darken(#036, 30%) // black
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'desaturate($color, $amount)', 'returns:color' %}
+ {% markdown %}
+ Makes `$color` less saturated.
+
+ The `$amount` must be a number between `0%` and `100%` (inclusive).
+ Decreases the HSL saturation of `$color` by that amount.
+ {% endmarkdown %}
+
+ {% headsUp false %}
+ {% markdown %}
+ The `desaturate()` function decreases saturation by a fixed amount, which
+ is often not the desired effect. To make a color a certain percentage less
+ saturated than it was before, use [`color.scale()`](#scale) instead.
+
+ Because `desaturate()` is usually not the best way to make a color less
+ saturated, it's not included directly in the new module system. However,
+ if you have to preserve the existing behavior, `desaturate($color,
+ $amount)` can be written [`color.adjust($color, $saturation:
+ -$amount)`](#adjust).
+ {% endmarkdown %}
+
+ {% codeExample 'color-desaturate', false %}
+ // #d2e1dd has saturation 20%, so when desaturate() subtracts 30% it just
+ // returns gray.
+ @debug desaturate(#d2e1dd, 30%); // #dadada
+
+ // scale() instead makes it 30% less saturated than it was originally.
+ @debug color.scale(#6b717f, $saturation: -30%); // #6e727c
+ ===
+ // #6b717f has saturation 20%, so when desaturate() subtracts 30% it just
+ // returns gray.
+ @debug desaturate(#d2e1dd, 30%) // #dadada
+
+ // scale() instead makes it 30% less saturated than it was originally.
+ @debug color.scale(#6b717f, $saturation: -30%) // #6e727c
+ {% endcodeExample %}
+ {% endheadsUp %}
+
+ {% codeExample 'color-desaturate-2', false %}
+ // Saturation 100% becomes 80%.
+ @debug desaturate(#036, 20%); // #0a335c
+
+ // Saturation 35% becomes 15%.
+ @debug desaturate(#f2ece4, 20%); // #eeebe8
+
+ // Saturation 20% becomes 0%.
+ @debug desaturate(#d2e1dd, 30%); // #dadada
+ ===
+ // Saturation 100% becomes 80%.
+ @debug desaturate(#036, 20%) // #0a335c
+
+ // Saturation 35% becomes 15%.
+ @debug desaturate(#f2ece4, 20%) // #eeebe8
+
+ // Saturation 20% becomes 0%.
+ @debug desaturate(#d2e1dd, 30%) // #dadada
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'color.grayscale($color)', 'grayscale($color)', 'returns:color' %}
+ {% markdown %}
+ Returns a gray color with the same lightness as `$color`.
+
+ This is identical to [`color.change($color, $saturation: 0%)`](#change).
+ {% endmarkdown %}
+
+ {% codeExample 'color-grayscale', false %}
+ @debug color.grayscale(#6b717f); // #757575
+ @debug color.grayscale(#d2e1dd); // #dadada
+ @debug color.grayscale(#036); // #333333
+ ===
+ @debug color.grayscale(#6b717f) // #757575
+ @debug color.grayscale(#d2e1dd) // #dadada
+ @debug color.grayscale(#036) // #333333
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'color.green($color)', 'green($color)', 'returns:number' %}
+ {% markdown %}
+ Returns the green channel of `$color` as a number between 0 and 255.
+
+ See also:
+
+ * [`color.red()`](#red) for getting a color's red channel.
+ * [`color.blue()`](#blue) for getting a color's blue channel.
+ * [`color.hue()`](#hue) for getting a color's hue.
+ * [`color.saturation()`](#saturation) for getting a color's saturation.
+ * [`color.lightness()`](#lightness) for getting a color's lightness.
+ * [`color.whiteness()`](#whiteness) for getting a color's whiteness.
+ * [`color.blackness()`](#blackness) for getting a color's blackness.
+ * [`color.alpha()`](#alpha) for getting a color's alpha channel.
+ {% endmarkdown %}
+
+ {% codeExample 'color-green', false %}
+ @debug color.green(#e1d7d2); // 215
+ @debug color.green(white); // 255
+ @debug color.green(black); // 0
+ ===
+ @debug color.green(#e1d7d2) // 215
+ @debug color.green(white) // 255
+ @debug color.green(black) // 0
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'color.hue($color)', 'hue($color)', 'returns:number' %}
+ {% markdown %}
+ Returns the hue of `$color` as a number between `0deg` and `360deg`.
+
+ See also:
+
+ * [`color.red()`](#red) for getting a color's red channel.
+ * [`color.green()`](#green) for getting a color's green channel.
+ * [`color.blue()`](#blue) for getting a color's blue channel.
+ * [`color.saturation()`](#saturation) for getting a color's saturation.
+ * [`color.lightness()`](#lightness) for getting a color's lightness.
+ * [`color.whiteness()`](#whiteness) for getting a color's whiteness.
+ * [`color.blackness()`](#blackness) for getting a color's blackness.
+ * [`color.alpha()`](#alpha) for getting a color's alpha channel.
+ {% endmarkdown %}
+
+ {% codeExample 'color-hue', false %}
+ @debug color.hue(#e1d7d2); // 20deg
+ @debug color.hue(#f2ece4); // 34.2857142857deg
+ @debug color.hue(#dadbdf); // 228deg
+ ===
+ @debug color.hue(#e1d7d2) // 20deg
+ @debug color.hue(#f2ece4) // 34.2857142857deg
+ @debug color.hue(#dadbdf) // 228deg
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'color.hwb($hue $whiteness $blackness)', 'color.hwb($hue $whiteness $blackness / $alpha)', 'color.hwb($hue, $whiteness, $blackness, $alpha: 1)', 'returns:color' %}
+ {% compatibility 'dart: "1.28.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %}
+
+ {% markdown %}
+ Returns a color with the given [hue, whiteness, and blackness][] and the
+ given alpha channel.
+
+ [hue, whiteness, and blackness]: https://en.wikipedia.org/wiki/HWB_color_model
+
+ The hue is a number between `0deg` and `360deg` (inclusive). The whiteness
+ and blackness are numbers between `0%` and `100%` (inclusive). The hue may
+ be [unitless][], but the whiteness and blackness must have unit `%`. The
+ alpha channel can be specified as either a unitless number between 0 and 1
+ (inclusive), or a percentage between `0%` and `100%` (inclusive).
+
+ [unitless]: /documentation/values/numbers#units
+ {% endmarkdown %}
+
+ {% headsUp %}
+ Sass's [special parsing rules][] for slash-separated values make it
+ difficult to pass variables for `$blackness` or `$alpha` when using the
+ `color.hwb($hue $whiteness $blackness / $alpha)` signature. Consider using
+ `color.hwb($hue, $whiteness, $blackness, $alpha)` instead.
+
+ [special parsing rules]: /documentation/operators/numeric#slash-separated-values
+ {% endheadsUp %}
+
+ {% codeExample 'color-hwb', false %}
+ @debug color.hwb(210, 0%, 60%); // #036
+ @debug color.hwb(34, 89%, 5%); // #f2ece4
+ @debug color.hwb(210 0% 60% / 0.5); // rgba(0, 51, 102, 0.5)
+ ===
+ @debug color.hwb(210, 0%, 60%) // #036
+ @debug color.hwb(34, 89%, 5%) // #f2ece4
+ @debug color.hwb(210 0% 60% / 0.5) // rgba(0, 51, 102, 0.5)
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'color.ie-hex-str($color)', 'ie-hex-str($color)', 'returns:unquoted string' %}
+ {% markdown %}
+ Returns an unquoted string that represents `$color` in the `#AARRGGBB`
+ format expected by Internet Explorer's [`-ms-filter`][] property.
+
+ [`-ms-filter`]: https://developer.mozilla.org/en-US/docs/Web/CSS/-ms-filter
+ {% endmarkdown %}
+
+ {% codeExample 'color-ie-hex-str', false %}
+ @debug color.ie-hex-str(#b37399); // #FFB37399
+ @debug color.ie-hex-str(#808c99); // #FF808C99
+ @debug color.ie-hex-str(rgba(242, 236, 228, 0.6)); // #99F2ECE4
+ ===
+ @debug color.ie-hex-str(#b37399); // #FFB37399
+ @debug color.ie-hex-str(#808c99); // #FF808C99
+ @debug color.ie-hex-str(rgba(242, 236, 228, 0.6)); // #99F2ECE4
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'color.invert($color, $weight: 100%)', 'invert($color, $weight: 100%)', 'returns:color' %}
+ {% markdown %}
+ Returns the inverse or [negative][] of `$color`.
+
+ [negative]: https://en.wikipedia.org/wiki/Negative_(photography)
+
+ The `$weight` must be a number between `0%` and `100%` (inclusive). A higher
+ weight means the result will be closer to the negative, and a lower weight
+ means it will be closer to `$color`. Weight `50%` will always produce
+ `#808080`.
+ {% endmarkdown %}
+
+ {% codeExample 'color-invert', false %}
+ @debug color.invert(#b37399); // #4c8c66
+ @debug color.invert(black); // white
+ @debug color.invert(#550e0c, 20%); // #663b3a
+ ===
+ @debug color.invert(#b37399) // #4c8c66
+ @debug color.invert(black) // white
+ @debug color.invert(#550e0c, 20%) // #663b3a
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'lighten($color, $amount)', 'returns:color' %}
+ {% markdown %}
+ Makes `$color` lighter.
+
+ The `$amount` must be a number between `0%` and `100%` (inclusive).
+ Increases the HSL lightness of `$color` by that amount.
+ {% endmarkdown %}
+
+ {% headsUp false %}
+ {% markdown %}
+ The `lighten()` function increases lightness by a fixed amount, which is
+ often not the desired effect. To make a color a certain percentage lighter
+ than it was before, use [`scale()`](#scale) instead.
+
+ Because `lighten()` is usually not the best way to make a color lighter,
+ it's not included directly in the new module system. However, if you have
+ to preserve the existing behavior, `lighten($color, $amount)` can be
+ written [`adjust($color, $lightness: $amount)`](#adjust).
+ {% endmarkdown %}
+
+ {% codeExample 'color-lighten', false %}
+ // #e1d7d2 has lightness 85%, so when lighten() adds 30% it just returns white.
+ @debug lighten(#e1d7d2, 30%); // white
+
+ // scale() instead makes it 30% lighter than it was originally.
+ @debug color.scale(#e1d7d2, $lightness: 30%); // #eae3e0
+ ===
+ // #e1d7d2 has lightness 85%, so when lighten() adds 30% it just returns white.
+ @debug lighten(#e1d7d2, 30%) // white
+
+ // scale() instead makes it 30% lighter than it was originally.
+ @debug color.scale(#e1d7d2, $lightness: 30%) // #eae3e0
+ {% endcodeExample %}
+ {% endheadsUp %}
+
+ {% codeExample 'color-lighten-2', false %}
+ // Lightness 46% becomes 66%.
+ @debug lighten(#6b717f, 20%); // #a1a5af
+
+ // Lightness 20% becomes 80%.
+ @debug lighten(#036, 60%); // #99ccff
+
+ // Lightness 85% becomes 100%.
+ @debug lighten(#e1d7d2, 30%); // white
+ ===
+ // Lightness 46% becomes 66%.
+ @debug lighten(#6b717f, 20%) // #a1a5af
+
+ // Lightness 20% becomes 80%.
+ @debug lighten(#036, 60%) // #99ccff
+
+ // Lightness 85% becomes 100%.
+ @debug lighten(#e1d7d2, 30%) // white
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'color.lightness($color)', 'lightness($color)', 'returns:number' %}
+ {% markdown %}
+ Returns the HSL lightness of `$color` as a number between `0%` and `100%`.
+
+ See also:
+
+ * [`color.red()`](#red) for getting a color's red channel.
+ * [`color.green()`](#green) for getting a color's green channel.
+ * [`color.blue()`](#blue) for getting a color's blue channel.
+ * [`color.hue()`](#hue) for getting a color's hue.
+ * [`color.saturation()`](#saturation) for getting a color's saturation.
+ * [`color.whiteness()`](#whiteness) for getting a color's whiteness.
+ * [`color.blackness()`](#blackness) for getting a color's blackness.
+ * [`color.alpha()`](#alpha) for getting a color's alpha channel.
+ {% endmarkdown %}
+
+ {% codeExample 'color-lightness', false %}
+ @debug color.lightness(#e1d7d2); // 85.2941176471%
+ @debug color.lightness(#f2ece4); // 92.1568627451%
+ @debug color.lightness(#dadbdf); // 86.4705882353%
+ ===
+ @debug color.lightness(#e1d7d2) // 85.2941176471%
+ @debug color.lightness(#f2ece4) // 92.1568627451%
+ @debug color.lightness(#dadbdf) // 86.4705882353%
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'color.mix($color1, $color2, $weight: 50%)', 'mix($color1, $color2, $weight: 50%)', 'returns:color' %}
+ {% markdown %}
+ Returns a color that's a mixture of `$color1` and `$color2`.
+
+ Both the `$weight` and the relative opacity of each color determines how
+ much of each color is in the result. The `$weight` must be a number between
+ `0%` and `100%` (inclusive). A larger weight indicates that more of
+ `$color1` should be used, and a smaller weight indicates that more of
+ `$color2` should be used.
+ {% endmarkdown %}
+
+ {% codeExample 'color-mix', false %}
+ @debug color.mix(#036, #d2e1dd); // #698aa2
+ @debug color.mix(#036, #d2e1dd, 75%); // #355f84
+ @debug color.mix(#036, #d2e1dd, 25%); // #9eb6bf
+ @debug color.mix(rgba(242, 236, 228, 0.5), #6b717f); // rgba(141, 144, 152, 0.75)
+ ===
+ @debug color.mix(#036, #d2e1dd) // #698aa2
+ @debug color.mix(#036, #d2e1dd, 75%) // #355f84
+ @debug color.mix(#036, #d2e1dd, 25%) // #9eb6bf
+ @debug color.mix(rgba(242, 236, 228, 0.5), #6b717f) // rgba(141, 144, 152, 0.75)
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'opacify($color, $amount)', 'fade-in($color, $amount)', 'returns:color' %}
+ {% markdown %}
+ Makes `$color` more opaque.
+
+ The `$amount` must be a number between `0` and `1` (inclusive). Increases
+ the alpha channel of `$color` by that amount.
+ {% endmarkdown %}
+
+ {% headsUp false %}
+ {% markdown %}
+ The `opacify()` function increases the alpha channel by a fixed amount,
+ which is often not the desired effect. To make a color a certain
+ percentage more opaque than it was before, use [`scale()`](#scale)
+ instead.
+
+ Because `opacify()` is usually not the best way to make a color more
+ opaque, it's not included directly in the new module system. However, if
+ you have to preserve the existing behavior, `opacify($color, $amount)` can
+ be written [`adjust($color, $alpha: -$amount)`](#adjust).
+ {% endmarkdown %}
+
+ {% codeExample 'color-opacify', false %}
+ // rgba(#036, 0.7) has alpha 0.7, so when opacify() adds 0.3 it returns a fully
+ // opaque color.
+ @debug opacify(rgba(#036, 0.7), 0.3); // #036
+
+ // scale() instead makes it 30% more opaque than it was originally.
+ @debug color.scale(rgba(#036, 0.7), $alpha: 30%); // rgba(0, 51, 102, 0.79)
+ ===
+ // rgba(#036, 0.7) has alpha 0.7, so when opacify() adds 0.3 it returns a fully
+ // opaque color.
+ @debug opacify(rgba(#036, 0.7), 0.3) // #036
+
+ // scale() instead makes it 30% more opaque than it was originally.
+ @debug color.scale(rgba(#036, 0.7), $alpha: 30%) // rgba(0, 51, 102, 0.79)
+ {% endcodeExample %}
+ {% endheadsUp %}
+
+ {% codeExample 'color-opacify-2', false %}
+ @debug opacify(rgba(#6b717f, 0.5), 0.2); // rgba(107, 113, 127, 0.7)
+ @debug fade-in(rgba(#e1d7d2, 0.5), 0.4); // rgba(225, 215, 210, 0.9)
+ @debug opacify(rgba(#036, 0.7), 0.3); // #036
+ ===
+ @debug opacify(rgba(#6b717f, 0.5), 0.2) // rgba(107, 113, 127, 0.7)
+ @debug fade-in(rgba(#e1d7d2, 0.5), 0.4) // rgba(225, 215, 210, 0.9)
+ @debug opacify(rgba(#036, 0.7), 0.3) // #036
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'color.red($color)', 'red($color)', 'returns:number' %}
+ {% markdown %}
+ Returns the red channel of `$color` as a number between 0 and 255.
+
+ See also:
+
+ * [`color.green()`](#green) for getting a color's green channel.
+ * [`color.blue()`](#blue) for getting a color's blue channel.
+ * [`color.hue()`](#hue) for getting a color's hue.
+ * [`color.saturation()`](#saturation) for getting a color's saturation.
+ * [`color.lightness()`](#lightness) for getting a color's lightness.
+ * [`color.whiteness()`](#whiteness) for getting a color's whiteness.
+ * [`color.blackness()`](#blackness) for getting a color's blackness.
+ * [`color.alpha()`](#alpha) for getting a color's alpha channel.
+ {% endmarkdown %}
+
+ {% codeExample 'color-red', false %}
+ @debug color.red(#e1d7d2); // 225
+ @debug color.red(white); // 255
+ @debug color.red(black); // 0
+ ===
+ @debug color.red(#e1d7d2) // 225
+ @debug color.red(white) // 255
+ @debug color.red(black) // 0
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'color.saturate($color, $amount)', 'saturate($color, $amount)', 'returns:color' %}
+ {% markdown %}
+ Makes `$color` more saturated.
+
+ The `$amount` must be a number between `0%` and `100%` (inclusive).
+ Increases the HSL saturation of `$color` by that amount.
+ {% endmarkdown %}
+
+ {% headsUp false %}
+ {% markdown %}
+ The `saturate()` function increases saturation by a fixed amount, which is
+ often not the desired effect. To make a color a certain percentage more
+ saturated than it was before, use [`scale()`](#scale) instead.
+
+ Because `saturate()` is usually not the best way to make a color more
+ saturated, it's not included directly in the new module system. However,
+ if you have to preserve the existing behavior, `saturate($color, $amount)`
+ can be written [`adjust($color, $saturation: $amount)`](#adjust).
+ {% endmarkdown %}
+
+ {% codeExample 'color-saturate', false %}
+ // #0e4982 has saturation 80%, so when saturate() adds 30% it just becomes
+ // fully saturated.
+ @debug saturate(#0e4982, 30%); // #004990
+
+ // scale() instead makes it 30% more saturated than it was originally.
+ @debug color.scale(#0e4982, $saturation: 30%); // #0a4986
+ ===
+ // #0e4982 has saturation 80%, so when saturate() adds 30% it just becomes
+ // fully saturated.
+ @debug saturate(#0e4982, 30%) // #004990
+
+ // scale() instead makes it 30% more saturated than it was originally.
+ @debug color.scale(#0e4982, $saturation: 30%) // #0a4986
+ {% endcodeExample %}
+ {% endheadsUp %}
+
+ {% codeExample 'color-saturate-2', false %}
+ // Saturation 50% becomes 70%.
+ @debug saturate(#c69, 20%); // #e05299
+
+ // Saturation 35% becomes 85%.
+ @debug desaturate(#f2ece4, 50%); // #ebebeb
+
+ // Saturation 80% becomes 100%.
+ @debug saturate(#0e4982, 30%) // #004990
+ ===
+ // Saturation 50% becomes 70%.
+ @debug saturate(#c69, 20%); // #e05299
+
+ // Saturation 35% becomes 85%.
+ @debug desaturate(#f2ece4, 50%); // #ebebeb
+
+ // Saturation 80% becomes 100%.
+ @debug saturate(#0e4982, 30%) // #004990
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'color.saturation($color)', 'saturation($color)', 'returns:number' %}
+ {% markdown %}
+ Returns the HSL saturation of `$color` as a number between `0%` and `100%`.
+
+ See also:
+
+ * [`color.red()`](#red) for getting a color's red channel.
+ * [`color.green()`](#green) for getting a color's green channel.
+ * [`color.blue()`](#blue) for getting a color's blue channel.
+ * [`color.hue()`](#hue) for getting a color's hue.
+ * [`color.lightness()`](#lightness) for getting a color's lightness.
+ * [`color.whiteness()`](#whiteness) for getting a color's whiteness.
+ * [`color.blackness()`](#blackness) for getting a color's blackness.
+ * [`color.alpha()`](#alpha) for getting a color's alpha channel.
+ {% endmarkdown %}
+
+ {% codeExample 'color-saturation', false %}
+ @debug color.saturation(#e1d7d2); // 20%
+ @debug color.saturation(#f2ece4); // 30%
+ @debug color.saturation(#dadbdf); // 7.2463768116%
+ ===
+ @debug color.saturation(#e1d7d2) // 20%
+ @debug color.saturation(#f2ece4) // 30%
+ @debug color.saturation(#dadbdf) // 7.2463768116%
+ {% endcodeExample %}
+{% endfunction %}
+
+{% capture color_scale %}
+ color.scale($color,
+ $red: null, $green: null, $blue: null,
+ $saturation: null, $lightness: null,
+ $whiteness: null, $blackness: null,
+ $alpha: null)
+{% endcapture %}
+
+{% function color_scale, 'scale-color(...)', 'returns:color' %}
+ {% compatibility 'dart: "1.28.0"', 'libsass: false', 'ruby: false', 'feature: "$whiteness and $blackness"' %}{% endcompatibility %}
+
+ {% markdown %}
+ Fluidly scales one or more properties of `$color`.
+
+ Each keyword argument must be a number between `-100%` and `100%`
+ (inclusive). This indicates how far the corresponding property should be
+ moved from its original position towards the maximum (if the argument is
+ positive) or the minimum (if the argument is negative). This means that, for
+ example, `$lightness: 50%` will make all colors `50%` closer to maximum
+ lightness without making them fully white.
+
+ It's an error to specify an RGB property (`$red`, `$green`, and/or `$blue`)
+ at the same time as an HSL property (`$saturation`, and/or `$lightness`), or
+ either of those at the same time as an [HWB][] property (`$whiteness`,
+ and/or `$blackness`).
+
+ [HWB]: https://en.wikipedia.org/wiki/HWB_color_model
+
+ See also:
+
+ * [`color.adjust()`](#adjust) for changing a color's properties by fixed
+ amounts.
+ * [`color.change()`](#change) for setting a color's properties.
+ {% endmarkdown %}
+
+ {% codeExample 'color-scale', false %}
+ @debug color.scale(#6b717f, $red: 15%); // #81717f
+ @debug color.scale(#d2e1dd, $lightness: -10%, $saturation: 10%); // #b3d4cb
+ @debug color.scale(#998099, $alpha: -40%); // rgba(153, 128, 153, 0.6)
+ ===
+ @debug color.scale(#6b717f, $red: 15%) // #81717f
+ @debug color.scale(#d2e1dd, $lightness: -10%, $saturation: 10%) // #b3d4cb
+ @debug color.scale(#998099, $alpha: -40%) // rgba(153, 128, 153, 0.6)
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'transparentize($color, $amount)', 'fade-out($color, $amount)', 'returns:color' %}
+ {% markdown %}
+ Makes `$color` more transparent.
+
+ The `$amount` must be a number between `0` and `1` (inclusive). Decreases
+ the alpha channel of `$color` by that amount.
+ {% endmarkdown %}
+
+ {% headsUp false %}
+ {% markdown %}
+ The `transparentize()` function decreases the alpha channel by a fixed
+ amount, which is often not the desired effect. To make a color a certain
+ percentage more transparent than it was before, use
+ [`color.scale()`](#scale) instead.
+
+ Because `transparentize()` is usually not the best way to make a color
+ more transparent, it's not included directly in the new module system.
+ However, if you have to preserve the existing behavior,
+ `transparentize($color, $amount)` can be written [`color.adjust($color,
+ $alpha: -$amount)`](#adjust).
+ {% endmarkdown %}
+
+ {% codeExample 'transparentize', false %}
+ // rgba(#036, 0.3) has alpha 0.3, so when transparentize() subtracts 0.3 it
+ // returns a fully transparent color.
+ @debug transparentize(rgba(#036, 0.3), 0.3); // rgba(0, 51, 102, 0)
+
+ // scale() instead makes it 30% more transparent than it was originally.
+ @debug color.scale(rgba(#036, 0.3), $alpha: -30%); // rgba(0, 51, 102, 0.21)
+ ===
+ // rgba(#036, 0.3) has alpha 0.3, so when transparentize() subtracts 0.3 it
+ // returns a fully transparent color.
+ @debug transparentize(rgba(#036, 0.3), 0.3) // rgba(0, 51, 102, 0)
+
+ // scale() instead makes it 30% more transparent than it was originally.
+ @debug color.scale(rgba(#036, 0.3), $alpha: -30%) // rgba(0, 51, 102, 0.21)
+ {% endcodeExample %}
+ {% endheadsUp %}
+
+ {% codeExample 'transparentize-2', false %}
+ @debug transparentize(rgba(#6b717f, 0.5), 0.2) // rgba(107, 113, 127, 0.3)
+ @debug fade-out(rgba(#e1d7d2, 0.5), 0.4) // rgba(225, 215, 210, 0.1)
+ @debug transparentize(rgba(#036, 0.3), 0.3) // rgba(0, 51, 102, 0)
+ ===
+ @debug transparentize(rgba(#6b717f, 0.5), 0.2) // rgba(107, 113, 127, 0.3)
+ @debug fade-out(rgba(#e1d7d2, 0.5), 0.4) // rgba(225, 215, 210, 0.1)
+ @debug transparentize(rgba(#036, 0.3), 0.3) // rgba(0, 51, 102, 0)
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'color.whiteness($color)', 'returns:number' %}
+ {% compatibility 'dart: "1.28.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %}
+
+ {% markdown %}
+ Returns the [HWB][] whiteness of `$color` as a number between `0%` and
+ `100%`.
+
+ [HWB]: https://en.wikipedia.org/wiki/HWB_color_model
+
+ See also:
+
+ * [`color.red()`](#red) for getting a color's red channel.
+ * [`color.green()`](#green) for getting a color's green channel.
+ * [`color.hue()`](#hue) for getting a color's hue.
+ * [`color.saturation()`](#saturation) for getting a color's saturation.
+ * [`color.lightness()`](#lightness) for getting a color's lightness.
+ * [`color.blackness()`](#blackness) for getting a color's blackness.
+ * [`color.alpha()`](#alpha) for getting a color's alpha channel.
+ {% endmarkdown %}
+
+ {% codeExample 'color-whiteness', false %}
+ @debug color.whiteness(#e1d7d2); // 82.3529411765%
+ @debug color.whiteness(white); // 100%
+ @debug color.whiteness(black); // 0%
+ ===
+ @debug color.whiteness(#e1d7d2) // 82.3529411765%
+ @debug color.whiteness(white) // 100%
+ @debug color.whiteness(black) // 0%
+ {% endcodeExample %}
+{% endfunction %}
diff --git a/source/documentation/modules/index.liquid b/source/documentation/modules/index.liquid
new file mode 100644
index 000000000..47c3e74bb
--- /dev/null
+++ b/source/documentation/modules/index.liquid
@@ -0,0 +1,272 @@
+---
+title: Built-In Modules
+eleventyComputed:
+ before_introduction: >
+ {% render 'doc_snippets/built-in-module-status' %}
+introduction: >
+ Sass provides many built-in modules which contain useful functions (and the
+ occasional mixin). These modules can be loaded with the [`@use`
+ rule](/documentation/at-rules/use) like any user-defined stylesheet, and their
+ functions can be called [like any other module
+ member](/documentation/at-rules/use#loading-members). All built-in module URLs
+ begin with `sass:` to indicate that they're part of Sass itself.
+---
+
+{% headsUp %}
+ Before the Sass module system was introduced, all Sass functions were globally
+ available at all times. Many functions still have global aliases (these are
+ listed in their documentation). The Sass team discourages their use and will
+ eventually deprecate them, but for now they remain available for compatibility
+ with older Sass versions and with LibSass (which doesn't support the module
+ system yet).
+
+ [A few functions][] are *only* available globally even in the new module
+ system, either because they have special evaluation behavior ([`if()`][]) or
+ because they add extra behavior on top of built-in CSS functions ([`rgb()`][]
+ and [`hsl()`][]). These will not be deprecated and can be used freely.
+
+ [a few functions]: #global-functions
+ [`if()`]: #if
+ [`rgb()`]: #rgb
+ [`hsl()`]: #hsl
+{% endheadsUp %}
+
+{% codeExample 'modules' %}
+ @use "sass:color";
+
+ .button {
+ $primary-color: #6b717f;
+ color: $primary-color;
+ border: 1px solid color.scale($primary-color, $lightness: 20%);
+ }
+ ===
+ @use "sass:color"
+
+ .button
+ $primary-color: #6b717f
+ color: $primary-color
+ border: 1px solid color.scale($primary-color, $lightness: 20%)
+ ===
+ .button {
+ color: #6b717f;
+ border: 1px solid #878d9a;
+ }
+{% endcodeExample %}
+
+{% markdown %}
+ Sass provides the following built-in modules:
+
+ * The [`sass:math` module][] provides functions that operate on [numbers][].
+
+ * The [`sass:string` module][] makes it easy to combine, search, or split
+ apart [strings][].
+
+ * The [`sass:color` module][] generates new [colors][] based on existing ones,
+ making it easy to build color themes.
+
+ * The [`sass:list` module][] lets you access and modify values in [lists][].
+
+ * The [`sass:map` module][] makes it possible to look up the value associated
+ with a key in a [map][], and much more.
+
+ * The [`sass:selector` module][] provides access to Sass's powerful selector
+ engine.
+
+ * The [`sass:meta` module][] exposes the details of Sass's inner workings.
+
+ [`sass:math` module]: /documentation/modules/math
+ [numbers]: /documentation/values/numbers
+ [`sass:string` module]: /documentation/modules/string
+ [strings]: /documentation/values/strings
+ [`sass:color` module]: /documentation/modules/color
+ [colors]: /documentation/values/colors
+ [`sass:list` module]: /documentation/modules/list
+ [lists]: /documentation/values/lists
+ [`sass:map` module]: /documentation/modules/map
+ [map]: /documentation/values/maps
+ [`sass:selector` module]: /documentation/modules/selector
+ [`sass:meta` module]: /documentation/modules/meta
+
+ ## Global Functions
+{% endmarkdown %}
+
+{% function 'hsl($hue $saturation $lightness)', 'hsl($hue $saturation $lightness / $alpha)', 'hsl($hue, $saturation, $lightness, $alpha: 1)', 'hsla($hue $saturation $lightness)', 'hsla($hue $saturation $lightness / $alpha)', 'hsla($hue, $saturation, $lightness, $alpha: 1)', 'returns:color' %}
+ {% compatibility 'dart: "1.15.0"', 'libsass: false', 'ruby: false', 'feature: "Level 4 Syntax"' %}
+ LibSass and Ruby Sass only support the following signatures:
+
+ * `hsl($hue, $saturation, $lightness)`
+ * `hsla($hue, $saturation, $lightness, $alpha)`
+
+ Note that for these implementations, the `$alpha` argument is *required* if
+ the function name `hsla()` is used, and *forbidden* if the function name
+ `hsl()` is used.
+ {% endcompatibility %}
+
+ {% compatibility 'dart: true', 'libsass: false', 'ruby: "3.7.0"', 'feature: "Percent Alpha"' %}
+ LibSass and older versions of Ruby Sass don't support alpha values specified
+ as percentages.
+ {% endcompatibility %}
+
+ {% markdown %}
+ Returns a color with the given [hue, saturation, and lightness][] and the
+ given alpha channel.
+
+ [hue, saturation, and lightness]: https://en.wikipedia.org/wiki/HSL_and_HSV
+
+ The hue is a number between `0deg` and `360deg` (inclusive) and may be
+ unitless. The saturation and lightness are numbers between `0%` and `100%`
+ (inclusive) and may *not* be unitless. The alpha channel can be specified as
+ either a unitless number between 0 and 1 (inclusive), or a percentage
+ between `0%` and `100%` (inclusive).
+ {% endmarkdown %}
+
+ {% funFact false %}
+ {% markdown %}
+ You can pass [special functions][] like `calc()` or `var()` in place of
+ any argument to `hsl()`. You can even use `var()` in place of multiple
+ arguments, since it might be replaced by multiple values! When a color
+ function is called this way, it returns an unquoted string using the same
+ signature it was called with.
+
+ [special functions]: /documentation/syntax/special-functions
+ {% endmarkdown %}
+
+ {% codeExample 'hsl-special', false %}
+ @debug hsl(210deg 100% 20% / var(--opacity)); // hsl(210deg 100% 20% / var(--opacity))
+ @debug hsla(var(--peach), 20%); // hsla(var(--peach), 20%)
+ ===
+ @debug hsl(210deg 100% 20% / var(--opacity)) // hsl(210deg 100% 20% / var(--opacity))
+ @debug hsla(var(--peach), 20%) // hsla(var(--peach), 20%)
+ {% endcodeExample %}
+ {% endfunFact %}
+
+ {% headsUp %}
+ Sass's [special parsing rules][] for slash-separated values make it
+ difficult to pass variables for `$lightness` or `$alpha` when using the
+ `hsl($hue $saturation $lightness / $alpha)` signature. Consider using
+ `hsl($hue, $saturation, $lightness, $alpha)` instead.
+
+ [special parsing rules]: /documentation/operators/numeric#slash-separated-values
+ {% endheadsUp %}
+
+ {% codeExample 'hsl', false %}
+ @debug hsl(210deg 100% 20%); // #036
+ @debug hsl(34, 35%, 92%); // #f2ece4
+ @debug hsl(210deg 100% 20% / 50%); // rgba(0, 51, 102, 0.5)
+ @debug hsla(34, 35%, 92%, 0.2); // rgba(242, 236, 228, 0.2)
+ ===
+ @debug hsl(210deg 100% 20%) // #036
+ @debug hsl(34, 35%, 92%) // #f2ece4
+ @debug hsl(210deg 100% 20% / 50%) // rgba(0, 51, 102, 0.5)
+ @debug hsla(34, 35%, 92%, 0.2) // rgba(242, 236, 228, 0.2)
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'if($condition, $if-true, $if-false)' %}
+ {% markdown %}
+ Returns `$if-true` if `$condition` is [truthy][], and `$if-false` otherwise.
+
+ This function is special in that it doesn't even evaluate the argument that
+ isn't returned, so it's safe to call even if the unused argument would throw
+ an error.
+
+ [truthy]: /documentation/at-rules/control/if#truthiness-and-falsiness
+ {% endmarkdown %}
+
+ {% codeExample 'debug', false %}
+ @debug if(true, 10px, 15px); // 10px
+ @debug if(false, 10px, 15px); // 15px
+ @debug if(variable-defined($var), $var, null); // null
+ ===
+ @debug if(true, 10px, 15px) // 10px
+ @debug if(false, 10px, 15px) // 15px
+ @debug if(variable-defined($var), $var, null) // null
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'rgb($red $green $blue)', 'rgb($red $green $blue / $alpha)', 'rgb($red, $green, $blue, $alpha: 1)', 'rgb($color, $alpha)', 'rgba($red $green $blue)', 'rgba($red $green $blue / $alpha)', 'rgba($red, $green, $blue, $alpha: 1)', 'rgba($color, $alpha)', 'returns:color' %}
+ {% compatibility 'dart: "1.15.0"', 'libsass: false', 'ruby: false', 'feature: "Level 4 Syntax"' %}
+ LibSass and Ruby Sass only support the following signatures:
+
+ * `rgb($red, $green, $blue)`
+ * `rgba($red, $green, $blue, $alpha)`
+ * `rgba($color, $alpha)`
+
+ Note that for these implementations, the `$alpha` argument is *required* if
+ the function name `rgba()` is used, and *forbidden* if the function name
+ `rgb()` is used.
+ {% endcompatibility %}
+
+ {% compatibility 'dart: true', 'libsass: false', 'ruby: "3.7.0"', 'feature: "Percent Alpha"' %}
+ LibSass and older versions of Ruby Sass don't support alpha values specified
+ as percentages.
+ {% endcompatibility %}
+
+ {% markdown %}
+ If `$red`, `$green`, `$blue`, and optionally `$alpha` are passed, returns a
+ color with the given red, green, blue, and alpha channels.
+
+ Each channel can be specified as either a [unitless][] number between 0 and
+ 255 (inclusive), or a percentage between `0%` and `100%` (inclusive). The
+ alpha channel can be specified as either a unitless number between 0 and 1
+ (inclusive), or a percentage between `0%` and `100%` (inclusive).
+
+ [unitless]: /documentation/values/numbers#units
+ {% endmarkdown %}
+
+ {% funFact false %}
+ {% markdown %}
+ You can pass [special functions][] like `calc()` or `var()` in place of
+ any argument to `rgb()`. You can even use `var()` in place of multiple
+ arguments, since it might be replaced by multiple values! When a color
+ function is called this way, it returns an unquoted string using the same
+ signature it was called with.
+
+ [special functions]: /documentation/syntax/special-functions
+ {% endmarkdown %}
+
+ {% codeExample 'rgb-special', false %}
+ @debug rgb(0 51 102 / var(--opacity)); // rgb(0 51 102 / var(--opacity))
+ @debug rgba(var(--peach), 0.2); // rgba(var(--peach), 0.2)
+ ===
+ @debug rgb(0 51 102 / var(--opacity)) // rgb(0 51 102 / var(--opacity))
+ @debug rgba(var(--peach), 0.2) // rgba(var(--peach), 0.2)
+ {% endcodeExample %}
+ {% endfunFact %}
+
+ {% headsUp %}
+ Sass's [special parsing rules][] for slash-separated values make it
+ difficult to pass variables for `$blue` or `$alpha` when using the
+ `rgb($red $green $blue / $alpha)` signature. Consider using
+ `rgb($red, $green, $blue, $alpha)` instead.
+
+ [special parsing rules]: /documentation/operators/numeric#slash-separated-values
+ {% endheadsUp %}
+
+ {% codeExample 'rgb', false %}
+ @debug rgb(0 51 102); // #036
+ @debug rgb(95%, 92.5%, 89.5%); // #f2ece4
+ @debug rgb(0 51 102 / 50%); // rgba(0, 51, 102, 0.5)
+ @debug rgba(95%, 92.5%, 89.5%, 0.2); // rgba(242, 236, 228, 0.2)
+ ===
+ @debug rgb(0 51 102) // #036
+ @debug rgb(95%, 92.5%, 89.5%) // #f2ece4
+ @debug rgb(0 51 102 / 50%) // rgba(0, 51, 102, 0.5)
+ @debug rgba(95%, 92.5%, 89.5%, 0.2) // rgba(242, 236, 228, 0.2)
+ {% endcodeExample %}
+
+ {{ '---' | markdown }}
+
+ {% markdown %}
+ If `$color` and `$alpha` are passed, this returns `$color` with the given
+ `$alpha` channel instead of its original alpha channel.
+ {% endmarkdown %}
+
+ {% codeExample 'color-and-alpha', false %}
+ @debug rgb(#f2ece4, 50%); // rgba(242, 236, 228, 0.5);
+ @debug rgba(rgba(0, 51, 102, 0.5), 1); // #003366
+ ===
+ @debug rgb(#f2ece4, 50%) // rgba(242, 236, 228, 0.5)
+ @debug rgba(rgba(0, 51, 102, 0.5), 1) // #003366
+ {% endcodeExample %}
+{% endfunction %}
diff --git a/source/documentation/modules/list.liquid b/source/documentation/modules/list.liquid
new file mode 100644
index 000000000..3efaaf993
--- /dev/null
+++ b/source/documentation/modules/list.liquid
@@ -0,0 +1,242 @@
+---
+title: sass:list
+---
+
+{% render 'doc_snippets/built-in-module-status' %}
+
+{% funFact %}
+ In Sass, every [map][] counts as a list that contains a two-element list for
+ each key/value pair. For example, `(1: 2, 3: 4)` counts as `(1 2, 3 4)`. So
+ all these functions work for maps as well!
+
+ [map]: /documentation/values/maps
+
+ Individual values also count as lists. All these functions treat `1px` as a
+ list that contains the value `1px`.
+{% endfunFact %}
+
+{% function 'list.append($list, $val, $separator: auto)', 'append($list, $val, $separator: auto)', 'returns:list' %}
+ {% markdown %}
+ Returns a copy of `$list` with `$val` added to the end.
+
+ If `$separator` is `comma`, `space`, or `slash`, the returned list is
+ comma-separated, space-separated, or slash-separated, respectively. If it's
+ `auto` (the default), the returned list will use the same separator as
+ `$list` (or `space` if `$list` doesn't have a separator). Other values
+ aren't allowed.
+
+ [separator]: /documentation/values/lists
+
+ Note that unlike [`list.join()`](#join), if `$val` is a list it's nested
+ within the returned list rather than having all its elements added to the
+ returned list.
+ {% endmarkdown %}
+
+ {% codeExample 'list-append', false %}
+ @debug list.append(10px 20px, 30px); // 10px 20px 30px
+ @debug list.append((blue, red), green); // blue, red, green
+ @debug list.append(10px 20px, 30px 40px); // 10px 20px (30px 40px)
+ @debug list.append(10px, 20px, $separator: comma); // 10px, 20px
+ @debug list.append((blue, red), green, $separator: space); // blue red green
+ ===
+ @debug list.append(10px 20px, 30px) // 10px 20px 30px
+ @debug list.append((blue, red), green) // blue, red, green
+ @debug list.append(10px 20px, 30px 40px) // 10px 20px (30px 40px)
+ @debug list.append(10px, 20px, $separator: comma) // 10px, 20px
+ @debug list.append((blue, red), green, $separator: space) // blue red green
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'list.index($list, $value)', 'index($list, $value)', 'returns:number | null' %}
+ {% markdown %}
+ Returns the [index][] of `$value` in `$list`.
+
+ [index]: /documentation/values/lists#indexes
+
+ If `$value` doesn't appear in `$list`, this returns [`null`][]. If `$value`
+ appears multiple times in `$list`, this returns the index of its first
+ appearance.
+
+ [`null`]: /documentation/values/null
+ {% endmarkdown %}
+
+ {% render 'code_snippets/example-list-index' %}
+{% endfunction %}
+
+{% function 'list.is-bracketed($list)', 'is-bracketed($list)', 'returns:boolean' %}
+ {% markdown %}
+ Returns whether `$list` has square brackets.
+ {% endmarkdown %}
+
+ {% codeExample 'list-is-bracketed', false %}
+ @debug list.is-bracketed(1px 2px 3px); // false
+ @debug list.is-bracketed([1px, 2px, 3px]); // true
+ ===
+ @debug list.is-bracketed(1px 2px 3px) // false
+ @debug list.is-bracketed([1px, 2px, 3px]) // true
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'list.join($list1, $list2, $separator: auto, $bracketed: auto)', 'join($list1, $list2, $separator: auto, $bracketed: auto)', 'returns:list' %}
+ {% markdown %}
+ Returns a new list containing the elements of `$list1` followed by the
+ elements of `$list2`.
+ {% endmarkdown %}
+
+ {% headsUp %}
+ Because individual values count as single-element lists, it's possible to
+ use `list.join()` to add a value to the end of a list. However, *this is not
+ recommended*, since if that value is a list it will be concatenated, which
+ is probably not what you're expecting.
+
+ Use [`list.append()`](#append) instead to add a single value to a list. Only
+ use `list.join()` to combine two lists together into one.
+ {% endheadsUp %}
+
+ {% markdown %}
+ If `$separator` is `comma`, `space`, or `slash`, the returned list is
+ comma-separated, space-separated, or slash-separated, respectively. If it's
+ `auto` (the default), the returned list will use the same separator as
+ `$list1` if it has a separator, or else `$list2` if it has a separator, or
+ else `space`. Other values aren't allowed.
+
+ If `$bracketed` is `auto` (the default), the returned list will be bracketed
+ if `$list1` is. Otherwise, the returned list will have square brackets if
+ `$bracketed` is [truthy] and no brackets if `$bracketed` is falsey.
+
+ [truthy]: /documentation/values/booleans#truthiness-and-falsiness
+ {% endmarkdown %}
+
+ {% codeExample 'list-join', false %}
+ @debug list.join(10px 20px, 30px 40px); // 10px 20px 30px 40px
+ @debug list.join((blue, red), (#abc, #def)); // blue, red, #abc, #def
+ @debug list.join(10px, 20px); // 10px 20px
+ @debug list.join(10px, 20px, $separator: comma); // 10px, 20px
+ @debug list.join((blue, red), (#abc, #def), $separator: space); // blue red #abc #def
+ @debug list.join([10px], 20px); // [10px 20px]
+ @debug list.join(10px, 20px, $bracketed: true); // [10px 20px]
+ ===
+ @debug list.join(10px 20px, 30px 40px) // 10px 20px 30px 40px
+ @debug list.join((blue, red), (#abc, #def)) // blue, red, #abc, #def
+ @debug list.join(10px, 20px) // 10px 20px
+ @debug list.join(10px, 20px, comma) // 10px, 20px
+ @debug list.join((blue, red), (#abc, #def), space) // blue red #abc #def
+ @debug list.join([10px], 20px) // [10px 20px]
+ @debug list.join(10px, 20px, $bracketed: true) // [10px 20px]
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'list.length($list)', 'length($list)', 'returns:number' %}
+ {% markdown %}
+ Returns the length of `$list`.
+
+ This can also return the number of pairs in a map.
+ {% endmarkdown %}
+
+ {% codeExample 'list-length', false %}
+ @debug list.length(10px); // 1
+ @debug list.length(10px 20px 30px); // 3
+ @debug list.length((width: 10px, height: 20px)); // 2
+ ===
+ @debug list.length(10px) // 1
+ @debug list.length(10px 20px 30px) // 3
+ @debug list.length((width: 10px, height: 20px)) // 2
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'list.separator($list)', 'list-separator($list)', 'returns:unquoted string' %}
+ {% markdown %}
+ Returns the name of the separator used by `$list`, either `space`, `comma`,
+ or `slash`.
+
+ If `$list` doesn't have a separator, returns `space`.
+ {% endmarkdown %}
+
+ {% codeExample 'list-separator', false %}
+ @debug list.separator(1px 2px 3px); // space
+ @debug list.separator(1px, 2px, 3px); // comma
+ @debug list.separator('Helvetica'); // space
+ @debug list.separator(()); // space
+ ===
+ @debug list.separator(1px 2px 3px) // space
+ @debug list.separator(1px, 2px, 3px) // comma
+ @debug list.separator('Helvetica') // space
+ @debug list.separator(()) // space
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'list.nth($list, $n)', 'nth($list, $n)' %}
+ {% markdown %}
+ Returns the element of `$list` at [index][] `$n`.
+
+ [index]: /documentation/values/lists#indexes
+
+ If `$n` is negative, it counts from the end of `$list`. Throws an error if
+ there is no element at index `$n`.
+ {% endmarkdown %}
+
+ {% render 'code_snippets/example-list-nth' %}
+{% endfunction %}
+
+{% function 'list.set-nth($list, $n, $value)', 'set-nth($list, $n, $value)', 'returns:list' %}
+ {% markdown %}
+ Returns a copy of `$list` with the element at [index][] `$n` replaced with
+ `$value`.
+
+ [index]: /documentation/values/lists#indexes
+
+ If `$n` is negative, it counts from the end of `$list`. Throws an error if
+ there is no existing element at index `$n`.
+ {% endmarkdown %}
+
+ {% codeExample 'list-set-nth', false %}
+ @debug list.set-nth(10px 20px 30px, 1, 2em); // 2em 20px 30px
+ @debug list.set-nth(10px 20px 30px, -1, 8em); // 10px, 20px, 8em
+ @debug list.set-nth((Helvetica, Arial, sans-serif), 3, Roboto); // Helvetica, Arial, Roboto
+ ===
+ @debug list.set-nth(10px 20px 30px, 1, 2em); // 2em 20px 30px
+ @debug list.set-nth(10px 20px 30px, -1, 8em); // 10px, 20px, 8em
+ @debug list.set-nth((Helvetica, Arial, sans-serif), 3, Roboto); // Helvetica, Arial, Roboto
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'list.slash($elements...)', 'returns:list' %}
+ {% markdown %}
+ Returns a slash-separated list that contains `$elements`.
+ {% endmarkdown %}
+
+ {% headsUp %}
+ This function is a temporary solution for creating slash-separated lists.
+ Eventually, they'll be written literally with slashes, as in
+ `1px / 2px / solid`, but for the time being [slashes are used for division]
+ so Sass can't use them for new syntax until the old syntax is removed.
+
+ [slashes are used for division]: /documentation/breaking-changes/slash-div
+ {% endheadsUp %}
+
+ {% codeExample 'list-slash', false %}
+ @debug list.slash(1px, 50px, 100px); // 1px / 50px / 100px
+ ===
+ @debug list.slash(1px, 50px, 100px) // 1px / 50px / 100px
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'list.zip($lists...)', 'zip($lists...)', 'returns:list' %}
+ {% markdown %}
+ Combines every list in `$lists` into a single list of sub-lists.
+
+ Each element in the returned list contains all the elements at that position
+ in `$lists`. The returned list is as long as the shortest list in `$lists`.
+
+ The returned list is always comma-separated and the sub-lists are always
+ space-separated.
+ {% endmarkdown %}
+
+ {% codeExample 'list-zip', false %}
+ @debug list.zip(10px 50px 100px, short mid long); // 10px short, 50px mid, 100px long
+ @debug list.zip(10px 50px 100px, short mid); // 10px short, 50px mid
+ ===
+ @debug list.zip(10px 50px 100px, short mid long) // 10px short, 50px mid, 100px long
+ @debug list.zip(10px 50px 100px, short mid) // 10px short, 50px mid
+ {% endcodeExample %}
+{% endfunction %}
diff --git a/source/documentation/modules/map.liquid b/source/documentation/modules/map.liquid
new file mode 100644
index 000000000..bc1f99040
--- /dev/null
+++ b/source/documentation/modules/map.liquid
@@ -0,0 +1,484 @@
+---
+title: sass:map
+---
+
+{% render 'doc_snippets/built-in-module-status' %}
+
+{% funFact false %}
+ {% markdown %}
+ Sass libraries and design systems tend to share and override configurations
+ that are represented as nested maps (maps that contain maps that contain
+ maps).
+
+ To help you work with nested maps, some map functions support deep
+ operations. For example, if you pass multiple keys to `map.get()`, it will
+ follow those keys to find the desired nested map:
+ {% endmarkdown %}
+
+ {% codeExample 'map', false %}
+ $config: (a: (b: (c: d)));
+ @debug map.get($config, a, b, c); // d
+ ===
+ $config: (a: (b: (c: d)))
+ @debug map.get($config, a, b, c) // d
+ {% endcodeExample %}
+{% endfunFact %}
+
+{% function 'map.deep-merge($map1, $map2)', 'returns:map' %}
+ {% compatibility 'dart: "1.27.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %}
+
+ {% markdown %}
+ Identical to [`map.merge()`](#merge), except that nested map values are
+ *also* recursively merged.
+ {% endmarkdown %}
+
+ {% codeExample 'map-deep-merge', false %}
+ $helvetica-light: (
+ "weights": (
+ "lightest": 100,
+ "light": 300
+ )
+ );
+ $helvetica-heavy: (
+ "weights": (
+ "medium": 500,
+ "bold": 700
+ )
+ );
+
+ @debug map.deep-merge($helvetica-light, $helvetica-heavy);
+ // (
+ // "weights": (
+ // "lightest": 100,
+ // "light": 300,
+ // "medium": 500,
+ // "bold": 700
+ // )
+ // )
+ @debug map.merge($helvetica-light, $helvetica-heavy);
+ // (
+ // "weights": (
+ // "medium: 500,
+ // "bold": 700
+ // )
+ // )
+ ===
+ $helvetica-light: ("weights": ("lightest": 100, "light": 300))
+ $helvetica-heavy: ("weights": ("medium": 500, "bold": 700))
+
+ @debug map.deep-merge($helvetica-light, $helvetica-heavy)
+ // (
+ // "weights": (
+ // "lightest": 100,
+ // "light": 300,
+ // "medium": 500,
+ // "bold": 700
+ // )
+ // )
+ @debug map.merge($helvetica-light, $helvetica-heavy);
+ // (
+ // "weights": (
+ // "medium: 500,
+ // "bold": 700
+ // )
+ // )
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'map.deep-remove($map, $key, $keys...)', 'returns:map' %}
+ {% compatibility 'dart: "1.27.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %}
+
+ {% markdown %}
+ If `$keys` is empty, returns a copy of `$map` without a value associated
+ with `$key`.
+ {% endmarkdown %}
+
+ {% codeExample 'map-deep-remove', false %}
+ $font-weights: ("regular": 400, "medium": 500, "bold": 700);
+
+ @debug map.deep-remove($font-weights, "regular");
+ // ("medium": 500, "bold": 700)
+ ===
+ $font-weights: ("regular": 400, "medium": 500, "bold": 700)
+
+ @debug map.deep-remove($font-weights, "regular")
+ // ("medium": 500, "bold": 700)
+ {% endcodeExample %}
+
+ {{ '---' | markdown }}
+
+ {% markdown %}
+ If `$keys` is not empty, follows the set of keys including `$key` and
+ excluding the last key in `$keys`, from left to right, to find the nested
+ map targeted for updating.
+
+ Returns a copy of `$map` where the targeted map does not have a value
+ associated with the last key in `$keys`.
+ {% endmarkdown %}
+
+ {% codeExample 'map-deep-remove-2', false %}
+ $fonts: (
+ "Helvetica": (
+ "weights": (
+ "regular": 400,
+ "medium": 500,
+ "bold": 700
+ )
+ )
+ );
+
+ @debug map.deep-remove($fonts, "Helvetica", "weights", "regular");
+ // (
+ // "Helvetica": (
+ // "weights: (
+ // "medium": 500,
+ // "bold": 700
+ // )
+ // )
+ // )
+ ===
+ $fonts: ("Helvetica": ("weights": ("regular": 400, "medium": 500, "bold": 700)))
+
+ @debug map.deep-remove($fonts, "Helvetica", "weights", "regular")
+ // (
+ // "Helvetica": (
+ // "weights: (
+ // "medium": 500,
+ // "bold": 700
+ // )
+ // )
+ // )
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'map.get($map, $key, $keys...)', 'map-get($map, $key, $keys...)' %}
+ {% markdown %}
+ If `$keys` is empty, returns the value in `$map` associated with `$key`.
+
+ If `$map` doesn't have a value associated with `$key`, returns [`null`][].
+
+ [`null`]: /documentation/values/null
+ {% endmarkdown %}
+
+ {% render 'code_snippets/example-map-get' %}
+
+ {{ '---' | markdown }}
+
+ {% compatibility 'dart: "1.27.0"', 'libsass: false', 'ruby: false' %}
+ Only Dart Sass supports calling `map.get()` with more than two arguments.
+ {% endcompatibility %}
+
+ {% markdown %}
+ If `$keys` is not empty, follows the set of keys including `$key` and
+ excluding the last key in `$keys`, from left to right, to find the nested
+ map targeted for searching.
+
+ Returns the value in the targeted map associated with the last key in
+ `$keys`.
+
+ Returns [`null`][] if the map does not have a value associated with the key,
+ or if any key in `$keys` is missing from a map or references a value that is
+ not a map.
+
+ [`null`]: /documentation/values/null
+ {% endmarkdown %}
+
+ {% codeExample 'map-deep-remove-2', false %}
+ $fonts: (
+ "Helvetica": (
+ "weights": (
+ "regular": 400,
+ "medium": 500,
+ "bold": 700
+ )
+ )
+ );
+
+ @debug map.get($fonts, "Helvetica", "weights", "regular"); // 400
+ @debug map.get($fonts, "Helvetica", "colors"); // null
+ ===
+ $fonts: ("Helvetica": ("weights": ("regular": 400, "medium": 500, "bold": 700)))
+
+ @debug map.get($fonts, "Helvetica", "weights", "regular") // 400
+ @debug map.get($fonts, "Helvetica", "colors") // null
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'map.has-key($map, $key, $keys...)', 'map-has-key($map, $key, $keys...)', 'returns:boolean' %}
+ {% markdown %}
+ If `$keys` is empty, returns whether `$map` contains a value associated with
+ `$key`.
+ {% endmarkdown %}
+
+ {% codeExample 'map-has-key', false %}
+ $font-weights: ("regular": 400, "medium": 500, "bold": 700);
+
+ @debug map.has-key($font-weights, "regular"); // true
+ @debug map.has-key($font-weights, "bolder"); // false
+ ===
+ $font-weights: ("regular": 400, "medium": 500, "bold": 700)
+
+ @debug map.has-key($font-weights, "regular") // true
+ @debug map.has-key($font-weights, "bolder") // false
+ {% endcodeExample %}
+
+ {{ '---' | markdown }}
+
+ {% compatibility 'dart: "1.27.0"', 'libsass: false', 'ruby: false' %}
+ Only Dart Sass supports calling `map.has-key()` with more than two
+ arguments.
+ {% endcompatibility %}
+
+ {% markdown %}
+ If `$keys` is not empty, follows the set of keys including `$key` and
+ excluding the last key in `$keys`, from left to right, to find the nested
+ map targeted for searching.
+
+ Returns true if the targeted map contains a value associated with the last
+ key in `$keys`.
+
+ Returns false if it does not, or if any key in `$keys` is missing from a map
+ or references a value that is not a map.
+ {% endmarkdown %}
+
+ {% codeExample 'map-has-key-2', false %}
+ $fonts: (
+ "Helvetica": (
+ "weights": (
+ "regular": 400,
+ "medium": 500,
+ "bold": 700
+ )
+ )
+ );
+
+ @debug map.has-key($fonts, "Helvetica", "weights", "regular"); // true
+ @debug map.has-key($fonts, "Helvetica", "colors"); // false
+ ===
+ $fonts: ("Helvetica": ("weights": ("regular": 400, "medium": 500, "bold": 700)))
+
+ @debug map.has-key($fonts, "Helvetica", "weights", "regular") // true
+ @debug map.has-key($fonts, "Helvetica", "colors") // false
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'map.keys($map)', 'map-keys($map)', 'returns:list' %}
+ {% markdown %}
+ Returns a comma-separated list of all the keys in `$map`.
+ {% endmarkdown %}
+
+ {% codeExample 'map-keys', false %}
+ $font-weights: ("regular": 400, "medium": 500, "bold": 700);
+
+ @debug map.keys($font-weights); // "regular", "medium", "bold"
+ ===
+ $font-weights: ("regular": 400, "medium": 500, "bold": 700)
+
+ @debug map.keys($font-weights) // "regular", "medium", "bold"
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'map.merge($map1, $map2)', 'map-merge($map1, $map2)', 'map.merge($map1, $keys..., $map2)', 'map-merge($map1, $keys..., $map2)', 'returns:map' %}
+ {% headsUp %}
+ In practice, the actual arguments to `map.merge($map1, $keys..., $map2)` are
+ passed as `map.merge($map1, $args...)`. They are described here as `$map1,
+ $keys..., $map2` for explanation purposes only.
+ {% endheadsUp %}
+
+ {% markdown %}
+ If no `$keys` are passed, returns a new map with all the keys and values
+ from both `$map1` and `$map2`.
+
+ If both `$map1` and `$map2` have the same key, `$map2`'s value takes
+ precedence.
+
+ All keys in the returned map that also appear in `$map1` have the same order
+ as in `$map1`. New keys from `$map2` appear at the end of the map.
+ {% endmarkdown %}
+
+ {% codeExample 'map-merge', false %}
+ $light-weights: ("lightest": 100, "light": 300);
+ $heavy-weights: ("medium": 500, "bold": 700);
+
+ @debug map.merge($light-weights, $heavy-weights);
+ // ("lightest": 100, "light": 300, "medium": 500, "bold": 700)
+ ===
+ $light-weights: ("lightest": 100, "light": 300)
+ $heavy-weights: ("medium": 500, "bold": 700)
+
+ @debug map.merge($light-weights, $heavy-weights)
+ // ("lightest": 100, "light": 300, "medium": 500, "bold": 700)
+ {% endcodeExample %}
+
+ {{ '---' | markdown }}
+
+ {% compatibility 'dart: "1.27.0"', 'libsass: false', 'ruby: false' %}
+ Only Dart Sass supports calling `map.merge()` with more than two arguments.
+ {% endcompatibility %}
+
+ {% markdown %}
+ If `$keys` is not empty, follows the `$keys` to find the nested map targeted
+ for merging. If any key in `$keys` is missing from a map or references a
+ value that is not a map, sets the value at that key to an empty map.
+
+ Returns a copy of `$map1` where the targeted map is replaced by a new map
+ that contains all the keys and values from both the targeted map and
+ `$map2`.
+ {% endmarkdown %}
+
+ {% codeExample 'map-merge-2', false %}
+ $fonts: (
+ "Helvetica": (
+ "weights": (
+ "lightest": 100,
+ "light": 300
+ )
+ )
+ );
+ $heavy-weights: ("medium": 500, "bold": 700);
+
+ @debug map.merge($fonts, "Helvetica", "weights", $heavy-weights);
+ // (
+ // "Helvetica": (
+ // "weights": (
+ // "lightest": 100,
+ // "light": 300,
+ // "medium": 500,
+ // "bold": 700
+ // )
+ // )
+ // )
+ ===
+ $fonts: ("Helvetica": ("weights": ("lightest": 100, "light": 300)))
+ $heavy-weights: ("medium": 500, "bold": 700)
+
+ @debug map.merge($fonts, "Helvetica", "weights", $heavy-weights)
+ // (
+ // "Helvetica": (
+ // "weights": (
+ // "lightest": 100,
+ // "light": 300,
+ // "medium": 500,
+ // "bold": 700
+ // )
+ // )
+ // )
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'map.remove($map, $keys...)', 'map-remove($map, $keys...)', 'returns:map' %}
+ {% markdown %}
+ Returns a copy of `$map` without any values associated with `$keys`.
+
+ If a key in `$keys` doesn't have an associated value in `$map`, it's
+ ignored.
+ {% endmarkdown %}
+
+ {% codeExample 'map-remove', false %}
+ $font-weights: ("regular": 400, "medium": 500, "bold": 700);
+
+ @debug map.remove($font-weights, "regular"); // ("medium": 500, "bold": 700)
+ @debug map.remove($font-weights, "regular", "bold"); // ("medium": 500)
+ @debug map.remove($font-weights, "bolder");
+ // ("regular": 400, "medium": 500, "bold": 700)
+ ===
+ $font-weights: ("regular": 400, "medium": 500, "bold": 700)
+
+ @debug map.remove($font-weights, "regular") // ("medium": 500, "bold": 700)
+ @debug map.remove($font-weights, "regular", "bold") // ("medium": 500)
+ @debug map.remove($font-weights, "bolder")
+ // ("regular": 400, "medium": 500, "bold": 700)
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'map.set($map, $key, $value)', 'map.set($map, $keys..., $key, $value)', 'returns:map' %}
+ {% headsUp %}
+ In practice, the actual arguments to `map.set($map, $keys..., $key, $value)`
+ are passed as `map.set($map, $args...)`. They are described here as `$map,
+ $keys..., $key, $value` for explanation purposes only.
+ {% endheadsUp %}
+
+ {% markdown %}
+ If `$keys` are not passed, returns a copy of `$map` with the value at `$key`
+ set to `$value`.
+ {% endmarkdown %}
+
+ {% codeExample 'map-set', false %}
+ $font-weights: ("regular": 400, "medium": 500, "bold": 700);
+
+ @debug map.set($font-weights, "regular", 300);
+ // ("regular": 300, "medium": 500, "bold": 700)
+ ===
+ $font-weights: ("regular": 400, "medium": 500, "bold": 700)
+
+ @debug map.set($font-weights, "regular", 300)
+ // ("regular": 300, "medium": 500, "bold": 700)
+ {% endcodeExample %}
+
+ {{ '---' | markdown }}
+
+ {% compatibility 'dart: "1.27.0"', 'libsass: false', 'ruby: false' %}
+ Only Dart Sass supports calling `map.set()` with more than three arguments.
+ {% endcompatibility %}
+
+ {% markdown %}
+ If `$keys` are passed, follows the `$keys` to find the nested map targeted
+ for updating. If any key in `$keys` is missing from a map or references a
+ value that is not a map, sets the value at that key to an empty map.
+
+ Returns a copy of `$map` with the targeted map's value at `$key` set to
+ `$value`.
+ {% endmarkdown %}
+
+ {% codeExample 'map-set-2', false %}
+ $fonts: (
+ "Helvetica": (
+ "weights": (
+ "regular": 400,
+ "medium": 500,
+ "bold": 700
+ )
+ )
+ );
+
+ @debug map.set($fonts, "Helvetica", "weights", "regular", 300);
+ // (
+ // "Helvetica": (
+ // "weights": (
+ // "regular": 300,
+ // "medium": 500,
+ // "bold": 700
+ // )
+ // )
+ // )
+ ===
+ $fonts: ("Helvetica": ("weights": ("regular": 400, "medium": 500, "bold": 700)))
+
+ @debug map.set($fonts, "Helvetica", "weights", "regular", 300)
+ // (
+ // "Helvetica": (
+ // "weights": (
+ // "regular": 300,
+ // "medium": 500,
+ // "bold": 700
+ // )
+ // )
+ // )
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'map.values($map)', 'map-values($map)', 'returns:list' %}
+ {% markdown %}
+ Returns a comma-separated list of all the values in `$map`.
+ {% endmarkdown %}
+
+ {% codeExample 'map-values', false %}
+ $font-weights: ("regular": 400, "medium": 500, "bold": 700);
+
+ @debug map.values($font-weights); // 400, 500, 700
+ ===
+ $font-weights: ("regular": 400, "medium": 500, "bold": 700)
+
+ @debug map.values($font-weights) // 400, 500, 700
+ {% endcodeExample %}
+{% endfunction %}
diff --git a/source/documentation/modules/math.liquid b/source/documentation/modules/math.liquid
new file mode 100644
index 000000000..7556ffd7c
--- /dev/null
+++ b/source/documentation/modules/math.liquid
@@ -0,0 +1,658 @@
+---
+title: sass:math
+---
+
+{% render 'doc_snippets/built-in-module-status' %}
+
+{{ '## Variables' | markdown }}
+
+{% function 'math.$e' %}
+ {% compatibility 'dart: "1.25.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %}
+
+ {% markdown %}
+ The closest 64-bit floating point approximation of the [mathematical
+ constant *e*][].
+
+ [mathematical constant *e*]: https://en.wikipedia.org/wiki/E_(mathematical_constant)
+ {% endmarkdown %}
+
+ {% codeExample 'math-e', false %}
+ @debug math.$e; // 2.7182818285
+ ===
+ @debug math.$e // 2.7182818285
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'math.$epsilon' %}
+ {% compatibility 'dart: "1.55.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %}
+
+ {% markdown %}
+ The difference between 1 and the smallest 64-bit floating point number
+ greater than 1 according to floating-point comparisons. Because of Sass
+ numbers' [10 digits of precision](/documentation/values/numbers), in many
+ cases this will appear to be 0.
+ {% endmarkdown %}
+{% endfunction %}
+
+{% function 'math.$max-number' %}
+ {% compatibility 'dart: "1.55.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %}
+
+ {% markdown %}
+ The maximum finite number that can be represented as a 64-bit floating point
+ number.
+ {% endmarkdown %}
+
+ {% codeExample 'math-max-number', false %}
+ @debug math.$max-number; // 179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+ ===
+ @debug math.$max-number // 179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'math.$max-safe-integer' %}
+ {% compatibility 'dart: "1.55.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %}
+
+ {% markdown %}
+ The maximum integer `n` such that both `n` and `n + 1` can be precisely
+ represented as a 64-bit floating-point number.
+ {% endmarkdown %}
+
+ {% codeExample 'math-max-safe-integer', false %}
+ @debug math.$max-safe-integer; // 9007199254740991
+ ===
+ @debug math.$max-safe-integer // 9007199254740991
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'math.$min-number' %}
+ {% compatibility 'dart: "1.55.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %}
+
+ {% markdown %}
+ The smallest positive number that can be represented as a 64-bit floating
+ point number. Because of Sass numbers' [10 digits of
+ precision](/documentation/values/numbers), in many cases this will appear to
+ be 0.
+ {% endmarkdown %}
+{% endfunction %}
+
+{% function 'math.$min-safe-integer' %}
+ {% compatibility 'dart: "1.55.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %}
+
+ {% markdown %}
+ The minimum integer `n` such that both `n` and `n - 1` can be precisely
+ represented as a 64-bit floating-point number.
+ {% endmarkdown %}
+
+ {% codeExample 'math-min-safe-integer', false %}
+ @debug math.$min-safe-integer; // -9007199254740991
+ ===
+ @debug math.$min-safe-integer // -9007199254740991
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'math.$pi' %}
+ {% compatibility 'dart: "1.25.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %}
+
+ {% markdown %}
+ The closest 64-bit floating point approximation of the [mathematical
+ constant *π*][].
+
+ [mathematical constant *π*]: https://en.wikipedia.org/wiki/Pi
+ {% endmarkdown %}
+
+ {% codeExample 'math-pi', false %}
+ @debug math.$pi; // 3.1415926536
+ ===
+ @debug math.$pi // 3.1415926536
+ {% endcodeExample %}
+{% endfunction %}
+
+{{ '## Bounding Functions' | markdown }}
+
+{% function 'math.ceil($number)', 'ceil($number)', 'returns:number' %}
+ {% markdown %}
+ Rounds `$number` up to the next highest whole number.
+ {% endmarkdown %}
+
+ {% codeExample 'math-ceil', false %}
+ @debug math.ceil(4); // 4
+ @debug math.ceil(4.2); // 5
+ @debug math.ceil(4.9); // 5
+ ===
+ @debug math.ceil(4) // 4
+ @debug math.ceil(4.2) // 5
+ @debug math.ceil(4.9) // 5
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'math.clamp($min, $number, $max)', 'returns:number' %}
+ {% compatibility 'dart: "1.25.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %}
+
+ {% markdown %}
+ Restricts `$number` to the range between `$min` and `$max`. If `$number` is
+ less than `$min` this returns `$min`, and if it's greater than `$max` this
+ returns `$max`.
+
+ `$min`, `$number`, and `$max` must have compatible units, or all be
+ unitless.
+ {% endmarkdown %}
+
+ {% codeExample 'math-clamp', false %}
+ @debug math.clamp(-1, 0, 1); // 0
+ @debug math.clamp(1px, -1px, 10px); // 1px
+ @debug math.clamp(-1in, 1cm, 10mm); // 10mm
+ ===
+ @debug math.clamp(-1, 0, 1) // 0
+ @debug math.clamp(1px, -1px, 10px) // 1px
+ @debug math.clamp(-1in, 1cm, 10mm) // 10mm
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'math.floor($number)', 'floor($number)', 'returns:number' %}
+ {% markdown %}
+ Rounds `$number` down to the next lowest whole number.
+ {% endmarkdown %}
+
+ {% codeExample 'math-floor', false %}
+ @debug math.floor(4); // 4
+ @debug math.floor(4.2); // 4
+ @debug math.floor(4.9); // 4
+ ===
+ @debug math.floor(4) // 4
+ @debug math.floor(4.2) // 4
+ @debug math.floor(4.9) // 4
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'math.max($number...)', 'max($number...)', 'returns:number' %}
+ {% markdown %}
+ Returns the highest of one or more numbers.
+ {% endmarkdown %}
+
+ {% codeExample 'math-max', false %}
+ @debug math.max(1px, 4px); // 4px
+
+ $widths: 50px, 30px, 100px;
+ @debug math.max($widths...); // 100px
+ ===
+ @debug math.max(1px, 4px) // 4px
+
+ $widths: 50px, 30px, 100px
+ @debug math.max($widths...) // 100px
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'math.min($number...)', 'min($number...)', 'returns:number' %}
+ {% markdown %}
+ Returns the lowest of one or more numbers.
+ {% endmarkdown %}
+
+ {% codeExample 'math-min', false %}
+ @debug math.min(1px, 4px); // 1px
+
+ $widths: 50px, 30px, 100px;
+ @debug math.min($widths...); // 30px
+ ===
+ @debug math.min(1px, 4px) // 1px
+
+ $widths: 50px, 30px, 100px
+ @debug math.min($widths...) // 30px
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'math.round($number)', 'round($number)', 'returns:number' %}
+ {% markdown %}
+ Rounds `$number` to the nearest whole number.
+ {% endmarkdown %}
+
+ {% codeExample 'math-round', false %}
+ @debug math.round(4); // 4
+ @debug math.round(4.2); // 4
+ @debug math.round(4.9); // 5
+ ===
+ @debug math.round(4) // 4
+ @debug math.round(4.2) // 4
+ @debug math.round(4.9) // 5
+ {% endcodeExample %}
+{% endfunction %}
+
+{{ '## Distance Functions' | markdown }}
+
+{% function 'math.abs($number)', 'abs($number)', 'returns:number' %}
+ {% markdown %}
+ Returns the [absolute value][] of `$number`. If `$number` is negative, this
+ returns `-$number`, and if `$number` is positive, it returns `$number`
+ as-is.
+
+ [absolute value]: https://en.wikipedia.org/wiki/Absolute_value
+ {% endmarkdown %}
+
+ {% codeExample 'math-abs', false %}
+ @debug math.abs(10px); // 10px
+ @debug math.abs(-10px); // 10px
+ ===
+ @debug math.abs(10px) // 10px
+ @debug math.abs(-10px) // 10px
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'math.hypot($number...)', 'returns:number' %}
+ {% compatibility 'dart: "1.25.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %}
+
+ {% markdown %}
+ Returns the length of the *n*-dimensional [vector][] that has components
+ equal to each `$number`. For example, for three numbers *a*, *b*, and *c*,
+ this returns the square root of *a² + b² + c²*.
+
+ The numbers must either all have compatible units, or all be unitless. And
+ since the numbers' units may differ, the output takes the unit of the first
+ number.
+
+ [vector]: https://en.wikipedia.org/wiki/Euclidean_vector
+ {% endmarkdown %}
+
+ {% codeExample 'math-hypot', false %}
+ @debug math.hypot(3, 4); // 5
+
+ $lengths: 1in, 10cm, 50px;
+ @debug math.hypot($lengths...); // 4.0952775683in
+ ===
+ @debug math.hypot(3, 4) // 5
+
+ $lengths: 1in, 10cm, 50px
+ @debug math.hypot($lengths...) // 4.0952775683in
+ {% endcodeExample %}
+{% endfunction %}
+
+{{ '## Exponential Functions' | markdown }}
+
+{% function 'math.log($number, $base: null)', 'returns:number' %}
+ {% compatibility 'dart: "1.25.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %}
+
+ {% markdown %}
+ Returns the [logarithm][] of `$number` with respect to `$base`. If `$base`
+ is `null`, the [natural log][] is calculated.
+
+ `$number` and `$base` must be unitless.
+
+ [logarithm]: https://en.wikipedia.org/wiki/Logarithm
+ [natural log]: https://en.wikipedia.org/wiki/Natural_logarithm
+ {% endmarkdown %}
+
+ {% codeExample 'math-log', false %}
+ @debug math.log(10); // 2.302585093
+ @debug math.log(10, 10); // 1
+ ===
+ @debug math.log(10) // 2.302585093
+ @debug math.log(10, 10) // 1
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'math.pow($base, $exponent)', 'returns:number' %}
+ {% compatibility 'dart: "1.25.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %}
+
+ {% markdown %}
+ Raises `$base` [to the power of][] `$exponent`.
+
+ `$base` and `$exponent` must be unitless.
+
+ [to the power of]: https://en.wikipedia.org/wiki/Exponentiation
+ {% endmarkdown %}
+
+ {% codeExample 'math-pow', false %}
+ @debug math.pow(10, 2); // 100
+ @debug math.pow(100, math.div(1, 3)); // 4.6415888336
+ @debug math.pow(5, -2); // 0.04
+ ===
+ @debug math.pow(10, 2) // 100
+ @debug math.pow(100, math.div(1, 3)) // 4.6415888336
+ @debug math.pow(5, -2) // 0.04
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'math.sqrt($number)', 'returns:number' %}
+ {% compatibility 'dart: "1.25.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %}
+
+ {% markdown %}
+ Returns the [square root][] of `$number`.
+
+ `$number` must be unitless.
+
+ [square root]: https://en.wikipedia.org/wiki/Square_root
+ {% endmarkdown %}
+
+ {% codeExample 'math-sqrt', false %}
+ @debug math.sqrt(100); // 10
+ @debug math.sqrt(math.div(1, 3)); // 0.5773502692
+ @debug math.sqrt(-1); // NaN
+ ===
+ @debug math.sqrt(100) // 10
+ @debug math.sqrt(math.div(1, 3)) // 0.5773502692
+ @debug math.sqrt(-1) // NaN
+ {% endcodeExample %}
+{% endfunction %}
+
+{{ '## Trigonometric Functions' | markdown }}
+
+{% function 'math.cos($number)', 'returns:number' %}
+ {% compatibility 'dart: "1.25.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %}
+
+ {% markdown %}
+ Returns the [cosine][] of `$number`.
+
+ `$number` must be an angle (its units must be compatible with `deg`) or
+ unitless. If `$number` has no units, it is assumed to be in `rad`.
+
+ [cosine]: https://en.wikipedia.org/wiki/Trigonometric_functions#Right-angled_triangle_definitions
+ {% endmarkdown %}
+
+ {% codeExample 'math-cos', false %}
+ @debug math.cos(100deg); // -0.1736481777
+ @debug math.cos(1rad); // 0.5403023059
+ @debug math.cos(1); // 0.5403023059
+ ===
+ @debug math.cos(100deg) // -0.1736481777
+ @debug math.cos(1rad) // 0.5403023059
+ @debug math.cos(1) // 0.5403023059
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'math.sin($number)', 'returns:number' %}
+ {% compatibility 'dart: "1.25.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %}
+
+ {% markdown %}
+ Returns the [sine][] of `$number`.
+
+ `$number` must be an angle (its units must be compatible with `deg`) or
+ unitless. If `$number` has no units, it is assumed to be in `rad`.
+
+ [sine]: https://en.wikipedia.org/wiki/Trigonometric_functions#Right-angled_triangle_definitions
+ {% endmarkdown %}
+
+ {% codeExample 'math-sin', false %}
+ @debug math.sin(100deg); // 0.984807753
+ @debug math.sin(1rad); // 0.8414709848
+ @debug math.sin(1); // 0.8414709848
+ ===
+ @debug math.sin(100deg) // 0.984807753
+ @debug math.sin(1rad) // 0.8414709848
+ @debug math.sin(1) // 0.8414709848
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'math.tan($number)', 'returns:number' %}
+ {% compatibility 'dart: "1.25.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %}
+
+ {% markdown %}
+ Returns the [tangent][] of `$number`.
+
+ `$number` must be an angle (its units must be compatible with `deg`) or
+ unitless. If `$number` has no units, it is assumed to be in `rad`.
+
+ [tangent]: https://en.wikipedia.org/wiki/Trigonometric_functions#Right-angled_triangle_definitions
+ {% endmarkdown %}
+
+ {% codeExample 'math-tan', false %}
+ @debug math.tan(100deg); // -5.6712818196
+ @debug math.tan(1rad); // 1.5574077247
+ @debug math.tan(1); // 1.5574077247
+ ===
+ @debug math.tan(100deg) // -5.6712818196
+ @debug math.tan(1rad) // 1.5574077247
+ @debug math.tan(1) // 1.5574077247
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'math.acos($number)', 'returns:number' %}
+ {% compatibility 'dart: "1.25.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %}
+
+ {% markdown %}
+ Returns the [arccosine][] of `$number` in `deg`.
+
+ `$number` must be unitless.
+
+ [arccosine]: https://en.wikipedia.org/wiki/Inverse_trigonometric_functions#Basic_properties
+ {% endmarkdown %}
+
+ {% codeExample 'math-acos', false %}
+ @debug math.acos(0.5); // 60deg
+ @debug math.acos(2); // NaNdeg
+ ===
+ @debug math.acos(0.5) // 60deg
+ @debug math.acos(2) // NaNdeg
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'math.asin($number)', 'returns:number' %}
+ {% compatibility 'dart: "1.25.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %}
+
+ {% markdown %}
+ Returns the [arcsine][] of `$number` in `deg`.
+
+ `$number` must be unitless.
+
+ [arcsine]: https://en.wikipedia.org/wiki/Inverse_trigonometric_functions#Basic_properties
+ {% endmarkdown %}
+
+ {% codeExample 'math-asin', false %}
+ @debug math.asin(0.5); // 30deg
+ @debug math.asin(2); // NaNdeg
+ ===
+ @debug math.asin(0.5) // 30deg
+ @debug math.asin(2) // NaNdeg
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'math.atan($number)', 'returns:number' %}
+ {% compatibility 'dart: "1.25.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %}
+
+ {% markdown %}
+ Returns the [arctangent][] of `$number` in `deg`.
+
+ `$number` must be unitless.
+
+ [arctangent]: https://en.wikipedia.org/wiki/Inverse_trigonometric_functions#Basic_properties
+ {% endmarkdown %}
+
+ {% codeExample 'math-atan', false %}
+ @debug math.atan(10); // 84.2894068625deg
+ ===
+ @debug math.atan(10) // 84.2894068625deg
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'math.atan2($y, $x)', 'returns:number' %}
+ {% compatibility 'dart: "1.25.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %}
+
+ {% markdown %}
+ Returns the [2-argument arctangent][] of `$y` and `$x` in `deg`.
+
+ `$y` and `$x` must have compatible units or be unitless.
+
+ [2-argument arctangent]: https://en.wikipedia.org/wiki/Atan2
+ {% endmarkdown %}
+
+ {% funFact %}
+ `math.atan2($y, $x)` is distinct from `atan(math.div($y, $x))` because it
+ preserves the quadrant of the point in question. For example, `math.atan2(1,
+ -1)` corresponds to the point `(-1, 1)` and returns `135deg`. In contrast,
+ `math.atan(math.div(1, -1))` and `math.atan(math.div(-1, 1))` resolve first
+ to `atan(-1)`, so both return `-45deg`.
+ {% endfunFact %}
+
+ {% codeExample 'math-atan2', false %}
+ @debug math.atan2(-1, 1); // 135deg
+ ===
+ @debug math.atan2(-1, 1) // 135deg
+ {% endcodeExample %}
+{% endfunction %}
+
+{{ '## Unit Functions' | markdown }}
+
+{% function 'math.compatible($number1, $number2)', 'comparable($number1, $number2)', 'returns:boolean' %}
+ {% markdown %}
+ Returns whether `$number1` and `$number2` have compatible units.
+
+ If this returns `true`, `$number1` and `$number2` can safely be [added][],
+ [subtracted][], and [compared][]. Otherwise, doing so will produce errors.
+
+ [added]: /documentation/operators/numeric
+ [subtracted]: /documentation/operators/numeric
+ [compared]: /documentation/operators/relational
+ {% endmarkdown %}
+
+ {% headsUp %}
+ The global name of this function is
+ comparable
, but when it was added to the
+ `sass:math` module the name was changed to
+ compatible
to more clearly convey what the
+ function does.
+ {% endheadsUp %}
+
+ {% codeExample 'math-compatible', false %}
+ @debug math.compatible(2px, 1px); // true
+ @debug math.compatible(100px, 3em); // false
+ @debug math.compatible(10cm, 3mm); // true
+ ===
+ @debug math.compatible(2px, 1px) // true
+ @debug math.compatible(100px, 3em) // false
+ @debug math.compatible(10cm, 3mm) // true
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'math.is-unitless($number)', 'unitless($number)', 'returns:boolean' %}
+ {% markdown %}
+ Returns whether `$number` has no units.
+ {% endmarkdown %}
+
+ {% codeExample 'math-is-unitless', false %}
+ @debug math.is-unitless(100); // true
+ @debug math.is-unitless(100px); // false
+ ===
+ @debug math.is-unitless(100) // true
+ @debug math.is-unitless(100px) // false
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'math.unit($number)', 'unit($number)', 'returns:quoted string' %}
+ {% markdown %}
+ Returns a string representation of `$number`'s units.
+ {% endmarkdown %}
+
+ {% headsUp %}
+ This function is intended for debugging; its output format is not guaranteed
+ to be consistent across Sass versions or implementations.
+ {% endheadsUp %}
+
+ {% codeExample 'math-unitless', false %}
+ @debug math.unit(100); // ""
+ @debug math.unit(100px); // "px"
+ @debug math.unit(5px * 10px); // "px*px"
+ @debug math.unit(math.div(5px, 1s)); // "px/s"
+ ===
+ @debug math.unit(100) // ""
+ @debug math.unit(100px) // "px"
+ @debug math.unit(5px * 10px) // "px*px"
+ @debug math.unit(math.div(5px, 1s)) // "px/s"
+ {% endcodeExample %}
+{% endfunction %}
+
+{{ '## Other Functions' | markdown }}
+
+{% function 'math.div($number1, $number2)', 'returns:number' %}
+ {% compatibility 'dart: "1.33.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %}
+
+ {% markdown %}
+ Returns the result of dividing `$number1` by `$number2`.
+
+ Any units shared by both numbers will be canceled out. Units in `$number1`
+ that aren't in `$number2` will end up in the return value's numerator, and
+ units in `$number2` that aren't in `$number1` will end up in its
+ denominator.
+ {% endmarkdown %}
+
+ {% headsUp %}
+ For backwards-compatibility purposes, this returns the *exact same result*
+ as [the deprecated `/` operator], including concatenating two strings with a
+ `/` character between them. However, this behavior will be removed
+ eventually and shouldn't be used in new stylesheets.
+
+ [the deprecated `/` operator]: /documentation/breaking-changes/slash-div
+ {% endheadsUp %}
+
+ {% codeExample 'math-div', false %}
+ @debug math.div(1, 2); // 0.5
+ @debug math.div(100px, 5px); // 20
+ @debug math.div(100px, 5); // 20px
+ @debug math.div(100px, 5s); // 20px/s
+ ===
+ @debug math.div(1, 2) // 0.5
+ @debug math.div(100px, 5px) // 20
+ @debug math.div(100px, 5) // 20px
+ @debug math.div(100px, 5s) // 20px/s
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'math.percentage($number)', 'percentage($number)', 'returns:number' %}
+ {% markdown %}
+ Converts a unitless `$number` (usually a decimal between 0 and 1) to a
+ percentage.
+ {% endmarkdown %}
+
+ {% funFact %}
+ This function is identical to `$number * 100%`.
+ {% endfunFact %}
+
+ {% codeExample 'math-percentage', false %}
+ @debug math.percentage(0.2); // 20%
+ @debug math.percentage(math.div(100px, 50px)); // 200%
+ ===
+ @debug math.percentage(0.2) // 20%
+ @debug math.percentage(math.div(100px, 50px)) // 200%
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'math.random($limit: null)', 'random($limit: null)', 'returns:number' %}
+ {% markdown %}
+ If `$limit` is `null`, returns a random decimal number between 0 and 1.
+ {% endmarkdown %}
+
+ {% codeExample 'math-random', false %}
+ @debug math.random(); // 0.2821251858
+ @debug math.random(); // 0.6221325814
+ ===
+ @debug math.random() // 0.2821251858
+ @debug math.random() // 0.6221325814
+ {% endcodeExample %}
+
+ {% markdown %}
+ * * *
+
+ If `$limit` is a number greater than or equal to 1, returns a random whole
+ number between 1 and `$limit`.
+ {% endmarkdown %}
+
+ {% headsUp false %}
+ {% markdown %}
+ `random()` ignores units in `$limit`. [This behavior is deprecated] and
+ `random($limit)` will return a random integer with the same units as the
+ `$limit` argument.
+
+ [This behavior is deprecated]: /documentation/breaking-changes/random-with-units
+ {% endmarkdown %}
+
+ {% codeExample 'math-random-warning', false %}
+ @debug math.random(100px); // 42
+ ===
+ @debug math.random(100px) // 42
+ {% endcodeExample %}
+ {% endheadsUp %}
+
+ {% codeExample 'math-random-limit', false %}
+ @debug math.random(10); // 4
+ @debug math.random(10000); // 5373
+ ===
+ @debug math.random(10) // 4
+ @debug math.random(10000) // 5373
+ {% endcodeExample %}
+{% endfunction %}
diff --git a/source/documentation/modules/meta.liquid b/source/documentation/modules/meta.liquid
new file mode 100644
index 000000000..86b7436de
--- /dev/null
+++ b/source/documentation/modules/meta.liquid
@@ -0,0 +1,572 @@
+---
+title: sass:meta
+---
+
+{% render 'doc_snippets/built-in-module-status' %}
+
+{{ '## Mixins' | markdown }}
+
+{% function 'meta.load-css($url, $with: null)' %}
+ {% compatibility 'dart: "1.23.0"', 'libsass: false', 'ruby: false' %}
+ Only Dart Sass currently supports this mixin.
+ {% endcompatibility %}
+
+ {% markdown %}
+ Loads the [module][] at `$url` and includes its CSS as though it were
+ written as the contents of this mixin. The `$with` parameter provides
+ [configuration][] for the modules; if it's passed, it must be a map from
+ variable names (without `$`) to the values of those variables to use in the
+ loaded module.
+
+ [module]: /documentation/at-rules/use
+ [configuration]: /documentation/at-rules/use#configuration
+
+ If `$url` is relative, it's interpreted as relative to the file in which
+ `meta.load-css()` is included.
+
+ **Like the [`@use` rule][]**:
+
+ [`@use` rule]: /documentation/at-rules/use
+
+ * This will only evaluate the given module once, even if it's loaded
+ multiple times in different ways.
+
+ * This cannot provide configuration to a module that's already been loaded,
+ whether or not it was already loaded with configuration.
+
+ **Unlike the [`@use` rule][]**:
+
+ * This doesn't make any members from the loaded module available in the
+ current module.
+
+ * This can be used anywhere in a stylesheet. It can even be nested within
+ style rules to create nested styles!
+
+ * The module URL being loaded can come from a variable and include
+ [interpolation][].
+
+ [interpolation]: /documentation/interpolation
+ {% endmarkdown %}
+
+ {% headsUp %}
+ The `$url` parameter should be a string containing a URL like you'd pass to
+ the `@use` rule. It shouldn't be a CSS `url()`!
+ {% endheadsUp %}
+
+ {% codeExample 'load-css', false %}
+ // dark-theme/_code.scss
+ $border-contrast: false !default;
+
+ code {
+ background-color: #6b717f;
+ color: #d2e1dd;
+ @if $border-contrast {
+ border-color: #dadbdf;
+ }
+ }
+ ---
+ // style.scss
+ @use "sass:meta";
+
+ body.dark {
+ @include meta.load-css("dark-theme/code",
+ $with: ("border-contrast": true));
+ }
+ ===
+ // dark-theme/_code.sass
+ $border-contrast: false !default
+
+ code
+ background-color: #6b717f
+ color: #d2e1dd
+ @if $border-contrast
+ border-color: #dadbdf
+ ---
+ // style.sass
+ @use "sass:meta"
+
+ body.dark
+ $configuration: ("border-contrast": true)
+ @include meta.load-css("dark-theme/code", $with: $configuration)
+ ===
+ body.dark code {
+ background-color: #6b717f;
+ color: #d2e1dd;
+ border-color: #dadbdf;
+ }
+ {% endcodeExample %}
+{% endfunction %}
+
+{{ '## Functions' | markdown }}
+
+{% function 'meta.calc-args($calc)', 'returns:list' %}
+ {% compatibility 'dart: "1.40.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %}
+
+ {% markdown %}
+ Returns the arguments for the given [calculation].
+
+ [calculation]: /documentation/values/calculations
+
+ If an argument is a number or a nested calculation, it's returned as that
+ type. Otherwise, it's returned as an unquoted string.
+ {% endmarkdown %}
+
+ {% codeExample 'calc-args', false %}
+ @debug meta.calc-args(calc(100px + 10%)); // unquote("100px + 10%")
+ @debug meta.calc-args(clamp(50px, var(--width), 1000px)); // 50px, unquote("var(--width)"), 1000px
+ ===
+ @debug meta.calc-args(calc(100px + 10%)) // unquote("100px + 10%")
+ @debug meta.calc-args(clamp(50px, var(--width), 1000px)) // 50px, unquote("var(--width)"), 1000px
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'meta.calc-name($calc)', 'returns:quoted string' %}
+ {% compatibility 'dart: "1.40.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %}
+
+ {% markdown %}
+ Returns the name of the given [calculation].
+
+ [calculation]: /documentation/values/calculations
+ {% endmarkdown %}
+
+ {% codeExample 'calc-name', false %}
+ @debug meta.calc-name(calc(100px + 10%)); // "calc"
+ @debug meta.calc-name(clamp(50px, var(--width), 1000px)); // "clamp"
+ ===
+ @debug meta.calc-name(calc(100px + 10%)) // "calc"
+ @debug meta.calc-name(clamp(50px, var(--width), 1000px)) // "clamp"
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'meta.call($function, $args...)', 'call($function, $args...)' %}
+ {% render 'doc_snippets/call-impl-status' %}
+
+ {% markdown %}
+ Invokes `$function` with `$args` and returns the result.
+
+ The `$function` should be a [function][] returned by
+ [`meta.get-function()`][].
+
+ [function]: /documentation/values/functions
+ [`meta.get-function()`]: #get-function
+ {% endmarkdown %}
+
+ {% render 'code_snippets/example-first-class-function' %}
+{% endfunction %}
+
+{% function 'meta.content-exists()', 'content-exists()', 'returns:boolean' %}
+ {% markdown %}
+ Returns whether the current mixin was passed a [`@content` block][].
+
+ [`@content` block]: /documentation/at-rules/mixin#content-blocks
+
+ Throws an error if called outside of a mixin.
+ {% endmarkdown %}
+
+ {% codeExample 'content-exists', false %}
+ @mixin debug-content-exists {
+ @debug meta.content-exists();
+ @content;
+ }
+
+ @include debug-content-exists; // false
+ @include debug-content-exists { // true
+ // Content!
+ }
+ ===
+ @mixin debug-content-exists
+ @debug meta.content-exists()
+ @content
+
+
+ @include debug-content-exists // false
+ @include debug-content-exists // true
+ // Content!
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'meta.feature-exists($feature)', 'feature-exists($feature)', 'returns:boolean' %}
+ {% markdown %}
+ Returns whether the current Sass implementation supports `$feature`.
+
+ The `$feature` must be a string. The currently recognized features are:
+
+ * `global-variable-shadowing`, which means that a local variable will
+ [shadow][] a global variable unless it has the `!global` flag.
+ * `extend-selector-pseudoclass`, which means that the [`@extend` rule][]
+ will affect selectors nested in pseudo-classes like `:not()`.
+ * `units-level3`, which means that [unit arithmetic][] supports units
+ defined in [CSS Values and Units Level 3][].
+ * `at-error`, which means that the [`@error` rule][] is supported.
+ * `custom-property`, which means that [custom property declaration][] values
+ don't support any [expressions][] other than [interpolation][].
+
+ [shadow]: /documentation/variables#shadowing
+ [`@extend` rule]: /documentation/at-rules/extend
+ [unit arithmetic]: /documentation/values/numbers#units
+ [CSS Values and Units Level 3]: http://www.w3.org/TR/css3-values
+ [`@error` rule]: /documentation/at-rules/error
+ [custom property declaration]: /documentation/style-rules/declarations#custom-properties
+ [expressions]: /documentation/syntax/structure#expressions
+ [interpolation]: /documentation/interpolation
+
+ Returns `false` for any unrecognized `$feature`.
+ {% endmarkdown %}
+
+ {% codeExample 'feature-exists', false %}
+ @debug meta.feature-exists("at-error"); // true
+ @debug meta.feature-exists("unrecognized"); // false
+ ===
+ @debug meta.feature-exists("at-error") // true
+ @debug meta.feature-exists("unrecognized") // false
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'meta.function-exists($name, $module: null)', 'function-exists($name)', 'returns:boolean' %}
+ {% markdown %}
+ Returns whether a function named `$name` is defined, either as a built-in
+ function or a user-defined function.
+
+ If `$module` is passed, this also checks the module named `$module` for the
+ function definition. `$module` must be a string matching the namespace of a
+ [`@use` rule][] in the current file.
+
+ [`@use` rule]: /documentation/at-rules/use
+ {% endmarkdown %}
+
+ {% codeExample 'function-exists', false %}
+ @use "sass:math";
+
+ @debug meta.function-exists("div", "math"); // true
+ @debug meta.function-exists("scale-color"); // true
+ @debug meta.function-exists("add"); // false
+
+ @function add($num1, $num2) {
+ @return $num1 + $num2;
+ }
+ @debug meta.function-exists("add"); // true
+ ===
+ @use "sass:math"
+
+ @debug meta.function-exists("div", "math") // true
+ @debug meta.function-exists("scale-color") // true
+ @debug meta.function-exists("add") // false
+
+ @function add($num1, $num2)
+ @return $num1 + $num2
+
+ @debug meta.function-exists("add") // true
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'meta.get-function($name, $css: false, $module: null)', 'get-function($name, $css: false, $module: null)', 'returns:function' %}
+ {% markdown %}
+ Returns the [function][] named `$name`.
+
+ [function]: /documentation/values/functions
+
+ If `$module` is `null`, this returns the function named `$name` without a
+ namespace (including [global built-in functions][]). Otherwise, `$module`
+ must be a string matching the namespace of a [`@use` rule][] in the current
+ file, in which case this returns the function in that module named `$name`.
+
+ [global built-in functions]: /documentation/modules#global-functions
+ [`@use` rule]: /documentation/at-rules/use
+
+ By default, this throws an error if `$name` doesn't refer to Sass function.
+ However, if `$css` is `true`, it instead returns a [plain CSS function][].
+
+ [user-defined]: /documentation/at-rules/function
+ [plain CSS function]: /documentation/at-rules/function#plain-css-functions
+
+ The returned function can be called using [`meta.call()`](#call).
+ {% endmarkdown %}
+
+ {% render 'code_snippets/example-first-class-function' %}
+{% endfunction %}
+
+{% function 'meta.global-variable-exists($name, $module: null)', 'global-variable-exists($name, $module: null)', 'returns:boolean' %}
+ {% markdown %}
+ Returns whether a [global variable][] named `$name` (without the `$`)
+ exists.
+
+ [global variable]: /documentation/variables#scope
+
+ If `$module` is `null`, this returns whether a variable named `$name`
+ without a namespace exists. Otherwise, `$module` must be a string matching
+ the namespace of a [`@use` rule][] in the current file, in which case this
+ returns whether that module has a variable named `$name`.
+
+ [`@use` rule]: /documentation/at-rules/use
+
+ See also [`meta.variable-exists()`](#variable-exists).
+ {% endmarkdown %}
+
+ {% codeExample 'global-variable-exists', false %}
+ @debug meta.global-variable-exists("var1"); // false
+
+ $var1: value;
+ @debug meta.global-variable-exists("var1"); // true
+
+ h1 {
+ // $var2 is local.
+ $var2: value;
+ @debug meta.global-variable-exists("var2"); // false
+ }
+ ===
+ @debug meta.global-variable-exists("var1") // false
+
+ $var1: value
+ @debug meta.global-variable-exists("var1") // true
+
+ h1
+ // $var2 is local.
+ $var2: value
+ @debug meta.global-variable-exists("var2") // false
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'meta.inspect($value)', 'inspect($value)', 'returns:unquoted string' %}
+ {% markdown %}
+ Returns a string representation of `$value`.
+
+ Returns a representation of *any* Sass value, not just those that can be
+ represented in CSS. As such, its return value is not guaranteed to be valid
+ CSS.
+ {% endmarkdown %}
+
+ {% headsUp %}
+ This function is intended for debugging; its output format is not guaranteed
+ to be consistent across Sass versions or implementations.
+ {% endheadsUp %}
+
+ {% codeExample 'inspect', false %}
+ @debug meta.inspect(10px 20px 30px); // unquote("10px 20px 30px")
+ @debug meta.inspect(("width": 200px)); // unquote('("width": 200px)')
+ @debug meta.inspect(null); // unquote("null")
+ @debug meta.inspect("Helvetica"); // unquote('"Helvetica"')
+ ===
+ @debug meta.inspect(10px 20px 30px) // unquote("10px 20px 30px")
+ @debug meta.inspect(("width": 200px)) // unquote('("width": 200px)')
+ @debug meta.inspect(null) // unquote("null")
+ @debug meta.inspect("Helvetica") // unquote('"Helvetica"')
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'meta.keywords($args)', 'keywords($args)', 'returns:map' %}
+ {% markdown %}
+ Returns the keywords passed to a mixin or function that takes [arbitrary
+ arguments][]. The `$args` argument must be an [argument list][].
+
+ [arbitrary arguments]: /documentation/at-rules/mixin#taking-arbitrary-arguments
+ [argument list]: /documentation/values/lists#argument-lists
+
+ The keywords are returned as a map from argument names as unquoted strings
+ (not including `$`) to the values of those arguments.
+ {% endmarkdown %}
+
+ {% render 'code_snippets/example-mixin-arbitrary-keyword-arguments' %}
+{% endfunction %}
+
+{% function 'meta.mixin-exists($name, $module: null)', 'mixin-exists($name, $module: null)', 'returns:boolean' %}
+ {% markdown %}
+ Returns whether a [mixin][] named `$name` exists.
+
+ [mixin]: /documentation/at-rules/mixin
+
+ If `$module` is `null`, this returns whether a mixin named `$name` without a
+ namespace exists. Otherwise, `$module` must be a string matching the
+ namespace of a [`@use` rule][] in the current file, in which case this
+ returns whether that module has a mixin named `$name`.
+
+ [`@use` rule]: /documentation/at-rules/use
+ {% endmarkdown %}
+
+ {% codeExample 'mixin-exists', false %}
+ @debug meta.mixin-exists("shadow-none"); // false
+
+ @mixin shadow-none {
+ box-shadow: none;
+ }
+
+ @debug meta.mixin-exists("shadow-none"); // true
+ ===
+ @debug meta.mixin-exists("shadow-none") // false
+
+ @mixin shadow-none
+ box-shadow: none
+
+
+ @debug meta.mixin-exists("shadow-none") // true
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'meta.module-functions($module)', 'returns:map' %}
+ {% render 'doc_snippets/module-system-function-status' %}
+
+ {% markdown %}
+ Returns all the functions defined in a module, as a map from function names
+ to [function values][].
+
+ [function values]: /documentation/values/functions
+
+ The `$module` parameter must be a string matching the namespace of a [`@use`
+ rule][] in the current file.
+
+ [`@use` rule]: /documentation/at-rules/use
+ {% endmarkdown %}
+
+ {% codeExample 'module-functions', false %}
+ // _functions.scss
+ @function pow($base, $exponent) {
+ $result: 1;
+ @for $_ from 1 through $exponent {
+ $result: $result * $base;
+ }
+ @return $result;
+ }
+ ---
+ @use "sass:map";
+ @use "sass:meta";
+
+ @use "functions";
+
+ @debug meta.module-functions("functions"); // ("pow": get-function("pow"))
+
+ @debug meta.call(map.get(meta.module-functions("functions"), "pow"), 3, 4); // 81
+ ===
+ // _functions.sass
+ @function pow($base, $exponent)
+ $result: 1
+ @for $_ from 1 through $exponent
+ $result: $result * $base
+
+ @return $result
+ ---
+ @use "sass:map"
+ @use "sass:meta"
+
+ @use "functions"
+
+ @debug meta.module-functions("functions") // ("pow": get-function("pow"))
+
+ @debug meta.call(map.get(meta.module-functions("functions"), "pow"), 3, 4) // 81
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'meta.module-variables($module)', 'returns:map' %}
+ {% render 'doc_snippets/module-system-function-status' %}
+
+ {% markdown %}
+ Returns all the variables defined in a module, as a map from variable names
+ (without `$`) to the values of those variables.
+
+ The `$module` parameter must be a string matching the namespace of a [`@use`
+ rule][] in the current file.
+
+ [`@use` rule]: /documentation/at-rules/use
+ {% endmarkdown %}
+
+ {% codeExample 'module-variables', false %}
+ // _variables.scss
+ $hopbush: #c69;
+ $midnight-blue: #036;
+ $wafer: #e1d7d2;
+ ---
+ @use "sass:meta";
+
+ @use "variables";
+
+ @debug meta.module-variables("variables");
+ // (
+ // "hopbush": #c69,
+ // "midnight-blue": #036,
+ // "wafer": #e1d7d2
+ // )
+ ===
+ // _variables.sass
+ $hopbush: #c69
+ $midnight-blue: #036
+ $wafer: #e1d7d2
+ ---
+ @use "sass:meta"
+
+ @use "variables"
+
+ @debug meta.module-variables("variables")
+ // (
+ // "hopbush": #c69,
+ // "midnight-blue": #036,
+ // "wafer": #e1d7d2
+ // )
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'meta.type-of($value)', 'type-of($value)', 'returns:unquoted string' %}
+ {% markdown %}
+ Returns the type of `$value`.
+
+ This can return the following values:
+
+ * [`number`](/documentation/values/numbers)
+ * [`string`](/documentation/values/strings)
+ * [`color`](/documentation/values/colors)
+ * [`list`](/documentation/values/lists)
+ * [`map`](/documentation/values/maps)
+ * [`calculation`](/documentation/values/calculations)
+ * [`bool`](/documentation/values/booleans)
+ * [`null`](/documentation/values/null)
+ * [`function`](/documentation/values/functions)
+ * [`arglist`](/documentation/values/lists#argument-lists)
+
+ New possible values may be added in the future. It may return either `list`
+ or `map` for `()`, depending on whether or not it was returned by a [map
+ function][].
+
+ [map function]: /documentation/modules/map
+ {% endmarkdown %}
+
+ {% codeExample 'type-of', false %}
+ @debug meta.type-of(10px); // number
+ @debug meta.type-of(10px 20px 30px); // list
+ @debug meta.type-of(()); // list
+ ===
+ @debug meta.type-of(10px) // number
+ @debug meta.type-of(10px 20px 30px) // list
+ @debug meta.type-of(()) // list
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'meta.variable-exists($name)', 'variable-exists($name)', 'returns:boolean' %}
+ {% markdown %}
+ Returns whether a variable named `$name` (without the `$`) exists in the
+ current [scope][].
+
+ [scope]: /documentation/variables#scope
+
+ See also [`meta.global-variable-exists()`](#global-variable-exists).
+ {% endmarkdown %}
+
+ {% codeExample 'variable-exists', false %}
+ @debug meta.variable-exists("var1"); // false
+
+ $var1: value;
+ @debug meta.variable-exists("var1"); // true
+
+ h1 {
+ // $var2 is local.
+ $var2: value;
+ @debug meta.variable-exists("var2"); // true
+ }
+ ===
+ @debug meta.variable-exists("var1") // false
+
+ $var1: value
+ @debug meta.variable-exists("var1") // true
+
+ h1
+ // $var2 is local.
+ $var2: value
+ @debug meta.variable-exists("var2") // true
+ {% endcodeExample %}
+{% endfunction %}
diff --git a/source/documentation/modules/selector.liquid b/source/documentation/modules/selector.liquid
new file mode 100644
index 000000000..abd5df783
--- /dev/null
+++ b/source/documentation/modules/selector.liquid
@@ -0,0 +1,255 @@
+---
+title: sass:selector
+---
+
+{% render 'doc_snippets/built-in-module-status' %}
+
+{% markdown %}
+ ## Selector Values
+
+ The functions in this module inspect and manipulate selectors. Whenever they
+ return a selector, it's always a comma-separated [list][] (the selector list)
+ that contains space-separated lists (the complex selectors) that contain
+ [unquoted strings][] (the compound selectors). For example, the selector
+ `.main aside:hover, .sidebar p` would be returned as:
+
+ [list]: /documentation/values/lists
+ [unquoted strings]: /documentation/values/strings#unquoted
+
+ ```scss
+ @debug ((unquote(".main") unquote("aside:hover")),
+ (unquote(".sidebar") unquote("p")));
+ // .main aside:hover, .sidebar p
+ ```
+
+ Selector arguments to these functions may be in the same format, but they can
+ also just be normal strings (quoted or unquoted), or a combination. For
+ example, `".main aside:hover, .sidebar p"` is a valid selector argument.
+{% endmarkdown %}
+
+{% function 'selector.is-superselector($super, $sub)', 'is-superselector($super, $sub)', 'returns:boolean' %}
+ {% markdown %}
+ Returns whether the selector `$super` matches all the elements that the
+ selector `$sub` matches.
+
+ Still returns true even if `$super` matches *more* elements than `$sub`.
+
+ The `$super` and `$sub` selectors may contain [placeholder selectors][], but
+ not [parent selectors][].
+
+ [placeholder selectors]: /documentation/style-rules/placeholder-selectors
+ [parent selectors]: /documentation/style-rules/parent-selector
+ {% endmarkdown %}
+
+ {% codeExample 'is-superselector', false %}
+ @debug selector.is-superselector("a", "a.disabled"); // true
+ @debug selector.is-superselector("a.disabled", "a"); // false
+ @debug selector.is-superselector("a", "sidebar a"); // true
+ @debug selector.is-superselector("sidebar a", "a"); // false
+ @debug selector.is-superselector("a", "a"); // true
+ ===
+ @debug selector.is-superselector("a", "a.disabled") // true
+ @debug selector.is-superselector("a.disabled", "a") // false
+ @debug selector.is-superselector("a", "sidebar a") // true
+ @debug selector.is-superselector("sidebar a", "a") // false
+ @debug selector.is-superselector("a", "a") // true
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'selector.append($selectors...)', 'selector-append($selectors...)', 'returns:selector' %}
+ {% markdown %}
+ Combines `$selectors` without [descendant combinators][]—that is, without
+ whitespace between them.
+
+ [descendant combinators]: https://developer.mozilla.org/en-US/docs/Web/CSS/Descendant_selectors
+
+ If any selector in `$selectors` is a selector list, each complex selector is
+ combined separately.
+
+ The `$selectors` may contain [placeholder selectors][], but not [parent
+ selectors][].
+
+ [placeholder selectors]: /documentation/style-rules/placeholder-selectors
+ [parent selectors]: /documentation/style-rules/parent-selector
+
+ See also [`selector.nest()`](#nest).
+ {% endmarkdown %}
+
+ {% codeExample 'append', false %}
+ @debug selector.append("a", ".disabled"); // a.disabled
+ @debug selector.append(".accordion", "__copy"); // .accordion__copy
+ @debug selector.append(".accordion", "__copy, __image");
+ // .accordion__copy, .accordion__image
+ ===
+ @debug selector.append("a", ".disabled") // a.disabled
+ @debug selector.append(".accordion", "__copy") // .accordion__copy
+ @debug selector.append(".accordion", "__copy, __image")
+ // .accordion__copy, .accordion__image
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'selector.extend($selector, $extendee, $extender)', 'selector-extend($selector, $extendee, $extender)', 'returns:selector' %}
+ {% markdown %}
+ Extends `$selector` as with the [`@extend` rule][].
+
+ [`@extend` rule]: /documentation/at-rules/extend
+
+ Returns a copy of `$selector` modified with the following `@extend` rule:
+
+ ```scss
+ #{$extender} {
+ @extend #{$extendee};
+ }
+ ```
+
+ In other words, replaces all instances of `$extendee` in `$selector` with
+ `$extendee, $extender`. If `$selector` doesn't contain `$extendee`, returns
+ it as-is.
+
+ The `$selector`, `$extendee`, and `$extender` selectors may contain
+ [placeholder selectors][], but not [parent selectors][].
+
+ [placeholder selectors]: /documentation/style-rules/placeholder-selectors
+ [parent selectors]: /documentation/style-rules/parent-selector
+
+ See also [`selector.replace()`](#replace).
+ {% endmarkdown %}
+
+ {% codeExample 'extend', false %}
+ @debug selector.extend("a.disabled", "a", ".link"); // a.disabled, .link.disabled
+ @debug selector.extend("a.disabled", "h1", "h2"); // a.disabled
+ @debug selector.extend(".guide .info", ".info", ".content nav.sidebar");
+ // .guide .info, .guide .content nav.sidebar, .content .guide nav.sidebar
+ ===
+ @debug selector.extend("a.disabled", "a", ".link") // a.disabled, .link.disabled
+ @debug selector.extend("a.disabled", "h1", "h2") // a.disabled
+ @debug selector.extend(".guide .info", ".info", ".content nav.sidebar")
+ // .guide .info, .guide .content nav.sidebar, .content .guide nav.sidebar
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'selector.nest($selectors...)', 'selector-nest($selectors...)', 'returns:selector' %}
+ {% markdown %}
+ Combines `$selectors` as though they were nested within one another in the
+ stylesheet.
+
+ The `$selectors` may contain [placeholder selectors][]. Unlike other
+ selector functions, all of them except the first may also contain [parent
+ selectors][].
+
+ [placeholder selectors]: /documentation/style-rules/placeholder-selectors
+ [parent selectors]: /documentation/style-rules/parent-selector
+
+ See also [`selector.append()`](#append).
+ {% endmarkdown %}
+
+ {% codeExample 'nest', false %}
+ @debug selector.nest("ul", "li"); // ul li
+ @debug selector.nest(".alert, .warning", "p"); // .alert p, .warning p
+ @debug selector.nest(".alert", "&:hover"); // .alert:hover
+ @debug selector.nest(".accordion", "&__copy"); // .accordion__copy
+ ===
+ @debug selector.nest("ul", "li") // ul li
+ @debug selector.nest(".alert, .warning", "p") // .alert p, .warning p
+ @debug selector.nest(".alert", "&:hover") // .alert:hover
+ @debug selector.nest(".accordion", "&__copy") // .accordion__copy
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'selector.parse($selector)', 'selector-parse($selector)', 'returns:selector' %}
+ {% markdown %}
+ Returns `$selector` in the [selector value](#selector-values) format.
+ {% endmarkdown %}
+
+ {% codeExample 'parse', false %}
+ @debug selector.parse(".main aside:hover, .sidebar p");
+ // ((unquote(".main") unquote("aside:hover")),
+ // (unquote(".sidebar") unquote("p")))
+ ===
+ @debug selector.parse(".main aside:hover, .sidebar p")
+ // ((unquote(".main") unquote("aside:hover")),
+ // (unquote(".sidebar") unquote("p")))
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'selector.replace($selector, $original, $replacement)', 'selector-replace($selector, $original, $replacement)', 'returns:selector' %}
+ {% markdown %}
+ Returns a copy of `$selector` with all instances of `$original` replaced by
+ `$replacement`.
+
+ This uses the [`@extend` rule][]'s [intelligent unification][] to make sure
+ `$replacement` is seamlessly integrated into `$selector`. If `$selector`
+ doesn't contain `$original`, returns it as-is.
+
+ [`@extend` rule]: /documentation/at-rules/extend
+ [intelligent unification]: /documentation/at-rules/extend#how-it-works
+
+ The `$selector`, `$original`, and `$replacement` selectors may contain
+ [placeholder selectors][], but not [parent selectors][].
+
+ [placeholder selectors]: /documentation/style-rules/placeholder-selectors
+ [parent selectors]: /documentation/style-rules/parent-selector
+
+ See also [`selector.extend()`](#extend).
+ {% endmarkdown %}
+
+ {% codeExample 'replace', false %}
+ @debug selector.replace("a.disabled", "a", ".link"); // .link.disabled
+ @debug selector.replace("a.disabled", "h1", "h2"); // a.disabled
+ @debug selector.replace(".guide .info", ".info", ".content nav.sidebar");
+ // .guide .content nav.sidebar, .content .guide nav.sidebar
+ ===
+ @debug selector.replace("a.disabled", "a", ".link") // .link.disabled
+ @debug selector.replace("a.disabled", "h1", "h2") // a.disabled
+ @debug selector.replace(".guide .info", ".info", ".content nav.sidebar")
+ // .guide .content nav.sidebar, .content .guide nav.sidebar
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'selector.unify($selector1, $selector2)', 'selector-unify($selector1, $selector2)', 'returns:selector | null' %}
+ {% markdown %}
+ Returns a selector that matches only elements matched by *both* `$selector1`
+ and `$selector2`.
+
+ Returns `null` if `$selector1` and `$selector2` don't match any of the same
+ elements, or if there's no selector that can express their overlap.
+
+ Like selectors generated by the [`@extend` rule][], the returned selector
+ isn't guaranteed to match *all* the elements matched by both `$selector1`
+ and `$selector2` if they're both complex selectors.
+
+ [`@extend` rule]: /documentation/at-rules/extend#html-heuristics
+ {% endmarkdown %}
+
+ {% codeExample 'unify', false %}
+ @debug selector.unify("a", ".disabled"); // a.disabled
+ @debug selector.unify("a.disabled", "a.outgoing"); // a.disabled.outgoing
+ @debug selector.unify("a", "h1"); // null
+ @debug selector.unify(".warning a", "main a"); // .warning main a, main .warning a
+ ===
+ @debug selector.unify("a", ".disabled") // a.disabled
+ @debug selector.unify("a.disabled", "a.outgoing") // a.disabled.outgoing
+ @debug selector.unify("a", "h1") // null
+ @debug selector.unify(".warning a", "main a") // .warning main a, main .warning a
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'selector.simple-selectors($selector)', 'simple-selectors($selector)', 'returns:list' %}
+ {% markdown %}
+ Returns a list of simple selectors in `$selector`.
+
+ The `$selector` must be a single string that contains a compound selector.
+ This means it may not contain combinators (including spaces) or commas.
+
+ The returned list is comma-separated, and the simple selectors are unquoted
+ strings.
+ {% endmarkdown %}
+
+ {% codeExample 'simple-selectors', false %}
+ @debug selector.simple-selectors("a.disabled"); // a, .disabled
+ @debug selector.simple-selectors("main.blog:after"); // main, .blog, :after
+ ===
+ @debug selector.simple-selectors("a.disabled") // a, .disabled
+ @debug selector.simple-selectors("main.blog:after") // main, .blog, :after
+ {% endcodeExample %}
+{% endfunction %}
diff --git a/source/documentation/modules/string.liquid b/source/documentation/modules/string.liquid
new file mode 100644
index 000000000..505871cf4
--- /dev/null
+++ b/source/documentation/modules/string.liquid
@@ -0,0 +1,188 @@
+---
+title: sass:string
+---
+
+{% render 'doc_snippets/built-in-module-status' %}
+
+{% function 'string.quote($string)', 'quote($string)', 'returns:string' %}
+ {% markdown %}
+ Returns `$string` as a quoted string.
+ {% endmarkdown %}
+
+ {% codeExample 'quote', false %}
+ @debug string.quote(Helvetica); // "Helvetica"
+ @debug string.quote("Helvetica"); // "Helvetica"
+ ===
+ @debug string.quote(Helvetica) // "Helvetica"
+ @debug string.quote("Helvetica") // "Helvetica"
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'string.index($string, $substring)', 'str-index($string, $substring)', 'returns:number' %}
+ {% markdown %}
+ Returns the first [index][] of `$substring` in `$string`, or `null` if
+ `$string` doesn't contain `$substring`.
+
+ [index]: /documentation/values/strings#string-indexes
+ {% endmarkdown %}
+
+ {% codeExample 'index', false %}
+ @debug string.index("Helvetica Neue", "Helvetica"); // 1
+ @debug string.index("Helvetica Neue", "Neue"); // 11
+ ===
+ @debug string.index("Helvetica Neue", "Helvetica") // 1
+ @debug string.index("Helvetica Neue", "Neue") // 11
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'string.insert($string, $insert, $index)', 'str-insert($string, $insert, $index)', 'returns:string' %}
+ {% markdown %}
+ Returns a copy of `$string` with `$insert` inserted at [`$index`][].
+
+ [`$index`]: /documentation/values/strings#string-indexes
+ {% endmarkdown %}
+
+ {% codeExample 'insert', false %}
+ @debug string.insert("Roboto Bold", " Mono", 7); // "Roboto Mono Bold"
+ @debug string.insert("Roboto Bold", " Mono", -6); // "Roboto Mono Bold"
+ ===
+ @debug string.insert("Roboto Bold", " Mono", 7) // "Roboto Mono Bold"
+ @debug string.insert("Roboto Bold", " Mono", -6) // "Roboto Mono Bold"
+ {% endcodeExample %}
+
+ {% markdown %}
+ If `$index` is higher than the length of `$string`, `$insert` is added to
+ the end. If `$index` is smaller than the negative length of the string,
+ `$insert` is added to the beginning.
+ {% endmarkdown %}
+
+ {% codeExample 'insert-2', false %}
+ @debug string.insert("Roboto", " Bold", 100); // "Roboto Bold"
+ @debug string.insert("Bold", "Roboto ", -100); // "Roboto Bold"
+ ===
+ @debug string.insert("Roboto", " Bold", 100) // "Roboto Bold"
+ @debug string.insert("Bold", "Roboto ", -100) // "Roboto Bold"
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'string.length($string)', 'str-length($string)', 'returns:number' %}
+ {% markdown %}
+ Returns the number of characters in `$string`.
+ {% endmarkdown %}
+
+ {% codeExample 'length', false %}
+ @debug string.length("Helvetica Neue"); // 14
+ @debug string.length(bold); // 4
+ @debug string.length(""); // 0
+ ===
+ @debug string.length("Helvetica Neue") // 14
+ @debug string.length(bold) // 4
+ @debug string.length("") // 0
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'string.slice($string, $start-at, $end-at: -1)', 'str-slice($string, $start-at, $end-at: -1)', 'returns:string' %}
+ {% markdown %}
+ Returns the slice of `$string` starting at [index][] `$start-at` and ending
+ at index `$end-at` (both inclusive).
+
+ [index]: /documentation/values/strings#string-indexes
+ {% endmarkdown %}
+
+ {% codeExample 'slice', false %}
+ @debug string.slice("Helvetica Neue", 11); // "Neue"
+ @debug string.slice("Helvetica Neue", 1, 3); // "Hel"
+ @debug string.slice("Helvetica Neue", 1, -6); // "Helvetica"
+ ===
+ @debug string.slice("Helvetica Neue", 11) // "Neue"
+ @debug string.slice("Helvetica Neue", 1, 3) // "Hel"
+ @debug string.slice("Helvetica Neue", 1, -6) // "Helvetica"
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'string.split($string, $separator, $limit: null)', 'returns:list' %}
+ {% compatibility 'dart: "1.57.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %}
+
+ {% markdown %}
+ Returns a bracketed, comma-separated list of substrings of `$string` that
+ are separated by `$separator`. The `$separator`s aren't included in these
+ substrings.
+
+ If `$limit` is a number `1` or higher, this splits on at most that many
+ `$separator`s (and so returns at most `$limit + 1` strings). The last
+ substring contains the rest of the string, including any remaining
+ `$separator`s.
+ {% endmarkdown %}
+
+ {% codeExample 'split', false %}
+ @debug string.split("Segoe UI Emoji", " "); // ["Segoe", "UI", "Emoji"]
+ @debug string.split("Segoe UI Emoji", " ", $limit: 1); // ["Segoe", "UI Emoji"]
+ ===
+ @debug string.split("Segoe UI Emoji", " ") // ["Segoe", "UI", "Emoji"]
+ @debug string.split("Segoe UI Emoji", " ", $limit: 1) // ["Segoe", "UI Emoji"]
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'string.to-upper-case($string)', 'to-upper-case($string)', 'returns:string' %}
+ {% markdown %}
+ Returns a copy of `$string` with the [ASCII][] letters converted to upper
+ case.
+
+ [ASCII]: https://en.wikipedia.org/wiki/ASCII
+ {% endmarkdown %}
+
+ {% codeExample 'to-upper-case', false %}
+ @debug string.to-upper-case("Bold"); // "BOLD"
+ @debug string.to-upper-case(sans-serif); // SANS-SERIF
+ ===
+ @debug string.to-upper-case("Bold") // "BOLD"
+ @debug string.to-upper-case(sans-serif) // SANS-SERIF
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'string.to-lower-case($string)', 'to-lower-case($string)', 'returns:string' %}
+ {% markdown %}
+ Returns a copy of `$string` with the [ASCII][] letters converted to lower
+ case.
+
+ [ASCII]: https://en.wikipedia.org/wiki/ASCII
+ {% endmarkdown %}
+
+ {% codeExample 'to-lower-case', false %}
+ @debug string.to-lower-case("Bold"); // "bold"
+ @debug string.to-lower-case(SANS-SERIF); // sans-serif
+ ===
+ @debug string.to-lower-case("Bold") // "bold"
+ @debug string.to-lower-case(SANS-SERIF) // sans-serif
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'string.unique-id()', 'unique-id()', 'returns:string' %}
+ {% markdown %}
+ Returns a randomly-generated unquoted string that's guaranteed to be a valid
+ CSS identifier and to be unique within the current Sass compilation.
+ {% endmarkdown %}
+
+ {% codeExample 'unique-id', false %}
+ @debug string.unique-id(); // uabtrnzug
+ @debug string.unique-id(); // u6w1b1def
+ ===
+ @debug string.unique-id(); // uabtrnzug
+ @debug string.unique-id(); // u6w1b1def
+ {% endcodeExample %}
+{% endfunction %}
+
+{% function 'string.unquote($string)', 'unquote($string)', 'returns:string' %}
+ {% markdown %}
+ Returns `$string` as an unquoted string. This can produce strings that
+ aren't valid CSS, so use with caution.
+ {% endmarkdown %}
+
+ {% codeExample 'unquote', false %}
+ @debug string.unquote("Helvetica"); // Helvetica
+ @debug string.unquote(".widget:hover"); // .widget:hover
+ ===
+ @debug string.unquote("Helvetica") // Helvetica
+ @debug string.unquote(".widget:hover") // .widget:hover
+ {% endcodeExample %}
+{% endfunction %}
diff --git a/source/documentation/style-rules/declarations.liquid b/source/documentation/style-rules/declarations.liquid
index 5df2c066f..80ebd7c25 100644
--- a/source/documentation/style-rules/declarations.liquid
+++ b/source/documentation/style-rules/declarations.liquid
@@ -168,7 +168,9 @@ introduction: >
[interpolation]: /documentation/interpolation
{% endmarkdown %}
-
+{% comment -%}
+ TODO(nweiz): auto-generate this CSS once we're compiling with Dart Sass
+{%- endcomment -%}
{% codeExample 'custom-properties' %}
$primary: #81899b;
$accent: #302e24;
diff --git a/source/documentation/style-rules/index.liquid b/source/documentation/style-rules/index.liquid
index 5a15b5d48..54b8522ce 100644
--- a/source/documentation/style-rules/index.liquid
+++ b/source/documentation/style-rules/index.liquid
@@ -32,7 +32,7 @@ introduction: >
rule's.
{% endmarkdown %}
-{% render 'code-snippets/example-nesting' %}
+{% render 'code_snippets/example-nesting' %}
{% headsUp %}
Nested rules are super helpful, but they can also make it hard to visualize
diff --git a/source/documentation/style-rules/parent-selector.liquid b/source/documentation/style-rules/parent-selector.liquid
index 8b2f4a265..24f38bbe8 100644
--- a/source/documentation/style-rules/parent-selector.liquid
+++ b/source/documentation/style-rules/parent-selector.liquid
@@ -154,7 +154,7 @@ introduction: >
[falsey]: /documentation/at-rules/control/if#truthiness-and-falsiness
{% endmarkdown %}
-{% render 'code-snippets/example-if-parent-selector' %}
+{% render 'code_snippets/example-if-parent-selector' %}
{% markdown %}
### Advanced Nesting
@@ -168,7 +168,7 @@ introduction: >
[`@at-root` rule]: /documentation/at-rules/at-root
{% endmarkdown %}
-{% render 'code-snippets/example-advanced-nesting' %}
+{% render 'code_snippets/example-advanced-nesting' %}
{% headsUp %}
When Sass is nesting selectors, it doesn't know what interpolation was used to
diff --git a/source/documentation/style-rules/placeholder-selectors.liquid b/source/documentation/style-rules/placeholder-selectors.liquid
index b6b14691e..2e6471f0e 100644
--- a/source/documentation/style-rules/placeholder-selectors.liquid
+++ b/source/documentation/style-rules/placeholder-selectors.liquid
@@ -8,7 +8,7 @@ introduction: >
CSS, nor is any style rule whose selectors all contain placeholders.
---
-{% render 'code-snippets/example-placeholder' %}
+{% render 'code_snippets/example-placeholder' %}
{% markdown %}
What's the use of a selector that isn't emitted? It can still be [extended][]!
diff --git a/source/documentation/syntax/structure.md b/source/documentation/syntax/structure.md
index d9dc6eb66..845949619 100644
--- a/source/documentation/syntax/structure.md
+++ b/source/documentation/syntax/structure.md
@@ -95,7 +95,7 @@ The simplest expressions just represent static values:
Sass defines syntax for a number of operations:
-{% render 'documentation/snippets/operator-list', parens: true %}
+{% render 'doc_snippets/operator-list', parens: true %}
### Other Expressions
diff --git a/source/documentation/variables.liquid b/source/documentation/variables.liquid
index 0d1d9b390..e59aec7db 100644
--- a/source/documentation/variables.liquid
+++ b/source/documentation/variables.liquid
@@ -99,7 +99,7 @@ introduction: >
### Configuring Modules
- {% render 'documentation/snippets/module-system-status' %}
+ {% render 'doc_snippets/module-system-status' %}
Variables defined with `!default` can be configured when loading a module with
the [`@use` rule][]. Sass libraries often use `!default` variables to allow
@@ -113,7 +113,7 @@ introduction: >
stylesheet with a `!default` flag can be configured.
{% endmarkdown %}
-{% render 'code-snippets/example-use-with' %}
+{% render 'code_snippets/example-use-with' %}
{% markdown %}
## Built-in Variables
diff --git a/source/guide.liquid b/source/guide.liquid
index 2cf37ac8a..638e2efaf 100644
--- a/source/guide.liquid
+++ b/source/guide.liquid
@@ -207,7 +207,7 @@ navigation: |
{% markdown %}
## Modules
- {% render 'documentation/snippets/module-system-status' %}
+ {% render 'doc_snippets/module-system-status' %}
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
diff --git a/source/helpers/function.ts b/source/helpers/function.ts
new file mode 100644
index 000000000..168781075
--- /dev/null
+++ b/source/helpers/function.ts
@@ -0,0 +1,93 @@
+import * as cheerio from 'cheerio';
+import stripIndent from 'strip-indent';
+
+import { codeBlock } from './components';
+import { liquidEngine } from './engines';
+
+const links: Record = {
+ number: '/documentation/values/numbers',
+ string: '/documentation/values/strings',
+ 'quoted string': '/documentation/values/strings#quoted',
+ 'unquoted string': '/documentation/values/strings#unquoted',
+ color: '/documentation/values/colors',
+ list: '/documentation/values/lists',
+ map: '/documentation/values/maps',
+ boolean: '/documentation/values/booleans',
+ null: '/documentation/values/null',
+ function: '/documentation/values/functions',
+ selector: '/documentation/modules/selector#selector-values',
+};
+
+const returnTypeLink = (returnType: string) =>
+ returnType
+ .split('|')
+ .map((type) => {
+ type = type.trim();
+ const link = links[type];
+ if (!link) {
+ throw new Error(`Unknown type ${type}`);
+ }
+ return `${type}`;
+ })
+ .join(' | ');
+
+/** Renders API docs for a Sass function (or mixin).
+ *
+ * The function's name is parsed from the signature. The API description is
+ * passed as an HTML block. If `returns:type` is passed as the last argument,
+ * it's included as the function's return type.
+ *
+ * Multiple signatures may be passed, in which case they're all included in
+ * sequence.
+ */
+export function _function(content: string, ...signatures: string[]) {
+ // Parse the last argument as the return type, if it's present
+ const returns = signatures.at(-1)?.match(/returns?:\s*(.*)/)?.[1];
+ if (returns) {
+ signatures.pop();
+ }
+
+ // Highlight each signature
+ const names: string[] = [];
+ const highlightedSignatures = signatures.map((signature) => {
+ signature = stripIndent(signature).trim();
+ const [name] = signature.split('(', 2);
+ const nameWithoutNamespace = name.split('.').at(-1) || name;
+ const html = codeBlock(`@function ${signature}`, 'scss');
+ const $ = cheerio.load(html);
+ const signatureElements = $('pre code')
+ .contents()
+ .filter((index, element) => $(element).text() !== '@function');
+ // Add a class to make it easier to index function documentation.
+ if (!names.includes(nameWithoutNamespace)) {
+ names.push(nameWithoutNamespace);
+ const nameEl = signatureElements
+ .filter((index, element) => {
+ return $(element).text() == nameWithoutNamespace;
+ })
+ .eq(0);
+ nameEl.addClass('docSearch-function');
+ nameEl.attr('name', name);
+ }
+ return signatureElements
+ .toArray()
+ .map((el) => $.html(el))
+ .join('')
+ .trim();
+ });
+
+ // Render the final HTML
+ return liquidEngine.renderFile('function', {
+ names,
+ signatures: highlightedSignatures.join('\n'),
+ content,
+ returns: returns ? returnTypeLink(returns) : null,
+ });
+}
+
+/* eslint-disable @typescript-eslint/no-unsafe-member-access,
+ @typescript-eslint/no-unsafe-call,
+ @typescript-eslint/no-explicit-any */
+export default function typePlugin(eleventyConfig: any) {
+ eleventyConfig.addPairedLiquidShortcode('function', _function);
+}
diff --git a/source/helpers/type.ts b/source/helpers/type.ts
index cb3b030d9..ffe86919c 100644
--- a/source/helpers/type.ts
+++ b/source/helpers/type.ts
@@ -66,6 +66,13 @@ export const replaceInternalLinks = (content: string, url: string) =>
*/
export const startsWith = (str: string, check: string) => str.startsWith(check);
+/**
+ * Strips leading whitespace from each line in a string.
+ *
+ * @see https://github.com/sindresorhus/strip-indent
+ */
+export const stripInd = (str: string) => stripIndent(str);
+
/* eslint-disable @typescript-eslint/no-unsafe-member-access,
@typescript-eslint/no-unsafe-call,
@typescript-eslint/no-explicit-any */
@@ -77,6 +84,7 @@ export default function typePlugin(eleventyConfig: any) {
eleventyConfig.addLiquidFilter('typogr', typogr);
eleventyConfig.addLiquidFilter('replaceInternalLinks', replaceInternalLinks);
eleventyConfig.addLiquidFilter('startsWith', startsWith);
+ eleventyConfig.addLiquidFilter('stripIndent', stripInd);
// shortcodes...
eleventyConfig.addLiquidShortcode('lorem', getLorem);