From a55d978bb60d3f8bcd454fa7564520e2883090d7 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Fri, 17 May 2024 16:45:21 -0700 Subject: [PATCH] Document new color spaces See #672 --- source/assets/sass/visual-design/_theme.scss | 4 + source/documentation/modules/color.md | 1101 ++++++++++++------ source/documentation/modules/index.md | 274 ++++- source/documentation/operators/equality.md | 6 +- source/documentation/values/colors.md | 346 +++++- 5 files changed, 1271 insertions(+), 460 deletions(-) diff --git a/source/assets/sass/visual-design/_theme.scss b/source/assets/sass/visual-design/_theme.scss index 48f893fcc..108ac2b5e 100644 --- a/source/assets/sass/visual-design/_theme.scss +++ b/source/assets/sass/visual-design/_theme.scss @@ -32,6 +32,10 @@ body { color: var(--text, var(--sl-color--pale-sky)); } +.fade { + opacity: 0.7; +} + ::selection { background: var(--sl-color--iron); } diff --git a/source/documentation/modules/color.md b/source/documentation/modules/color.md index 29c7a6f8b..c958510c7 100644 --- a/source/documentation/modules/color.md +++ b/source/documentation/modules/color.md @@ -9,60 +9,649 @@ title: sass:color $red: null, $green: null, $blue: null, $hue: null, $saturation: null, $lightness: null, $whiteness: null, $blackness: null, - $alpha: null) + $x: null, $y: null, $z: null, + $chroma: null, + $alpha: null, + $space: null) {% endcapture %} {% function color_adjust, 'adjust-color(...)', 'returns:color' %} + {% compatibility 'dart: "1.78.0"', 'libsass: false', 'ruby: false', 'feature: "$x, $y, $z, $chroma, and $space"' %}{% endcompatibility %} {% compatibility 'dart: "1.28.0"', 'libsass: false', 'ruby: false', 'feature: "$whiteness and $blackness"' %}{% endcompatibility %} - Increases or decreases one or more properties of `$color` by fixed amounts. + Increases or decreases one or more channels 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`). + Adds the value passed for each keyword argument to the corresponding channel + of the color, and returns the adjusted color. By default, this can only adjust + channels in `$color`'s space, but a different color space can be passed as + `$space` to adjust channels there instead. This always returns a color in the + same space as `$color`. - [HWB]: https://en.wikipedia.org/wiki/HWB_color_model + {% headsUp %} + For historical reasons, if `$color` is in a [legacy color space], _any_ + legacy color space channels can be adjusted. However, it's an error to + specify an RGB channel (`$red`, `$green`, and/or `$blue`) at the same time + as an HSL channel (`$hue`, `$saturation`, and/or `$lightness`), or either of + those at the same time as an [HWB] channel (`$hue`, `$whiteness`, and/or + `$blackness`). - 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). + [legacy color space]: /documentation/values/colors#legacy-color-spaces + [HWB]: https://en.wikipedia.org/wiki/HWB_color_model - [unitless]: /documentation/values/numbers#units + Even so, it's a good idea to pass `$space` explicitly even for legacy colors. + {% endheadsUp %} + + All channel arguments must be numbers, and must be units that could be passed + for those channels in the color space's constructor. If the existing channel + value plus the adjustment value is outside the channel's native range, it's + clamped for: + + * red, green, and blue channels for the `rgb` space; + * lightness channel for the `lab`, `lch`, `oklab`, and `oklch` spaces; + * the lower bound of the saturation and chroma channels for the `hsl`, `lch`, + and `oklch` spaces; + * and the alpha channel for all spaces. See also: * [`color.scale()`](#scale) for fluidly scaling a color's properties. * [`color.change()`](#change) for setting a color's properties. - {% codeExample 'adjust-color' %} + {% codeExample 'adjust-color', false %} @use 'sass:color'; @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(lab(40% 30 40), $lightness: 10%, $a: -20); // lab(50% 10 40) + @debug color.adjust(#d2e1dd, $hue: 45deg, $space: oklch); + // rgb(209.7987626149, 223.8632000471, 229.3988769575) === @use 'sass:color' @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(lab(40% 30 40), $lightness: 10%, $a: -20) // lab(50% 10 40) + @debug color.adjust(#d2e1dd, $hue: 45deg, $space: oklch) + // rgb(209.7987626149, 223.8632000471, 229.3988769575) + {% endcodeExample %} +{% endfunction %} + +{% capture color_change %} + color.change($color, + $red: null, $green: null, $blue: null, + $hue: null, $saturation: null, $lightness: null, + $whiteness: null, $blackness: null, + $x: null, $y: null, $z: null, + $chroma: null, + $alpha: null, + $space: null) +{% endcapture %} + +{% function color_change, 'change-color(...)', 'returns:color' %} + {% compatibility 'dart: "1.78.0"', 'libsass: false', 'ruby: false', 'feature: "$x, $y, $z, $chroma, and $space"' %}{% endcompatibility %} + {% compatibility 'dart: "1.28.0"', 'libsass: false', 'ruby: false', 'feature: "$whiteness and $blackness"' %}{% endcompatibility %} + + Sets one or more channels of a color to new values. + + Uses the value passed for each keyword argument in place of the corresponding + color channel, and returns the changed color. By default, this can only change + channels in `$color`'s space, but a different color space can be passed as + `$space` to adjust channels there instead. This always returns a color in the + same space as `$color`. + + {% headsUp %} + + For historical reasons, if `$color` is in a [legacy color space], _any_ + legacy color space channels can be changed. However, it's an error to + specify an RGB channel (`$red`, `$green`, and/or `$blue`) at the same time + as an HSL channel (`$hue`, `$saturation`, and/or `$lightness`), or either + of those at the same time as an [HWB] channel (`$hue`, `$whiteness`, and/or + `$blackness`). + + [legacy color space]: /documentation/values/colors#legacy-color-spaces + [HWB]: https://en.wikipedia.org/wiki/HWB_color_model + + Even so, it's a good idea to pass `$space` explicitly even for legacy colors. + {% endheadsUp %} + + All channel arguments must be numbers, and must be units that could be passed + for those channels in the color space's constructor. Channels are never + clamped for `color.change()`. + + See also: + + * [`color.scale()`](#scale) for fluidly scaling a color's properties. + * [`color.adjust()`](#adjust) for adjusting a color's properties by fixed + amounts. + + {% codeExample 'color-change', false %} + @use 'sass:color'; + + @debug color.change(#6b717f, $red: 100); // #64717f + @debug color.change(color(srgb 0 0.2 0.4), $red: 0.8, $blue: 0.1); + // color(srgb 0.8 0.1 0.4) + @debug color.change(#998099, $lightness: 30%, $space: oklch); + // rgb(58.0719961509, 37.2631531594, 58.4201613409) + === + @use 'sass:color' + + @debug color.change(#6b717f, $red: 100) // #64717f + @debug color.change(color(srgb 0 0.2 0.4), $red: 0.8, $blue: 0.1) + // color(srgb 0.8 0.1 0.4) + @debug color.change(#998099, $lightness: 30%, $space: oklch) + // rgb(58.0719961509, 37.2631531594, 58.4201613409) + {% endcodeExample %} +{% endfunction %} + +{% function 'color.complement($color, $space: null)', 'complement($color, $space: null)', 'returns:color' %} + {% compatibility 'dart: "1.78.0"', 'libsass: false', 'ruby: false', 'feature: "$space"' %}{% endcompatibility %} + + Returns the [complement] of `$color` in `$space`. + + [complement]: https://en.wikipedia.org/wiki/Complementary_colors + + This rotates `$color`'s hue by `180deg` in `$space`. This means that `$space` + has to be a polar color space: `hsl`, `hwb`, `lch`, or `oklch`. It always + returns a color in the same space as `$color`. + + {% headsUp %} + For historical reasons, `$space` is optional if `$color` is in a [legacy + color space]. In that case, `$space` defaults to `hsl`. It's always a good + idea to pass `$space` explicitly regardless. + + [legacy color space]: /documentation/values/colors#legacy-color-spaces + {% endheadsUp %} + + {% codeExample 'color-complement', false %} + @use 'sass:color'; + + // HSL hue 222deg becomes 42deg. + @debug color.complement(#6b717f); // #7f796b + + // Oklch hue 267.1262408996deg becomes 87.1262408996deg + @debug color.complement(#6b717f, oklch); + // rgb(118.8110604298, 112.5123650034, 98.1616586336) + + // Hue 70deg becomes 250deg. + @debug color.complement(oklch(50% 0.12 70deg), oklch); // oklch(50% 0.12 250deg) + === + @use 'sass:color' + + // HSL hue 222deg becomes 42deg. + @debug color.complement(#6b717f) // #7f796b + + // Oklch hue 267.1262408996deg becomes 87.1262408996deg + @debug color.complement(#6b717f, oklch) + // rgb(118.8110604298, 112.5123650034, 98.1616586336) + + // Hue 70deg becomes 250deg. + @debug color.complement(oklch(50% 0.12 70deg), oklch) // oklch(50% 0.12 250deg) + {% endcodeExample %} +{% endfunction %} + +{% function 'color.channel($color, $channel, $space: null)', 'returns:number' %} + {% compatibility 'dart: "1.78.0"', 'libsass: false', 'ruby: false', 'feature: "$space"' %}{% endcompatibility %} + + Returns the value of `$channel` in `$space`, which defaults to `$color`'s + space. The `$channel` must be a quoted string, and the `$space` must be an + unquoted string. + + This returns a number with unit `deg` for the `hue` channel of the `hsl`, + `hwb`, `lch`, and `oklch` spaces. It returns a number with unit `%` for the + `saturation`, `lightness`, `whiteness`, and `blackness` channels of the `hsl`, + `hwb`, `lab`, `lch`, `oklab`, and `oklch` spaces. For all other channels, it + returns a unitless number. + + This will return `0` (possibly with an appropriate unit) if the `$channel` is + missing in `$color`. You can use [`color.is-missing()`] to check explicitly + for missing channels. + + [`color.is-missing()`]: #is-missing + + {% codeExample 'color-channel', false %} + @use 'sass:color'; + + @debug color.channel(hsl(80deg 30% 50%), "hue"); // 80deg + @debug color.channel(hsl(80deg 30% 50%), "hue", $space: oklch); // 124.279238779deg + @debug color.channel(hsl(80deg 30% 50%), "red", $space: rgb); // 140.25 + === + @use 'sass:color' + + @debug color.channel(hsl(80deg 30% 50%), "hue") // 80deg + @debug color.channel(hsl(80deg 30% 50%), "hue", $space: oklch) // 124.279238779deg + @debug color.channel(hsl(80deg 30% 50%), "red", $space: rgb) // 140.25 + {% endcodeExample %} +{% endfunction %} + +{% function 'color.grayscale($color)', 'grayscale($color)', 'returns:color' %} + Returns a gray color with the same lightness as `$color`. + + If `$color` is in a [legacy color space], this sets the HSL saturation to 0%. + Otherwise, it sets the Oklch chroma to 0%. + + [legacy color space]: /documentation/values/colors#legacy-color-spaces + + {% codeExample 'color-grayscale', false %} + @use 'sass:color'; + + @debug color.grayscale(#6b717f); // #757575 + @debug color.grayscale(color(srgb 0.4 0.2 0.6)); // color(srgb 0.3233585271 0.3233585411 0.3233585792) + @debug color.grayscale(oklch(50% 80% 270deg)); // oklch(50% 0% 270deg) + === + @use 'sass:color' + + @debug color.grayscale(#6b717f) // #757575 + @debug color.grayscale(color(srgb 0.4 0.2 0.6)) // color(srgb 0.3233585271 0.3233585411 0.3233585792) + @debug color.grayscale(oklch(50% 80% 270deg)) // oklch(50% 0% 270deg) + {% endcodeExample %} +{% endfunction %} + +{% function 'color.ie-hex-str($color)', 'ie-hex-str($color)', 'returns:unquoted string' %} + Returns an unquoted string that represents `$color` in the `#AARRGGBB` format + expected by Internet Explorer's [`-ms-filter`] property. + + [`-ms-filter`]: https://learn.microsoft.com/en-us/previous-versions/ms530752(v=vs.85) + + If `$color` isn't already in the `rgb` color space, it's converted to `rgb` + and gamut-mapped if necessary. The specific gamut-mapping algorithm may change + in future Sass versions as the state of the art improves; currently, + `local-minde` is used. + + {% codeExample 'color-ie-hex-str', false %} + @use 'sass:color'; + + @debug color.ie-hex-str(#b37399); // #FFB37399 + @debug color.ie-hex-str(rgba(242, 236, 228, 0.6)); // #99F2ECE4 + @debug color.ie-hex-str(oklch(70% 10% 120deg)); // #FF9BA287 + === + @use 'sass:color' + + @debug color.ie-hex-str(#b37399) // #FFB37399 + @debug color.ie-hex-str(rgba(242, 236, 228, 0.6)) // #99F2ECE4 + @debug color.ie-hex-str(oklch(70% 10% 120deg)) // #FF9BA287 + {% endcodeExample %} +{% endfunction %} + +{% function 'color.invert($color, $weight: 100%, $space: null)', 'invert($color, $weight: 100%, $space: null)', 'returns:color' %} + {% compatibility 'dart: "1.78.0"', 'libsass: false', 'ruby: false', 'feature: "$space"' %}{% endcompatibility %} + + Returns the inverse or [negative] of `$color` in `$space`. + + [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 a + medium-lightness gray in `$space`. + + {% headsUp %} + For historical reasons, `$space` is optional if `$color` is in a [legacy + color space]. In that case, `$space` defaults to `$color`'s own space. It's + always a good idea to pass `$space` explicitly regardless. + + [legacy color space]: /documentation/values/colors#legacy-color-spaces + {% endheadsUp %} + + {% codeExample 'color-invert', false %} + @use 'sass:color'; + + @debug color.invert(#b37399, $space: rgb); // #4c8c66 + @debug color.invert(#550e0c, 20%, $space: display-p3); // rgb(103.4937692017, 61.3720912206, 59.430641338) + === + @use 'sass:color'; + + @debug color.invert(#b37399, $space: rgb) // #4c8c66 + @debug color.invert(#550e0c, 20%, $space: display-p3) // rgb(103.4937692017, 61.3720912206, 59.430641338) + {% endcodeExample %} +{% endfunction %} + +{% function 'color.is-legacy($color)', 'returns:boolean' %} + Returns whether `$color` is in a [legacy color space]. + + [legacy color space]: /documentation/values/colors#legacy-color-spaces + + {% codeExample 'color-is-legacy', false %} + @use 'sass:color'; + + @debug color.is-legacy(#b37399); // true + @debug color.is-legacy(hsl(90deg 30% 90%)); // true + @debug color.is-legacy(oklch(70% 10% 120deg)); // false + === + @use 'sass:color' + + @debug color.is-legacy(#b37399) // true + @debug color.is-legacy(hsl(90deg 30% 90%)) // true + @debug color.is-legacy(oklch(70% 10% 120deg)) // false + {% endcodeExample %} +{% endfunction %} + +{% function 'color.is-missing($color, $channel)', 'returns:boolean' %} + Returns whether `$channel` is [missing] in `$color`. The `$channel` must be a + quoted string. + + [missing channel]: /documentation/values/colors#missing-channels + + {% codeExample 'color-is-missing', false %} + @use 'sass:color'; + + @debug color.is-missing(#b37399, "green"); // false + @debug color.is-missing(rgb(100 none 200), "green"); // true + @debug color.is-missing(color.to-space(grey, lch), "hue"); // true + === + @use 'sass:color' + + @debug color.is-legacy(#b37399) // true + @debug color.is-legacy(hsl(90deg 30% 90%)) // true + @debug color.is-legacy(oklch(70% 10% 120deg)) // false + {% endcodeExample %} +{% endfunction %} + +{% function 'color.is-powerless($color, $channel, $space: null)', 'returns:boolean' %} + Returns whether `$color`'s `$channel` is [powerless] in `$space`, which + defaults to `$color`'s space. The `$channel` must be a quoted string and the + `$space` must be an unquoted string. + + [powerless]: /documentation/values/colors#powerless-channels + + Channels are considered powerless in the following circumstances: + + * In the `hsl` space, the `hue` is powerless if the `saturation` is 0%. + * In the `hwb` space, the `hue` is powerless if the `whiteness` plus the + `blackness` is greater than 100%. + * In the `lch` and `oklch` spaces, the `hue` is powerless if the `chroma` is + 0%. + + {% codeExample 'color-is-powerless', false %} + @use 'sass:color'; + + @debug color.is-powerless(hsl(180deg 0% 40%), "hue"); // true + @debug color.is-powerless(hsl(180deg 0% 40%), "saturation"); // false + @debug color.is-powerless(#999, "hue", $space: hsl); // true + === + @use 'sass:color' + + @debug color.is-powerless(hsl(180deg 0% 40%), "hue") // true + @debug color.is-powerless(hsl(180deg 0% 40%), "saturation") // false + @debug color.is-powerless(#999, "hue", $space: hsl) // true + {% endcodeExample %} +{% endfunction %} + +{% function 'color.mix($color1, $color2, $weight: 50%, $method: null)', 'mix($color1, $color2, $weight: 50%, $method: null)', 'returns:color' %} + {% compatibility 'dart: "1.78.0"', 'libsass: false', 'ruby: false', 'feature: "$method"' %}{% endcompatibility %} + + Returns a color that's a mixture of `$color1` and `$color2` using `$method`, + which is the name of a color space, optionally followed by a [hue + interpolation method] if it's a polar color space (`hsl`, `hwb`, `lch`, or + `oklch`). + + [hue interpolation method]: https://developer.mozilla.org/en-US/docs/Web/CSS/hue-interpolation-method + + This uses the same algorithm to mix colors as [the CSS `color-mix()` + function]. This also means that if either color has a [missing channel] in the + interpolation space, it will take on the corresponding channel value from the + other color. This always returns a color in `$color1`'s space. + + [the CSS `color-mix()` function]: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color-mix + [missing channel]: /documentation/values/colors#missing-channels + + 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. + + {% headsUp %} + For historical reasons, `$method` is optional if `$color1` and `$color2` are + both in [legacy color spaces]. In this case, color mixing is done using the + same algorithm that Sass used historically, in which both the `$weight` and + the relative opacity of each color determines how much of each color is in + the result. + + [legacy color spaces]: /documentation/values/colors#legacy-color-spaces + {% endheadsUp %} + + {% codeExample 'color-mix', false %} + @use 'sass:color'; + + @debug color.mix(#036, #d2e1dd, $method: rgb); // #698aa2 + @debug color.mix(#036, #d2e1dd, $method: oklch); // rgb(87.864037264, 140.601918773, 154.2876826946) + @debug color.mix( + color(rec2020 1 0.7 0.1), + color(rec2020 0.8 none 0.3), + $weight: 75%, + $method: rec2020 + ); // color(rec2020 0.95 0.7 0.15) + @debug color.mix( + oklch(80% 20% 0deg), + oklch(50% 10% 120deg), + $method: oklch longer hue + ); // oklch(65% 0.06 240deg) + === + @use 'sass:color'; + + @debug color.mix(#036, #d2e1dd, $method: rgb) // #698aa2 + @debug color.mix(#036, #d2e1dd, $method: oklch) // rgb(87.864037264, 140.601918773, 154.2876826946) + @debug color.mix(color(rec2020 1 0.7 0.1), color(rec2020 0.8 none 0.3), $weight: 75%, $method: rec2020) // color(rec2020 0.95 0.7 0.15) + + + + + + @debug color.mix(oklch(80% 20% 0deg), oklch(50% 10% 120deg), $method: oklch longer hue) // oklch(65% 0.06 240deg) + {% endcodeExample %} +{% endfunction %} + +{% function 'color.same($color1, $color2)', 'returns:boolean' %} + {% compatibility 'dart: "1.78.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %} + + Returns whether `$color1` and `$color2` visually render as the same color. + Unlike `==`, this considers colors to be equivalent even if they're in + different color spaces as long as they represent the same color value in the + `xyz` color space. This treats [missing channels] as equivalent to zero. + + [missing channels]: /documentation/values/colors#missing-channels + + {% codeExample 'color-same', false %} + @use 'sass:color'; + + @debug color.same(#036, #036); // true + @debug color.same(#036, #037); // false + @debug color.same(#036, color.to-space(#036, oklch)); // true + @debug color.same(hsl(none 50% 50%), hsl(0deg 50% 50%)); // true + === + @use 'sass:color' + + @debug color.same(#036, #036) // true + @debug color.same(#036, #037) // false + @debug color.same(#036, color.to-space(#036, oklch)) // true + @debug color.same(hsl(none 50% 50%), hsl(0deg 50% 50%)) // true + {% endcodeExample %} +{% endfunction %} + +{% capture color_scale %} + color.scale($color, + $red: null, $green: null, $blue: null, + $saturation: null, $lightness: null, + $whiteness: null, $blackness: null, + $x: null, $y: null, $z: null, + $chroma: null, + $alpha: null, + $space: null) +{% endcapture %} + +{% function color_scale, 'scale-color(...)', 'returns:color' %} + {% compatibility 'dart: "1.78.0"', 'libsass: false', 'ruby: false', 'feature: "$x, $y, $z, $chroma, and $space"' %}{% endcompatibility %} + {% compatibility 'dart: "1.28.0"', 'libsass: false', 'ruby: false', 'feature: "$whiteness and $blackness"' %}{% endcompatibility %} + + 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. By default, this can only scale colors in + `$color`'s space, but a different color space can be passed as `$space` to + scale channels there instead. This always returns a color in the same space as + `$color`. + + {% headsUp %} + For historical reasons, if `$color` is in a [legacy color space], _any_ + legacy color space channels can be scaled. However, it's an error to specify + an RGB channel (`$red`, `$green`, and/or `$blue`) at the same time as an HSL + channel (`$saturation`, and/or `$lightness`), or either of those at the same + time as an [HWB] channel (`$hue`, `$whiteness`, and/or `$blackness`). + + [legacy color space]: /documentation/values/colors#legacy-color-spaces + [HWB]: https://en.wikipedia.org/wiki/HWB_color_model + + Even so, it's a good idea to pass `$space` explicitly even for legacy colors. + {% endheadsUp %} + + [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. + + {% codeExample 'color-scale', false %} + @use 'sass:color'; + + @debug color.scale(#6b717f, $red: 15%); // rgb(129.2, 113, 127) + @debug color.scale(#d2e1dd, $lightness: -10%, $space: oklch); + // rgb(181.2580722731, 195.8949200496, 192.0059024063) + @debug color.scale(oklch(80% 20% 120deg), $chroma: 50%, $alpha: -40%); + // oklch(80% 0.24 120deg / 0.6) + === + @use 'sass:color' + + @debug color.scale(#6b717f, $red: 15%) // rgb(129.2, 113, 127) + @debug color.scale(#d2e1dd, $lightness: -10%, $space: oklch) + // rgb(181.2580722731, 195.8949200496, 192.0059024063) + @debug color.scale(oklch(80% 20% 120deg), $chroma: 50%, $alpha: -40%) + // oklch(80% 0.24 120deg / 0.6) {% endcodeExample %} {% endfunction %} +{% function 'color.space($color)', 'returns:unquoted string' %} + {% compatibility 'dart: "1.78.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %} + + Returns the name of `$color`'s space as an unquoted string. + + {% codeExample 'color-space', false %} + @use 'sass:color'; + + @debug color.space(#036); // rgb + @debug color.space(hsl(120deg 40% 50%)); // hsl + @debug color.space(color(xyz-d65 0.1 0.2 0.3)); // xyz + === + @use 'sass:color' + + @debug color.space(#036) // rgb + @debug color.space(hsl(120deg 40% 50%)) // hsl + @debug color.space(color(xyz-d65 0.1 0.2 0.3)) // xyz + {% endcodeExample %} +{% endfunction %} + +{% function 'color.to-gamut($color, $space: null, $method: null)', 'returns:color' %} + {% compatibility 'dart: "1.78.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %} + + Returns a visually similar color to `$color` in the gamut of `$space`, which + defaults to `$color`'s space. If `$color` is already in-gamut for `$space`, + it's returned as-is. This always returns a color in` $color`'s original space. + The `$space` must be an unquoted string. + + The `$method` indicates how Sass should choose a "similar" color: + + * `local-minde`: This is the method currently recommended by the CSS Colors 4 + specification. It binary searches the Oklch chroma space of the color until + it finds a color whose clipped-to-gamut value is as close as possible to the + reduced-chroma variant. + + * `clip`: This simply clips all channels to within `$space`'s gamut, setting + them to the minimum or maximum gamut values if they're out-of-gamut. + + {% headsUp %} + The CSS working group and browser vendors are still actively discussing + alternative options for a recommended gamut-mapping algorithm. Until they + settle on a recommendation, the `$method` parameter is mandatory in + `color.to-gamut()` so that we can eventually make its default value the same + as the CSS default. + {% endheadsUp %} + + {% codeExample 'color-to-gamut', false %} + @use 'sass:color'; + + @debug color.to-gamut(#036, $method: local-minde); // #036 + @debug color.to-gamut(oklch(60% 70% 20deg), $space: rgb, $method: local-minde); + // oklch(61.2058838235% 0.2466052584 22.0773325274deg) + @debug color.to-gamut(oklch(60% 70% 20deg), $space: rgb, $method: clip); + // oklch(62.5026609544% 0.2528579741 24.1000466758deg) + === + @use 'sass:color' + + @debug color.to-gamut(#036, $method: local-minde) // #036 + @debug color.to-gamut(oklch(60% 70% 20deg), $space: rgb, $method: local-minde) + // oklch(61.2058838235% 0.2466052584 22.0773325274deg) + @debug color.to-gamut(oklch(60% 70% 20deg), $space: rgb, $method: clip) + // oklch(62.5026609544% 0.2528579741 24.1000466758deg) + {% endcodeExample %} +{% endfunction %} + +{% function 'color.to-space($color, $space)', 'returns:color' %} + {% compatibility 'dart: "1.78.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %} + + Converts `$color` into the given `$space`, which must be an unquoted string. + + If the gamut of `$color`'s original space is wider than `$space`'s gamut, this + may return a color that's out-of-gamut for the `$space`. You can convert it to + a similar in-gamut color using [`color.to-gamut()`]. + + [`color.to-gamut()`]: #to-gamut + + This can produce colors with [missing channels], either if `$color` has an + [analogous channel] that's missing, or if the channel is [powerless] in the + destination space. In order to ensure that converting to legacy color spaces + always produces a color that's compatible with older browsers, if `$space` is + legacy this will never return a new missing channel. + + [missing channels]: /documentation/values/colors#missing-channels + [analogous channel]: https://www.w3.org/TR/css-color-4/#analogous-components + [powerless]: /documentation/values/colors#powerless-channels + + {% funFact %} + This is the only Sass function that returns a color in a different space + than the one passed in. + {% endfunFact %} + + {% codeExample 'color-to-space', false %} + @use 'sass:color'; + + @debug color.to-space(#036, display-p3); // lch(20.7457453073% 35.0389733355 273.0881809283deg) + @debug color.to-space(oklab(44% 0.09 -0.13)); // rgb(103.1328911972, 50.9728091281, 150.8382311692) + @debug color.to-space(xyz(0.8 0.1 0.1)); // color(a98-rgb 1.2177586808 -0.7828263424 0.3516847577) + @debug color.to-space(grey, lch); // lch(53.5850134522% 0 none) + @debug color.to-space(lch(none 10% 30deg), oklch); // oklch(none 0.3782382429 11.1889160032deg) + === + @use 'sass:color' + + @debug color.to-space(#036, display-p3) // lch(20.7457453073% 35.0389733355 273.0881809283deg) + @debug color.to-space(oklab(44% 0.09 -0.13)) // rgb(103.1328911972, 50.9728091281, 150.8382311692) + @debug color.to-space(xyz(0.8 0.1 0.1)) // color(a98-rgb 1.2177586808 -0.7828263424 0.3516847577) + @debug color.to-space(grey, lch) // lch(53.5850134522% 0 none) + @debug color.to-space(lch(none 10% 30deg), oklch) // oklch(none 0.3782382429 11.1889160032deg) + {% endcodeExample %} +{% endfunction %} + +## Deprecated Functions + {% function 'adjust-hue($color, $degrees)', 'returns:color' %} - Increases or decreases `$color`'s hue. + Increases or decreases `$color`'s HSL 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`. + to `$color`'s hue. It may be [unitless] or have any angle unit. The `$color` + must be in a [legacy color space]. [unitless]: /documentation/values/numbers#units + [legacy color space]: /documentation/values/colors#legacy-color-spaces See also [`color.adjust()`](#adjust), which can adjust any property of a color. @@ -70,7 +659,8 @@ title: sass:color {% 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). + $amount)`, you can write [`color.adjust($color, $hue: $amount, $space: + hsl)`](#adjust). {% endheadsUp %} {% codeExample 'adjust-hue' %} @@ -96,20 +686,21 @@ title: sass:color {% function 'color.alpha($color)', 'alpha($color)', 'opacity($color)', 'returns:number' %} Returns the alpha channel of `$color` as a number between 0 and 1. + + The `$color` must be in a [legacy color space]. + + [legacy color space]: /documentation/values/colors#legacy-color-spaces As a special case, this supports the Internet Explorer syntax - `alpha(opacity=20)`, for which it returns an [unquoted string][]. + `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. + {% headsUp %} + Because `color.alpha()` is redundant with [`color.channel()`](#channel), + it's no longer recommended. Instead of `color.alpha($color)`, you can write + [`color.channel($color, "alpha")`](#channel). + {% endheadsUp %} {% codeExample 'color-alpha' %} @use 'sass:color'; @@ -126,22 +717,22 @@ title: sass:color {% endcodeExample %} {% endfunction %} -{% function 'color.blackness($color)', 'returns:number' %} +{% function 'color.blackness($color)', 'blackness($color)', 'returns:number' %} {% compatibility 'dart: "1.28.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %} - Returns the [HWB][] blackness of `$color` as a number between `0%` and `100%`. + Returns the [HWB] blackness of `$color` as a number between `0%` and `100%`. [HWB]: https://en.wikipedia.org/wiki/HWB_color_model - See also: + The `$color` must be in a [legacy color space]. + + [legacy color space]: /documentation/values/colors#legacy-color-spaces - * [`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. + {% headsUp %} + Because `color.blackness()` is redundant with [`color.channel()`](#channel), + it's no longer recommended. Instead of `color.blackness($color)`, you can + write [`color.channel($color, "blackness")`](#channel). + {% endheadsUp %} {% codeExample 'color-blackness' %} @use 'sass:color'; @@ -161,16 +752,15 @@ title: sass:color {% function 'color.blue($color)', 'blue($color)', 'returns:number' %} Returns the blue channel of `$color` as a number between 0 and 255. - See also: + The `$color` must be in a [legacy color space]. + + [legacy color space]: /documentation/values/colors#legacy-color-spaces - * [`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. + {% headsUp %} + Because `color.blue()` is redundant with [`color.channel()`](#channel), it's + no longer recommended. Instead of `color.blue($color)`, you can write + [`color.channel($color, "blue")`](#channel). + {% endheadsUp %} {% codeExample 'color-blue' %} @use 'sass:color'; @@ -187,93 +777,13 @@ title: sass:color {% 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 %} - - 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. - - {% codeExample 'color-change' %} - @use 'sass:color'; - - @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) - === - @use 'sass:color' - - @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' %} - Returns the RGB [complement][] of `$color`. - - This is identical to [`color.adjust($color, $hue: 180deg)`](#adjust). - - [complement]: https://en.wikipedia.org/wiki/Complementary_colors - - {% codeExample 'color-complement' %} - @use 'sass:color'; - - // 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 - === - @use 'sass:color' - - // 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' %} Makes `$color` darker. + The `$color` must be in a [legacy color space]. + + [legacy color space]: /documentation/values/colors#legacy-color-spaces + The `$amount` must be a number between `0%` and `100%` (inclusive). Decreases the HSL lightness of `$color` by that amount. @@ -285,7 +795,7 @@ title: sass:color 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). + [`color.adjust($color, $lightness: -$amount, $space: hsl)`](#adjust). {% codeExample 'color-darken' %} @use 'sass:color'; @@ -330,6 +840,10 @@ title: sass:color {% function 'desaturate($color, $amount)', 'returns:color' %} Makes `$color` less saturated. + The `$color` must be in a [legacy color space]. + + [legacy color space]: /documentation/values/colors#legacy-color-spaces + The `$amount` must be a number between `0%` and `100%` (inclusive). Decreases the HSL saturation of `$color` by that amount. @@ -341,7 +855,8 @@ title: sass:color 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). + can be written [`color.adjust($color, $saturation: -$amount, $space: + hsl)`](#adjust). {% codeExample 'color-desaturate' %} @use 'sass:color'; @@ -385,39 +900,18 @@ title: sass:color {% endcodeExample %} {% endfunction %} -{% function 'color.grayscale($color)', 'grayscale($color)', 'returns:color' %} - Returns a gray color with the same lightness as `$color`. - - This is identical to [`color.change($color, $saturation: 0%)`](#change). - - {% codeExample 'color-grayscale' %} - @use 'sass:color'; - - @debug color.grayscale(#6b717f); // #757575 - @debug color.grayscale(#d2e1dd); // #dadada - @debug color.grayscale(#036); // #333333 - === - @use 'sass:color' - - @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' %} Returns the green channel of `$color` as a number between 0 and 255. - See also: + The `$color` must be in a [legacy color space]. - * [`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. + [legacy color space]: /documentation/values/colors#legacy-color-spaces + + {% headsUp %} + Because `color.green()` is redundant with [`color.channel()`](#channel), + it's no longer recommended. Instead of `color.green($color)`, you can write + [`color.channel($color, "green")`](#channel). + {% endheadsUp %} {% codeExample 'color-green' %} @use 'sass:color'; @@ -437,16 +931,15 @@ title: sass:color {% function 'color.hue($color)', 'hue($color)', 'returns:number' %} Returns the hue of `$color` as a number between `0deg` and `360deg`. - See also: + The `$color` must be in a [legacy color space]. - * [`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. + [legacy color space]: /documentation/values/colors#legacy-color-spaces + + {% headsUp %} + Because `color.hue()` is redundant with [`color.channel()`](#channel), it's + no longer recommended. Instead of `color.hue($color)`, you can write + [`color.channel($color, "hue")`](#channel). + {% endheadsUp %} {% codeExample 'color-hue' %} @use 'sass:color'; @@ -463,95 +956,13 @@ title: sass:color {% 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 %} - - 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 - - {% 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' %} - @use 'sass:color'; - - @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) - === - @use 'sass:color' - - @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' %} - Returns an unquoted string that represents `$color` in the `#AARRGGBB` format - expected by Internet Explorer's [`-ms-filter`][] property. - - [`-ms-filter`]: https://learn.microsoft.com/en-us/previous-versions/ms530752(v=vs.85) - - {% codeExample 'color-ie-hex-str' %} - @use 'sass:color'; - - @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 - === - @use 'sass:color' - - @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' %} - 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`. - - {% codeExample 'color-invert' %} - @use 'sass:color'; - - @debug color.invert(#b37399); // #4c8c66 - @debug color.invert(black); // white - @debug color.invert(#550e0c, 20%); // #663b3a - === - @use 'sass:color' - - @debug color.invert(#b37399) // #4c8c66 - @debug color.invert(black) // white - @debug color.invert(#550e0c, 20%) // #663b3a - {% endcodeExample %} -{% endfunction %} - {% function 'lighten($color, $amount)', 'returns:color' %} Makes `$color` lighter. + The `$color` must be in a [legacy color space]. + + [legacy color space]: /documentation/values/colors#legacy-color-spaces + The `$amount` must be a number between `0%` and `100%` (inclusive). Increases the HSL lightness of `$color` by that amount. @@ -563,7 +974,7 @@ title: sass:color 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). + [`adjust($color, $lightness: $amount, $space: hsl)`](#adjust). {% codeExample 'color-lighten' %} @use 'sass:color'; @@ -608,16 +1019,15 @@ title: sass:color {% function 'color.lightness($color)', 'lightness($color)', 'returns:number' %} Returns the HSL lightness of `$color` as a number between `0%` and `100%`. - See also: + The `$color` must be in a [legacy color space]. - * [`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. + [legacy color space]: /documentation/values/colors#legacy-color-spaces + + {% headsUp %} + Because `color.lightness()` is redundant with [`color.channel()`](#channel), + it's no longer recommended. Instead of `color.lightness($color)`, you can write + [`color.channel($color, "lightness")`](#channel). + {% endheadsUp %} {% codeExample 'color-lightness' %} @use 'sass:color'; @@ -634,35 +1044,13 @@ title: sass:color {% endcodeExample %} {% endfunction %} -{% function 'color.mix($color1, $color2, $weight: 50%)', 'mix($color1, $color2, $weight: 50%)', 'returns:color' %} - 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. - - {% codeExample 'color-mix' %} - @use 'sass:color'; - - @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) - === - @use 'sass:color' - - @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' %} Makes `$color` more opaque. + The `$color` must be in a [legacy color space]. + + [legacy color space]: /documentation/values/colors#legacy-color-spaces + The `$amount` must be a number between `0` and `1` (inclusive). Increases the alpha channel of `$color` by that amount. @@ -711,16 +1099,15 @@ title: sass:color {% function 'color.red($color)', 'red($color)', 'returns:number' %} Returns the red channel of `$color` as a number between 0 and 255. - See also: + The `$color` must be in a [legacy color space]. + + [legacy color space]: /documentation/values/colors#legacy-color-spaces - * [`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. + {% headsUp %} + Because `color.red()` is redundant with [`color.channel()`](#channel), it's + no longer recommended. Instead of `color.red($color)`, you can write + [`color.channel($color, "red")`](#channel). + {% endheadsUp %} {% codeExample 'color-red' %} @use 'sass:color'; @@ -740,6 +1127,10 @@ title: sass:color {% function 'color.saturate($color, $amount)', 'saturate($color, $amount)', 'returns:color' %} Makes `$color` more saturated. + The `$color` must be in a [legacy color space]. + + [legacy color space]: /documentation/values/colors#legacy-color-spaces + The `$amount` must be a number between `0%` and `100%` (inclusive). Increases the HSL saturation of `$color` by that amount. @@ -751,7 +1142,7 @@ title: sass:color 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). + be written [`adjust($color, $saturation: $amount, $space: hsl)`](#adjust). {% codeExample 'color-saturate' %} @use 'sass:color'; @@ -798,16 +1189,16 @@ title: sass:color {% function 'color.saturation($color)', 'saturation($color)', 'returns:number' %} Returns the HSL saturation of `$color` as a number between `0%` and `100%`. - See also: + The `$color` must be in a [legacy color space]. - * [`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. + [legacy color space]: /documentation/values/colors#legacy-color-spaces + + {% headsUp %} + Because `color.saturation()` is redundant with + [`color.channel()`](#channel), it's no longer recommended. Instead of + `color.saturation($color)`, you can write + [`color.channel($color, "saturation")`](#channel). + {% endheadsUp %} {% codeExample 'color-saturation' %} @use 'sass:color'; @@ -824,57 +1215,13 @@ title: sass:color {% 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 %} - - 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. - - {% codeExample 'color-scale' %} - @use 'sass:color'; - - @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) - === - @use 'sass:color' - - @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' %} Makes `$color` more transparent. + The `$color` must be in a [legacy color space]. + + [legacy color space]: /documentation/values/colors#legacy-color-spaces + The `$amount` must be a number between `0` and `1` (inclusive). Decreases the alpha channel of `$color` by that amount. @@ -887,8 +1234,8 @@ title: sass:color 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). + $amount)` can be written [`color.adjust($color, $alpha: -$amount, + $space: hsl)`](#adjust). {% codeExample 'transparentize' %} @use 'sass:color'; @@ -925,19 +1272,19 @@ title: sass:color {% function 'color.whiteness($color)', 'returns:number' %} {% compatibility 'dart: "1.28.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %} - Returns the [HWB][] whiteness of `$color` as a number between `0%` and `100%`. + Returns the [HWB] whiteness of `$color` as a number between `0%` and `100%`. [HWB]: https://en.wikipedia.org/wiki/HWB_color_model - See also: + The `$color` must be in a [legacy color space]. - * [`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. + [legacy color space]: /documentation/values/colors#legacy-color-spaces + + {% headsUp %} + Because `color.whiteness()` is redundant with [`color.channel()`](#channel), + it's no longer recommended. Instead of `color.whiteness($color)`, you can + write [`color.channel($color, "whiteness")`](#channel). + {% endheadsUp %} {% codeExample 'color-whiteness' %} @use 'sass:color'; diff --git a/source/documentation/modules/index.md b/source/documentation/modules/index.md index 76e1c3a23..39fe9329f 100644 --- a/source/documentation/modules/index.md +++ b/source/documentation/modules/index.md @@ -83,6 +83,46 @@ Sass provides the following built-in modules: ## Global Functions +{% funFact %} + You can pass [special functions] like `calc()` or `var()` in place of any + argument to a global color constructor. 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 + + {% codeExample 'color-special', false %} + @debug rgb(0 51 102 / var(--opacity)); // rgb(0 51 102 / var(--opacity)) + @debug color(display-p3 var(--peach)); // color(display-p3 var(--peach)) + === + @debug rgb(0 51 102 / var(--opacity)) // rgb(0 51 102 / var(--opacity)) + @debug color(display-p3 var(--peach)) // color(display-p3 var(--peach)) + {% endcodeExample %} +{% endfunFact %} + +{% function 'color($space $channel1 $channel2 $channel3)', 'color($space $channel1 $channel2 $channel3 / $alpha)', 'returns:color' %} + {% compatibility 'dart: "1.78.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %} + + Returns a color in the given color space with the given channel values. + + This supports the color spaces `srgb`, `srgb-linear`, `display-p3`, `a98-rgb`, + `prophoto-rgb`, `rec2020`, `xyz`, and `xyz-d50`, as well as `xyz-d65` which is + an alias for `xyz`. For all spaces, the channels are numbers between 0 and 1 + (inclusive) or percentages between `0%` and `100%` (inclusive). + + If any color channel is outside the range 0 to 1, this represents a color + outside the standard gamut for its color space. + + {% codeExample 'hsl', false %} + @debug color(srgb 0.1 0.6 1); // color(srgb 0.1 0.6 1) + @debug color(xyz 30% 0% 90% / 50%); // color(xyz 0.3 0 0.9 / 50%) + === + @debug color(srgb 0.1 0.6 1) // color(srgb 0.1 0.6 1) + @debug color(xyz 30% 0% 90% / 50%) // color(xyz 0.3 0 0.9 / 50%) + {% endcodeExample %} +{% endfunction %} + {% 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: @@ -106,28 +146,15 @@ Sass provides the following built-in modules: [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). - - {% funFact %} - 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 - - {% 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 %} + unitless. The saturation and lightness are typically 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). + + A hue outside `0deg` and `360deg` is equivalent to `$hue % 360deg`. A + saturation less than `0%` is clamped to `0%`. A saturation above `100%` or a + lightness outside `0%` and `100%` are both allowed, and represent colors + outside the standard RGB gamut. {% headsUp %} Sass's [special parsing rules][] for slash-separated values make it @@ -140,14 +167,65 @@ Sass provides the following built-in modules: {% 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 hsla(34, 35%, 92%, 0.2); // rgba(241.74, 235.552, 227.46, 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) + @debug hsla(34, 35%, 92%, 0.2) // rgba(241.74, 235.552, 227.46, 0.2) + {% endcodeExample %} +{% endfunction %} + +{% function 'if($condition, $if-true, $if-false)' %} + 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 + + {% 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 'hwb($hue $whiteness $blackness)', 'hwb($hue $whiteness $blackness / $alpha)', 'color.hwb($hue $whiteness $blackness)', 'color.hwb($hue $whiteness $blackness / $alpha)', 'color.hwb($hue, $whiteness, $blackness, $alpha: 1)', 'returns:color' %} + {% compatibility 'dart: "1.78.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %} + + 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) and may be + unitless. The whiteness and blackness are numbers typically 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). + + A hue outside `0deg` and `360deg` is equivalent to `$hue % 360deg`. If + `$whiteness + $blackness > 100%`, the two values are scaled so that they add + up to `100%`. If `$whiteness`, `$blackness`, or both are less than `0%`, this + represents a color outside the standard RGB gamut. + + {% headsUp %} + The `color.hwb()` variants are deprecated. New Sass code should use the + global `hwb()` function instead. + {% endheadsUp %} + + {% codeExample 'hwb', false %} + @debug hwb(210deg 0% 60%); // #036 + @debug hwb(210 0% 60% / 0.5); // rgba(0, 51, 102, 0.5) + === + @debug hwb(210deg 0% 60%) // #036 + @debug hwb(210 0% 60% / 0.5) // rgba(0, 51, 102, 0.5) {% endcodeExample %} {% endfunction %} @@ -171,6 +249,129 @@ Sass provides the following built-in modules: {% endcodeExample %} {% endfunction %} +{% function 'lab($lightness $a $b)', 'lab($lightness $a $b / $alpha)', 'returns:color' %} + {% compatibility 'dart: "1.78.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %} + + Returns a color with the given [lightness, a, b], and alpha channels. + + [hue, whiteness, and blackness]: https://en.wikipedia.org/wiki/CIELAB_color_space + + The lightness is a number between `0%` and `100%` (inclusive) and may be + unitless. The a and b channels can be specified as either [unitless] numbers + between -125 and 125 (inclusive), or percentages between `-100%` 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 + + A lightness outside the range `0%` and `100%` is clamped to be within that + range. If the a or b channels are outside the range `-125` to `125`, this + represents a color outside the standard CIELAB gamut. + + {% codeExample 'lab', false %} + @debug lab(50% -20 30); // lab(50% -20 30) + @debug lab(80% 0% 20% / 0.5); // lab(80% 0 25 / 0.5); + === + @debug lab(50% -20 30) // lab(50% -20 30) + @debug lab(80% 0% 20% / 0.5) // lab(80% 0 25 / 0.5); + {% endcodeExample %} +{% endfunction %} + +{% function 'lch($lightness $chroma $hue)', 'lch($lightness $chroma $hue / $alpha)', 'returns:color' %} + {% compatibility 'dart: "1.78.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %} + + Returns a color with the given [lightness, chroma, and hue], and the given + alpha channel. + + [hue, whiteness, and blackness]: https://en.wikipedia.org/wiki/CIELAB_color_space#Cylindrical_model + + The lightness is a number between `0%` and `100%` (inclusive) and may be + unitless. The chroma channel can be specified as either a [unitless] number + between 0 and 150 (inclusive), or a percentage between `0%` and `100%` + (inclusive). The hue is a number between `0deg` and `360deg` (inclusive) and + may 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). + + [unitless]: /documentation/values/numbers#units + + A lightness outside the range `0%` and `100%` is clamped to be within that + range. A chroma below 0 is clamped to 0, and a chroma above 150 represents a + color outside the standard CIELAB gamut. A hue outside `0deg` and `360deg` is + equivalent to `$hue % 360deg`. + + {% codeExample 'lch', false %} + @debug lch(50% 10 270deg); // lch(50% 10 270deg) + @debug lch(80% 50% 0.2turn / 0.5); // lch(80% 75 72deg / 0.5); + === + @debug lch(50% 10 270deg) // lch(50% 10 270deg) + @debug lch(80% 50% 0.2turn / 0.5) // lch(80% 75 72deg / 0.5); + {% endcodeExample %} +{% endfunction %} + +{% function 'oklab($lightness $a $b)', 'oklab($lightness $a $b / $alpha)', 'returns:color' %} + {% compatibility 'dart: "1.78.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %} + + Returns a color with the given [perceptually-uniform lightness, a, b], and + alpha channels. + + [perceptually-uniform lightness, a, b]: https://bottosson.github.io/posts/oklab/ + + The lightness is a number between `0%` and `100%` (inclusive) and may be + unitless. The a and b channels can be specified as either [unitless] numbers + between -0.4 and 0.4 (inclusive), or percentages between `-100%` 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 + + A lightness outside the range `0%` and `100%` is clamped to be within that + range. If the a or b channels are outside the range `-0.4` to `0.4`, this + represents a color outside the standard Oklab gamut. + + {% codeExample 'oklab', false %} + @debug oklab(50% -0.1 0.15); // oklab(50% -0.1 0.15) + @debug oklab(80% 0% 20% / 0.5); // oklab(80% 0 0.08 / 0.5) + === + @debug oklab(50% -0.1 0.15) // oklab(50% -0.1 0.15) + @debug oklab(80% 0% 20% / 0.5) // oklab(80% 0 0.08 / 0.5) + {% endcodeExample %} +{% endfunction %} + +{% function 'oklch($lightness $chroma $hue)', 'oklch($lightness $chroma $hue / $alpha)', 'returns:color' %} + {% compatibility 'dart: "1.78.0"', 'libsass: false', 'ruby: false' %}{% endcompatibility %} + + Returns a color with the given [perceptually-uniform lightness, chroma, and + hue], and the given alpha channel. + + [hue, whiteness, and blackness]: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch + + The lightness is a number between `0%` and `100%` (inclusive) and may be + unitless. The chroma channel can be specified as either a [unitless] number + between 0 and 0.4 (inclusive), or a percentage between `0%` and `100%` + (inclusive). The hue is a number between `0deg` and `360deg` (inclusive) and + may 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). + + [unitless]: /documentation/values/numbers#units + + A lightness outside the range `0%` and `100%` is clamped to be within that + range. A chroma below 0 is clamped to 0, and a chroma above 0.4 represents a + color outside the standard Oklab gamut. A hue outside `0deg` and `360deg` is + equivalent to `$hue % 360deg`. + + {% codeExample 'oklch', false %} + @debug oklch(50% 0.3 270deg); // oklch(50% 0.3 270deg) + @debug oklch(80% 50% 0.2turn / 0.5); // oklch(80% 0.2 72deg / 0.5); + === + @debug oklch(50% 0.3 270deg) // oklch(50% 0.3 270deg) + @debug oklch(80% 50% 0.2turn / 0.5) // oklch(80% 0.2 72deg / 0.5); + {% 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: @@ -192,30 +393,15 @@ Sass provides the following built-in modules: 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 + 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 - {% funFact %} - 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 - - {% 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 %} + If any color channel is outside the range 0 to 255, this represents a color + outside the standard RGB gamut. {% headsUp %} Sass's [special parsing rules][] for slash-separated values make it diff --git a/source/documentation/operators/equality.md b/source/documentation/operators/equality.md index bbee2c5d7..ac66ba556 100644 --- a/source/documentation/operators/equality.md +++ b/source/documentation/operators/equality.md @@ -23,7 +23,9 @@ different types: their values are equal when their units are converted between one another. * [Strings][] are unusual in that [unquoted][] and [quoted][] strings with the same contents are considered equal. -* [Colors][] are equal if they have the same red, green, blue, and alpha values. +* [Colors] are equal if they're in the same [color space] and have the same + channel values, *or* if they're both in [legacy color spaces] and have the + same RGBA channel values. * [Lists][] are equal if their contents are equal. Comma-separated lists aren't equal to space-separated lists, and bracketed lists aren't equal to unbracketed lists. @@ -40,6 +42,8 @@ different types: [quoted]: /documentation/values/strings#quoted [unquoted]: /documentation/values/strings#unquoted [Colors]: /documentation/values/colors +[color space]: /documentation/values/colors#color-spaces +[legacy color spaces]: /documentation/values/colors#legacy-color-spaces [Lists]: /documentation/values/lists [`true`, `false`]: /documentation/values/booleans [`null`]: /documentation/values/null diff --git a/source/documentation/values/colors.md b/source/documentation/values/colors.md index 0309d1041..227fa2c8c 100644 --- a/source/documentation/values/colors.md +++ b/source/documentation/values/colors.md @@ -1,77 +1,347 @@ --- title: Colors +table_of_contents: true --- -{% compatibility 'dart: "1.14.0"', 'libsass: "3.6.0"', 'ruby: "3.6.0"', 'feature: "Level 4 Syntax"' %} +{% compatibility 'dart: "1.78.0"', 'libsass: false', 'ruby: false', 'feature: "Color Spaces"' %} + LibSass, Ruby Sass, and older versions of Dart Sass don't support color spaces + other than `rgb` and `hsl`. + + As well as to adding support for new color spaces, this release changed some + details of the way colors were handled. In particular, even the legacy `rgb` + and `hsl` color spaces are no longer clamped to their gamuts; it's now + possible to represent `rgb(500 0 0)` or other out-of-bounds values. In + addition, `rgb` colors are no longer rounded to the nearest integer because + the CSS spec now requires implementations to maintain precision wherever + possible. +{% endcompatibility %} + +{% compatibility 'dart: "1.14.0"', 'libsass: false', 'ruby: "3.6.0"', 'feature: "Level 4 Syntax"' %} LibSass and older versions of Dart or Ruby Sass don't support [hex colors with an alpha channel][]. [hex colors with an alpha channel]: https://drafts.csswg.org/css-color/#hex-notation {% endcompatibility %} -Sass has built-in support for color values. Just like CSS colors, they represent -points in the [sRGB color space][], although many Sass [color functions][] -operate using [HSL coordinates][] (which are just another way of expressing sRGB -colors). Sass colors can be written as hex codes (`#f2ece4` or `#b37399aa`), -[CSS color names][] (`midnightblue`, `transparent`), or the functions -[`rgb()`][], [`rgba()`][], [`hsl()`][], and [`hsla()`][]. +Sass has built-in support for color values. Just like CSS colors, each color +represents a point in a particular color space such as `rgb` or `lab`. Sass +colors can be written as hex codes (`#f2ece4` or `#b37399aa`), [CSS color names] +(`midnightblue`, `transparent`), or color functions like [`rgb()`], [`lab()`], +or [`color()`]. [sRGB color space]: https://en.wikipedia.org/wiki/SRGB [color functions]: /documentation/modules/color -[HSL coordinates]: https://en.wikipedia.org/wiki/HSL_and_HSV [CSS color names]: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#Color_keywords [`rgb()`]: /documentation/modules#rgb -[`rgba()`]: /documentation/modules#rgba -[`hsl()`]: /documentation/modules#hsl -[`hsla()`]: /documentation/modules#hsla +[`lab()`]: /documentation/modules#lab +[`color()`]: /documentation/modules#color {% codeExample 'colors', false %} @debug #f2ece4; // #f2ece4 @debug #b37399aa; // rgba(179, 115, 153, 67%) @debug midnightblue; // #191970 - @debug rgb(204, 102, 153); // #c69 - @debug rgba(107, 113, 127, 0.8); // rgba(107, 113, 127, 0.8) - @debug hsl(228, 7%, 86%); // #dadbdf - @debug hsla(20, 20%, 85%, 0.7); // rgb(225, 215, 210, 0.7) + @debug rgb(204 102 153); // #c69 + @debug lab(32.4% 38.4 -47.7 / 0.7); // lab(32.4% 38.4 -47.7 / 0.7) + @debug color(display-p3 0.597 0.732 0.576); // color(display-p3 0.597 0.732 0.576) === @debug #f2ece4 // #f2ece4 @debug #b37399aa // rgba(179, 115, 153, 67%) @debug midnightblue // #191970 - @debug rgb(204, 102, 153) // #c69 - @debug rgba(107, 113, 127, 0.8) // rgba(107, 113, 127, 0.8) - @debug hsl(228, 7%, 86%) // #dadbdf - @debug hsla(20, 20%, 85%, 0.7) // rgb(225, 215, 210, 0.7) + @debug rgb(204 102 153) // #c69 + @debug lab(32.4% 38.4 -47.7 / 0.7) // lab(32.4% 38.4 -47.7 / 0.7) + @debug color(display-p3 0.597 0.732 0.576) // color(display-p3 0.597 0.732 0.576) {% endcodeExample %} -{% funFact %} - No matter how a Sass color is originally written, it can be used with both - HSL-based and RGB-based functions! -{% endfunFact %} +## Color Spaces + +Sass supports the same set of color spaces as CSS. A Sass color will always be +emitted in the same color space it was written in unless it's in a [legacy color +space] or you convert it to another space using [`color.to-space()`]. All the +other color functions in Sass will always return a color in the same spaces as +the original color, even if the function made changes to that color in another +space. + +[legacy color space]: #legacy-color-spaces +[`color.to-space()`]: /documentation/modules/color#to-space + +Although each color space has bounds on the gamut it expects for its channels, +Sass can represent out-of-gamut values for any color space. This allows a color +from a wide-gamut space to be safely converted into and back out of a +narrow-gamut space without losing information. + +{% headsUp %} + CSS requires that some color functions clip their input channels. For example, + `rgb(500 0 0)` clips its red channel to be within [0, 255] and so is + equivalent to `rgb(255 0 0)` even though `rgb(500 0 0)` is a distinct value + that Sass can represent. You can always use Sass's [`color.change()`] function + to set an out-of-gamut value for any space. + + [`color.change()`]: /documentation/modules/color#change +{% endheadsUp %} + +Following is a full list of all the color spaces Sass supports. You can read +learn about these spaces [on MDN]. + +[on MDN]: https://developer.mozilla.org/en-US/docs/Glossary/Color_space + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SpaceSyntaxChannels [min, max]
rgb* + rgb(102 51 153)
+ #663399
+ rebeccapurple +
+ red [0, 255]; + green [0, 255]; + blue [0, 255] +
hsl*hsl(270 50% 40%) + hue [0, 360]; + saturation [0%, 100%]; + lightness [0%, 100%] +
hwb*hwb(270 20% 40%) + hue [0, 360]; + whiteness [0%, 100%]; + blackness [0%, 100%] +
srgbcolor(srgb 0.4 0.2 0.6) + red [0, 1]; + green [0, 1]; + blue [0, 1] +
srgb-linearcolor(srgb-linear 0.133 0.033 0.319) + red [0, 1]; + green [0, 1]; + blue [0, 1] +
display-p3color(display-p3 0.374 0.21 0.579) + red [0, 1]; + green [0, 1]; + blue [0, 1] +
a98-rgbcolor(a98-rgb 0.358 0.212 0.584) + red [0, 1]; + green [0, 1]; + blue [0, 1] +
prophoto-rgbcolor(prophoto-rgb 0.316 0.191 0.495) + red [0, 1]; + green [0, 1]; + blue [0, 1] +
rec2020color(rec2020 0.305 0.168 0.531) + red [0, 1]; + green [0, 1]; + blue [0, 1] +
xyz, xyz-d65 + color(xyz 0.124 0.075 0.309)
+ color(xyz-d65 0.124 0.075 0.309) +
+ x [0, 1]; + y [0, 1]; + z [0, 1] +
xyz-d50color(xyz-d50 0.116 0.073 0.233) + x [0, 1]; + y [0, 1]; + z [0, 1] +
lablab(32.4% 38.4 -47.7) + lightness [0%, 100%]; + a [-125, 125]; + b [-125, 125] +
lchlch(32.4% 61.2 308.9deg) + lightness [0%, 100%]; + chroma [0, 150]; + hue [0deg, 360deg] +
oklaboklab(44% 0.088 -0.134) + lightness [0%, 100%]; + a [-0.4, 0.4]; + b [-0.4, 0.4] +
oklchoklch(44% 0.16 303.4deg) + lightness [0%, 100%]; + chroma [0, 0.4]; + hue [0deg, 360deg] +
+ +Spaces marked with * are [legacy color spaces]. + +[legacy color spaces]: #legacy-color-spaces + +## Missing Channels + +Colors in CSS and Sass can have "missing channels", which are written `none` and +represent a channel whose value isn't known or doesn't affect the way the color +is rendered. For example, you might write `hsl(none 0% 50%)`, because the hue +doesn't matter if the saturation is `0%`. In most cases, missing channels are +just treated as 0 values, but they do come up occasionally: + +* If you're mixing colors together, either as part of CSS interpolation for + something like an animation or using Sass's [`color.mix()`] function, missing + channels always take on the other color's value for that channel if possible. + + [`color.mix()`]: /documentation/modules/color#mix + +* If you convert a color with a missing channel to another space that has an + analogous channel, that channel will be set to `none` after the conversion is + complete. + +Although [`color.channel()`] will return 0 for missing channels, you can always +check for them using [`color.is-missing()`]. -CSS supports many different formats that can all represent the same color: its -name, its hex code, and [functional notation][]. Which format Sass chooses to -compile a color to depends on the color itself, how it was written in the -original stylesheet, and the current output mode. Because it can vary so much, -stylesheet authors shouldn't rely on any particular output format for colors -they write. +[`color.channel()`]: /documentation/modules/color#channel +[`color.is-missing()`]: /documentation/modules/color#is-missing -[functional notation]: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value +{% codeExample 'missing-channels', false %} + @use 'sass:color'; -Sass supports many useful [color functions][] that can be used to create new -colors based on existing ones by [mixing colors together][] or [scaling their -hue, saturation, or lightness][]. + $grey: hsl(none 0% 50%); + + @debug color.mix($grey, blue, $method: hsl); // hsl(240, 50%, 50%) + @debug color.to-space($grey, lch); // lch(53.3889647411% 0 none) + === + @use 'sass:color' + + $grey: hsl(none 0% 50%) + + @debug color.mix($grey, blue, $method: hsl) // hsl(240, 50%, 50%) + @debug color.to-space($grey, lch) // lch(53.3889647411% 0 none) +{% endcodeExample %} + +### Powerless Channels + +A color channel is considered "powerless" under certain circumstances its value +doesn't affect the way the color is rendered on screen. The CSS spec requires +that when a color is converted to a new space, any powerless channels are +replaced by `none`. Sass does this in all cases except conversions to legacy +spaces, to guarantee that converting to a legacy space always produces a color +that's compatible with older browsers. + +For more details on powerless channels, see [`color.is-powerless()`]. + +[`color.is-powerless()`]: /documentation/modules/color#is-powerless + +## Legacy Color Spaces + +Historically, CSS and Sass only supported the standard RGB gamut, and only +supported the `rgb`, `hsl`, and `hwb` functions for defining colors. Because at +the time all colors used the same gamut, every color function worked with every +color regardless of its color space. Sass still preserves this behavior, but +only for older functions and only for colors in these three "legacy" color +spaces. Even so, it's still a good practice to explicitly specify the `$space` +you want to work in when using color functions. + +Sass will also freely convert between different legacy color spaces when +converting legacy color values to CSS. This is always safe, because they all use +the same underlying color model, and this helps ensure that Sass emits colors in +as compatible a format as possible. + +## Color Functions + +Sass supports many useful [color functions] that can be used to create new +colors based on existing ones by [mixing colors together] or [scaling their +channel values]. When calling color functions, color spaces should always be +written as unquoted strings to match CSS, while channel names should be written +as quoted strings so that channels like `"red"` aren't parsed as color values. [mixing colors together]: /documentation/modules/color#mix -[scaling their hue, saturation, or lightness]: /documentation/modules/color#scale +[scaling their channel values]: /documentation/modules/color#scale + +{% funFact %} + Sass color functions can automatically convert colors between spaces, which + makes it easy to do transformations in perceptually-uniform color spaces like + Oklch. But they'll *always* return a color in the same space you gave it, + unless you explicitly call [`color.to-space()`] to convert it. + + [`color.to-space()`]: /documentation/modules/color#to-space +{% endfunFact %} {% codeExample 'color-formats', false %} + @use 'sass:color'; + $venus: #998099; - @debug scale-color($venus, $lightness: +15%); // #a893a8 - @debug mix($venus, midnightblue); // #594d85 + @debug color.scale($venus, $lightness: +15%, $space: oklch); + // rgb(170.1523703626, 144.612080603, 170.1172627174) + @debug color.mix($venus, midnightblue, $method: oklch); + // rgb(95.9363315581, 74.5687109346, 133.2082569526) === + @use 'sass:color' + $venus: #998099 - @debug scale-color($venus, $lightness: +15%) // #a893a8 - @debug mix($venus, midnightblue) // #594d85 + @debug color.scale($venus, $lightness: +15%, $space: oklch) + // rgb(170.1523703626, 144.612080603, 170.1172627174) + @debug color.mix($venus, midnightblue, $method: oklch) + // rgb(95.9363315581, 74.5687109346, 133.2082569526) {% endcodeExample %}