Skip to content

Commit f208023

Browse files
committed
Document new color spaces
See #672
1 parent 3a50fc0 commit f208023

File tree

2 files changed

+296
-38
lines changed

2 files changed

+296
-38
lines changed

source/assets/sass/visual-design/_theme.scss

+4
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ body {
3232
color: var(--text, var(--sl-color--pale-sky));
3333
}
3434

35+
.fade {
36+
opacity: 0.7;
37+
}
38+
3539
::selection {
3640
background: var(--sl-color--iron);
3741
}

source/documentation/values/colors.md

+292-38
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,331 @@
11
---
22
title: Colors
3+
table_of_contents: true
34
---
45

5-
{% compatibility 'dart: "1.14.0"', 'libsass: "3.6.0"', 'ruby: "3.6.0"', 'feature: "Level 4 Syntax"' %}
6+
{% compatibility 'dart: "1.78.0"', 'libsass: false', 'ruby: false', 'feature: "Color Spaces"' %}
7+
LibSass, Ruby Sass, and older versions of Dart Sass don't support color spaces
8+
other than `rgb` and `hsl`.
9+
{% endcompatibility %}
10+
11+
{% compatibility 'dart: "1.14.0"', 'libsass: false', 'ruby: "3.6.0"', 'feature: "Level 4 Syntax"' %}
612
LibSass and older versions of Dart or Ruby Sass don't support [hex colors with
713
an alpha channel][].
814

915
[hex colors with an alpha channel]: https://drafts.csswg.org/css-color/#hex-notation
1016
{% endcompatibility %}
1117

12-
Sass has built-in support for color values. Just like CSS colors, they represent
13-
points in the [sRGB color space][], although many Sass [color functions][]
14-
operate using [HSL coordinates][] (which are just another way of expressing sRGB
15-
colors). Sass colors can be written as hex codes (`#f2ece4` or `#b37399aa`),
16-
[CSS color names][] (`midnightblue`, `transparent`), or the functions
17-
[`rgb()`][], [`rgba()`][], [`hsl()`][], and [`hsla()`][].
18+
Sass has built-in support for color values. Just like CSS colors, each color
19+
represents a point in a particular color space such as `rgb` or `lab`. Sass
20+
colors can be written as hex codes (`#f2ece4` or `#b37399aa`), [CSS color names]
21+
(`midnightblue`, `transparent`), or color functions like [`rgb()`], [`lab()`],
22+
or [`color()`].
1823

1924
[sRGB color space]: https://en.wikipedia.org/wiki/SRGB
2025
[color functions]: /documentation/modules/color
21-
[HSL coordinates]: https://en.wikipedia.org/wiki/HSL_and_HSV
2226
[CSS color names]: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#Color_keywords
2327
[`rgb()`]: /documentation/modules#rgb
24-
[`rgba()`]: /documentation/modules#rgba
25-
[`hsl()`]: /documentation/modules#hsl
26-
[`hsla()`]: /documentation/modules#hsla
28+
[`lab()`]: /documentation/modules#lab
29+
[`color()`]: /documentation/modules#color
2730

2831
{% codeExample 'colors', false %}
2932
@debug #f2ece4; // #f2ece4
3033
@debug #b37399aa; // rgba(179, 115, 153, 67%)
3134
@debug midnightblue; // #191970
32-
@debug rgb(204, 102, 153); // #c69
33-
@debug rgba(107, 113, 127, 0.8); // rgba(107, 113, 127, 0.8)
34-
@debug hsl(228, 7%, 86%); // #dadbdf
35-
@debug hsla(20, 20%, 85%, 0.7); // rgb(225, 215, 210, 0.7)
35+
@debug rgb(204 102 153); // #c69
36+
@debug lab(32.4% 38.4 -47.7 / 0.7); // lab(32.4% 38.4 -47.7 / 0.7)
37+
@debug color(display-p3 0.597 0.732 0.576); // color(display-p3 0.597 0.732 0.576)
3638
===
3739
@debug #f2ece4 // #f2ece4
3840
@debug #b37399aa // rgba(179, 115, 153, 67%)
3941
@debug midnightblue // #191970
40-
@debug rgb(204, 102, 153) // #c69
41-
@debug rgba(107, 113, 127, 0.8) // rgba(107, 113, 127, 0.8)
42-
@debug hsl(228, 7%, 86%) // #dadbdf
43-
@debug hsla(20, 20%, 85%, 0.7) // rgb(225, 215, 210, 0.7)
42+
@debug rgb(204 102 153) // #c69
43+
@debug lab(32.4% 38.4 -47.7 / 0.7) // lab(32.4% 38.4 -47.7 / 0.7)
44+
@debug color(display-p3 0.597 0.732 0.576) // color(display-p3 0.597 0.732 0.576)
4445
{% endcodeExample %}
4546

46-
{% funFact %}
47-
No matter how a Sass color is originally written, it can be used with both
48-
HSL-based and RGB-based functions!
49-
{% endfunFact %}
47+
## Color Spaces
48+
49+
Sass supports the same set of color spaces as CSS. A Sass color will always be
50+
emitted in the same color space it was written in unless it's in a [legacy color
51+
space] or you convert it to another space using [`color.to-space()`]. All the
52+
other color functions in Sass will always return a color in the same spaces as
53+
the original color, even if the function made changes to that color in another
54+
space.
55+
56+
[legacy color space]: #legacy-color-spaces
57+
[`color.to-space()`]: /documentation/modules/color#to-space
58+
59+
Although each color space has bounds on the gamut it expects for its channels,
60+
Sass can represent out-of-gamut values for any color space. This allows a color
61+
from a wide-gamut space to be safely converted into and back out of a
62+
narrow-gamut space without losing information.
63+
64+
{% headsUp %}
65+
CSS requires that some color functions clip their input channels. For example,
66+
`rgb(500 0 0)` clips its red channel to be within [0, 255] and so is
67+
equivalent to `rgb(255 0 0)` even though `rgb(500 0 0)` is a distinct value
68+
that Sass can represent. You can always use Sass's [`color.change()`] function
69+
to set an out-of-gamut value for any space.
70+
71+
[`color.change()`]: /documentation/modules/color#change
72+
{% endheadsUp %}
73+
74+
Following is a full list of all the color spaces Sass supports. You can read
75+
learn about these spaces [on MDN].
76+
77+
[on MDN]: https://developer.mozilla.org/en-US/docs/Glossary/Color_space
78+
79+
<table class="sl-c-table">
80+
<tr>
81+
<th scope="col">Space</th>
82+
<th scope="col">Syntax</th>
83+
<th scope="col">Channels [min, max]</th>
84+
</tr>
85+
<tr>
86+
<th scope="row"><code>rgb</code>*</th>
87+
<td>
88+
<code>rgb(102 51 153)</code><br>
89+
<code>#663399</code><br>
90+
<code>rebeccapurple</code>
91+
</td>
92+
<td>
93+
red <span class="fade">[0, 255]</span>;
94+
green <span class="fade">[0, 255]</span>;
95+
blue <span class="fade">[0, 255]</span>
96+
</td>
97+
</tr>
98+
<tr>
99+
<th scope="row"><code>hsl</code>*</th>
100+
<td><code>hsl(270 50% 40%)</code></td>
101+
<td>
102+
hue <span class="fade">[0, 360]</span>;
103+
saturation <span class="fade">[0%, 100%]</span>;
104+
lightness <span class="fade">[0%, 100%]</span>
105+
</td>
106+
</tr>
107+
<tr>
108+
<th scope="row"><code>hwb</code>*</th>
109+
<td><code>hwb(270 20% 40%)</code></td>
110+
<td>
111+
hue <span class="fade">[0, 360]</span>;
112+
whiteness <span class="fade">[0%, 100%]</span>;
113+
blackness <span class="fade">[0%, 100%]</span>
114+
</td>
115+
</tr>
116+
<tr>
117+
<th scope="row"><code>srgb</code></th>
118+
<td><code>color(srgb 0.4 0.2 0.6)</code></td>
119+
<td>
120+
red <span class="fade">[0, 1]</span>;
121+
green <span class="fade">[0, 1]</span>;
122+
blue <span class="fade">[0, 1]</span>
123+
</td>
124+
</tr>
125+
<tr>
126+
<th scope="row"><code>srgb-linear</code></th>
127+
<td><code>color(srgb-linear 0.133 0.033 0.319)</code></td>
128+
<td>
129+
red <span class="fade">[0, 1]</span>;
130+
green <span class="fade">[0, 1]</span>;
131+
blue <span class="fade">[0, 1]</span>
132+
</td>
133+
</tr>
134+
<tr>
135+
<th scope="row"><code>display-p3</code></th>
136+
<td><code>color(display-p3 0.374 0.21 0.579)</code></td>
137+
<td>
138+
red <span class="fade">[0, 1]</span>;
139+
green <span class="fade">[0, 1]</span>;
140+
blue <span class="fade">[0, 1]</span>
141+
</td>
142+
</tr>
143+
<tr>
144+
<th scope="row"><code>a98-rgb</code></th>
145+
<td><code>color(a98-rgb 0.358 0.212 0.584)</code></td>
146+
<td>
147+
red <span class="fade">[0, 1]</span>;
148+
green <span class="fade">[0, 1]</span>;
149+
blue <span class="fade">[0, 1]</span>
150+
</td>
151+
</tr>
152+
<tr>
153+
<th scope="row"><code>prophoto-rgb</code></th>
154+
<td><code>color(prophoto-rgb 0.316 0.191 0.495)</code></td>
155+
<td>
156+
red <span class="fade">[0, 1]</span>;
157+
green <span class="fade">[0, 1]</span>;
158+
blue <span class="fade">[0, 1]</span>
159+
</td>
160+
</tr>
161+
<tr>
162+
<th scope="row"><code>rec2020</code></th>
163+
<td><code>color(rec2020 0.305 0.168 0.531)</code></td>
164+
<td>
165+
red <span class="fade">[0, 1]</span>;
166+
green <span class="fade">[0, 1]</span>;
167+
blue <span class="fade">[0, 1]</span>
168+
</td>
169+
</tr>
170+
<tr>
171+
<th scope="row"><code>xyz</code>, <code>xyz-d65</code></th>
172+
<td>
173+
<code>color(xyz 0.124 0.075 0.309)</code><br>
174+
<code>color(xyz-d65 0.124 0.075 0.309)</code>
175+
</td>
176+
<td>
177+
x <span class="fade">[0, 1]</span>;
178+
y <span class="fade">[0, 1]</span>;
179+
z <span class="fade">[0, 1]</span>
180+
</td>
181+
</tr>
182+
<tr>
183+
<th scope="row"><code>xyz-d50</code></th>
184+
<td><code>color(xyz-d50 0.116 0.073 0.233)</code></td>
185+
<td>
186+
x <span class="fade">[0, 1]</span>;
187+
y <span class="fade">[0, 1]</span>;
188+
z <span class="fade">[0, 1]</span>
189+
</td>
190+
</tr>
191+
<tr>
192+
<th scope="row"><code>lab</code></th>
193+
<td><code>lab(32.4% 38.4 -47.7)</code></td>
194+
<td>
195+
lightness <span class="fade">[0%, 100%]</span>;
196+
a <span class="fade">[-125, 125]</span>;
197+
b <span class="fade">[-125, 125]</span>
198+
</td>
199+
</tr>
200+
<tr>
201+
<th scope="row"><code>lch</code></th>
202+
<td><code>lch(32.4% 61.2 308.9deg)</code></td>
203+
<td>
204+
lightness <span class="fade">[0%, 100%]</span>;
205+
chroma <span class="fade">[0, 150]</span>;
206+
hue <span class="fade">[0deg, 360deg]</span>
207+
</td>
208+
</tr>
209+
<tr>
210+
<th scope="row"><code>oklab</code></th>
211+
<td><code>oklab(44% 0.088 -0.134)</code></td>
212+
<td>
213+
lightness <span class="fade">[0%, 100%]</span>;
214+
a <span class="fade">[-0.4, 0.4]</span>;
215+
b <span class="fade">[-0.4, 0.4]</span>
216+
</td>
217+
</tr>
218+
<tr>
219+
<th scope="row"><code>oklch</code></th>
220+
<td><code>oklch(44% 0.16 303.4deg)</code></td>
221+
<td>
222+
lightness <span class="fade">[0%, 100%]</span>;
223+
chroma <span class="fade">[0, 0.4]</span>;
224+
hue <span class="fade">[0deg, 360deg]</span>
225+
</td>
226+
</tr>
227+
</table>
228+
229+
Spaces marked with * are [legacy color spaces].
230+
231+
[legacy color spaces]: #legacy-color-spaces
232+
233+
## Missing Channels
234+
235+
Colors in CSS and Sass can have "missing channels", which are written `none` and
236+
represent a channel whose value isn't known or doesn't affect the way the color
237+
is rendered. For example, you might write `hsl(none 0% 50%)`, because the hue
238+
doesn't matter if the saturation is `0%`. In most cases, missing channels are
239+
just treated as 0 values, but they do come up occasionally:
240+
241+
* If you're mixing colors together, either as part of CSS interpolation for
242+
something like an animation or using Sass's [`color.mix()`] function, missing
243+
channels always take on the other color's value for that channel if possible.
244+
245+
[`color.mix()`]: /documentation/modules/color#mix
246+
247+
* If you convert a color with a missing channel to another space that has an
248+
analogous channel, that channel will be set to `none` after the conversion is
249+
complete.
50250

51-
CSS supports many different formats that can all represent the same color: its
52-
name, its hex code, and [functional notation][]. Which format Sass chooses to
53-
compile a color to depends on the color itself, how it was written in the
54-
original stylesheet, and the current output mode. Because it can vary so much,
55-
stylesheet authors shouldn't rely on any particular output format for colors
56-
they write.
251+
{% codeExample 'missing-channels', false %}
252+
@use 'sass:color';
57253

58-
[functional notation]: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value
254+
$grey: hsl(none 0% 50%);
59255

60-
Sass supports many useful [color functions][] that can be used to create new
61-
colors based on existing ones by [mixing colors together][] or [scaling their
62-
hue, saturation, or lightness][].
256+
@debug color.mix($grey, blue, $method: hsl); // hsl(240, 50%, 50%)
257+
@debug color.to-space($grey, lch); // lch(53.3889647411% 0 none)
258+
===
259+
@use 'sass:color'
260+
261+
$grey: hsl(none 0% 50%)
262+
263+
@debug color.mix($grey, blue, $method: hsl) // hsl(240, 50%, 50%)
264+
@debug color.to-space($grey, lch) // lch(53.3889647411% 0 none)
265+
{% endcodeExample %}
266+
267+
### Powerless Channels
268+
269+
A color channel is considered "powerless" under certain circumstances its value
270+
doesn't affect the way the color is rendered on screen. The CSS spec requires
271+
that when a color is converted to a new space, any powerless channels are
272+
replaced by `none`. Sass does this in all cases except conversions to legacy
273+
spaces, to guarantee that converting to a legacy space always produces a color
274+
that's compatible with older browsers.
275+
276+
For more details on powerless channels, see [`color.is-powerless()`].
277+
278+
[`color.is-powerless()`]: /documentation/modules/color#is-powerless
279+
280+
## Legacy Color Spaces
281+
282+
Historically, CSS and Sass only supported the standard RGB gamut, and only
283+
supported the `rgb`, `hsl`, and `hwb` functions for defining colors. Because at
284+
the time all colors used the same gamut, every color function worked with every
285+
color regardless of its color space. Sass still preserves this behavior, but
286+
only for older functions and only for colors in these three "legacy" color
287+
spaces. Even so, it's still a good practice to explicitly specify the `$space`
288+
you want to work in when using color functions.
289+
290+
Sass will also freely convert between different legacy color spaces when
291+
converting legacy color values to CSS. This is always safe, because they all use
292+
the same underlying color model, and this helps ensure that Sass emits colors in
293+
as compatible a format as possible.
294+
295+
## Color Functions
296+
297+
Sass supports many useful [color functions] that can be used to create new
298+
colors based on existing ones by [mixing colors together] or [scaling their
299+
channel values].
63300

64301
[mixing colors together]: /documentation/modules/color#mix
65-
[scaling their hue, saturation, or lightness]: /documentation/modules/color#scale
302+
[scaling their channel values]: /documentation/modules/color#scale
303+
304+
{% funFact %}
305+
Sass color functions can automatically convert colors between spaces, which
306+
makes it easy to do transformations in perceptually-uniform color spaces like
307+
Oklch. But they'll *always* return a color in the same space you gave it,
308+
unless you explicitly call [`color.to-space()`] to convert it.
309+
310+
[`color.to-space()`]: /documentation/modules/color#to-space
311+
{% endfunFact %}
66312

67313
{% codeExample 'color-formats', false %}
314+
@use 'sass:color';
315+
68316
$venus: #998099;
69317

70-
@debug scale-color($venus, $lightness: +15%); // #a893a8
71-
@debug mix($venus, midnightblue); // #594d85
318+
@debug color.scale($venus, $lightness: +15%, $space: oklch);
319+
// rgb(170.1523703626, 144.612080603, 170.1172627174)
320+
@debug color.mix($venus, midnightblue, $method: oklch);
321+
// rgb(95.9363315581, 74.5687109346, 133.2082569526)
72322
===
323+
@use 'sass:color'
324+
73325
$venus: #998099
74326

75-
@debug scale-color($venus, $lightness: +15%) // #a893a8
76-
@debug mix($venus, midnightblue) // #594d85
327+
@debug color.scale($venus, $lightness: +15%, $space: oklch)
328+
// rgb(170.1523703626, 144.612080603, 170.1172627174)
329+
@debug color.mix($venus, midnightblue, $method: oklch)
330+
// rgb(95.9363315581, 74.5687109346, 133.2082569526)
77331
{% endcodeExample %}

0 commit comments

Comments
 (0)