Skip to content

Commit 9a0034d

Browse files
committed
Write a blog post for Color Spaces
See sass/sass#2831
1 parent 27c36b2 commit 9a0034d

File tree

1 file changed

+261
-0
lines changed

1 file changed

+261
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
---
2+
title: "Request for Comments: Color Spaces"
3+
author: Miriam Suzanne and Natalie Weizenbaum
4+
date: 2022-09-20 19:00 PST
5+
---
6+
7+
There's been a lot of exciting work in the CSS color specifications lately, and
8+
as it begins to land in browsers we've been preparing to add support for it in
9+
Sass as well. The first and largest part of that is adding support for *color
10+
spaces* to Sass, which represents a huge (but largely backwards-compatible)
11+
rethinking of the way colors work.
12+
13+
Historically, all colors in CSS have existed in the same color space, known as
14+
"sRGB". Whether you represent them as a hex code, an `hsl()` function, or a
15+
color name, they represented the same set of visible colors you could tell a
16+
screen to display. While this is conceptually simple, there are some major
17+
downsides:
18+
19+
* As monitors have improved over time, they've become capable of displaying more
20+
colors than can be represented in the sRGB color space.
21+
22+
* sRGB, even when you're using it via `hsl()`, doesn't correspond very well with
23+
how humans perceive colors. Cyan looks noticeably lighter than purple with the
24+
same saturation and lightness values.
25+
26+
* There's no way to represent domain- or device-specific color spaces, such as
27+
the [CMYK] color space that's used by printers.
28+
29+
[CMYK]: https://en.wikipedia.org/wiki/CMYK_color_model
30+
31+
Color spaces solve all of these problems. Now not every color has a red, green,
32+
and blue channel (which can be interpreted as hue, saturation, and lightness).
33+
Instead, a every color has a specific *color space* which specifies which
34+
channels it has. For example, the color `oklch(80% 50% 90deg)` has `oklch` as
35+
its color space, `80%` lightness, `50%` chroma, and `90deg` hue.
36+
37+
## Color Spaces in Sass
38+
39+
Today we're announcing [a proposal for how to handle color spaces in Sass]. In
40+
addition to expanding Sass's color values to support color spaces, this proposal
41+
defines Sassified versions of all the color functions in [CSS Color Level
42+
4][color-4].
43+
44+
[a proposal for how to handle color spaces in Sass]: https://github.com/sass/sass/blob/main/proposal/color-4-new-spaces.md
45+
46+
### Rules of Thumb
47+
48+
There are several rules of thumb for working with color spaces in Sass:
49+
50+
* The `rgb`, `hsl`, and `hwb` spaces are considered "legacy spaces", and will
51+
often get special handling for the sake of backwards compatibility. Colors
52+
defined using hex notation or CSS color names are considered part of the
53+
`rgb` color space. Legacy colors are emitted in the most compatible format.
54+
55+
* Otherwise, any color defined in a given space will remain in that space, and
56+
be emitted in that space.
57+
58+
* Authors can explicitly convert a color's space by using `color.to-space()`.
59+
This can be useful to enforce non-legacy behavior, by converting into a
60+
non-legacy space, or to ensure the color output is compatible with older
61+
browsers by converting colors into a legacy space before emitting.
62+
63+
* The `srgb` color space is equivalent to `rgb`, except that one is a legacy
64+
space, and the other is not. They also use different coordinate systems, with
65+
`rgb()` accepting a range from 0-255, and `srgb` using a range of 0-1.
66+
67+
* Color functions that allow specifying a color space for manipulation will
68+
always use the source color space by default. When an explicit space is
69+
provided for manipulation, the resulting color will still be returned in the
70+
same space as the origin color. For `color.mix()`, the first color parameter
71+
is considered the origin color.
72+
73+
* All legacy and RGB-style spaces represent bounded gamuts of color. Since
74+
mapping colors into gamut is a lossy process, it should generally be left to
75+
browsers, which can map colors as-needed, based on the capabilities of a
76+
display. For that reason, out-of-gamut channel values are maintained by Sass
77+
whenever possible, even when converting into gamut-bounded color spaces. The
78+
only exception is that `hsl` and `hwb` color spaces are not able to express
79+
out-of-gamut color, so converting colors into those spaces will gamut-map the
80+
colors as well. Authors can also perform explicit gamut mapping with the
81+
`color.to-gamut()` function.
82+
83+
* Legacy browsers require colors in the `srgb` gamut. However, most modern
84+
displays support the wider `display-p3` gamut.
85+
86+
### Standard CSS Color Functions
87+
88+
#### `oklab()` and `oklch()`
89+
90+
The `oklab()` (cubic) and `oklch()` (cylindrical) functions provide access to an
91+
unbounded gamut of colors in a perceptually uniform space. Authors can use these
92+
functions to define reliably uniform colors. For example, the following colors
93+
are perceptually similar in lightness and saturation:
94+
95+
```scss
96+
$pink: oklch(64% 0.196 353); // hsl(329.8 70.29% 58.75%)
97+
$blue: oklch(64% 0.196 253); // hsl(207.4 99.22% 50.69%)
98+
```
99+
100+
The `oklch()` format uses consistent "lightness" and "chroma" values, while the
101+
`hsl()` format shows dramatic changes in both "lightness" and "saturation". As
102+
such, `oklch` is often the best space for consistent transforms.
103+
104+
#### `lab()` and `lch()`
105+
106+
The `lab()` and `lch()` functions provide access to an unbounded gamut of colors
107+
in a space that's less perpetually-uniform but more widely-adopted than OKLab
108+
and OKLCH.
109+
110+
#### `hwb()`
111+
112+
Sass now supports a top-level `hwb()` function that uses the same syntax as
113+
CSS's built-in `hwb()` syntax.
114+
115+
#### `color()`
116+
117+
The new `color()` function provides access to a number of specialty spaces. Most
118+
notably, `display-p3` is a common space for wide-gamut monitors, making it
119+
likely one of the more popular options for authors who simply want access to a
120+
wider range of colors. For example, P3 greens are significantly 'brighter' and
121+
more saturated than the greens available in sRGB:
122+
123+
```scss
124+
$fallback-green: rgb(0% 100% 0%);
125+
$brighter-green: color(display-p3 0 1 0);
126+
```
127+
128+
Sass will natively support all predefined color spaces declared in the Colors
129+
Level 4 specification. It will also support unknown color spaces, although these
130+
can't be converted to and from and other color space.
131+
132+
### New Sass Color Functions
133+
134+
#### `color.channel()`
135+
136+
This function returns the value of a single channel in a color. By default, it
137+
only supports channels that are available in the color's own space, but you can
138+
pass the `$space` parameter to return the value of the channel after converting
139+
to the given space.
140+
141+
```scss
142+
$brand: hsl(0 100% 25.1%);
143+
144+
// result: 25.1%
145+
$hsl-lightness: color.channel($brand, "lightness");
146+
147+
// result: 37.67%
148+
$oklch-lightness: color.channel($brand, "lightness", $space: oklch);
149+
```
150+
151+
#### `color.space()`
152+
153+
This function returns the name of the color's space.
154+
155+
```scss
156+
// result: hsl
157+
$hsl-space: color.space(hsl(0 100% 25.1%));
158+
159+
// result: oklch
160+
$oklch-space: color.space(oklch(37.7% 38.75% 29.23deg));
161+
```
162+
163+
#### `color.is-in-gamut()`, `color.is-legacy()`
164+
165+
These functions return various facts about the color. `color.is-in-gamut()`
166+
returns whether the color is in-gamut for its color space (as opposed to having
167+
one or more of its channels out of bounds, like `rgb(300 0 0)`).
168+
`color.is-legacy()` returns whether the color is a legacy color in the `rgb`,
169+
`hsl`, or `hwb` color space.
170+
171+
#### `color.is-powerless()`
172+
173+
This function returns whether a given channel is "powerless" in the given color.
174+
This is a special state that's defined for individual color spaces, which
175+
indicates that a channel's value won't affect how a color is displayed.
176+
177+
```scss
178+
$grey: hsl(0 0% 60%);
179+
180+
// result: true, because saturation is 0
181+
$hue-powerless: color.is-powerless($grey, "hue");
182+
183+
// result: false
184+
$hue-powerless: color.is-powerless($grey, "lightness");
185+
```
186+
187+
#### `color.same()`
188+
189+
This function returns whether two colors will be displayed the same way, even if
190+
this requires converting between spaces. This is unlike the `==` operator, which
191+
always considers colors in different non-legacy spaces to be inequal.
192+
193+
```scss
194+
$orange-rgb: #ff5f00;
195+
$orange-oklch: oklch(68.72% 20.966858279% 41.4189852913deg);
196+
197+
// result: false
198+
$equal: $orange-rgb == $orange-oklch;
199+
200+
// result: true
201+
$same: color.same($orange-rb, $orange-oklch);
202+
```
203+
204+
### Existing Sass Color Functions
205+
206+
#### `color.scale()`, `color.adjust()`, and `color.change()`
207+
208+
By default, all Sass color transformations are handled and returned in the color
209+
space of the original color parameter. However, all relevant functions now allow
210+
specifying an explicit color space for transformations. For example, lightness &
211+
darkness adjustments are most reliable in `oklch`:
212+
213+
```scss
214+
$brand: hsl(0 100% 25.1%);
215+
216+
// result: hsl(0 100% 43.8%)
217+
$hsl-lightness: color.scale($brand, $lightness: 25%);
218+
219+
// result: hsl(5.76 56% 45.4%)
220+
$oklch-lightness: color.scale($brand, $lightness: 25%, $space: oklch);
221+
```
222+
223+
Note that the returned color is still emitted in the original color space, even
224+
when the adjustment is performed in a different space.
225+
226+
#### `color.mix()`
227+
228+
The `color.mix()` function will retain its existing behavior for legacy color
229+
spaces, but for new color spaces it will match CSS's "color interpolation"
230+
specification. This is how CSS computes which color to use in between two colors
231+
in a gradient or an animation.
232+
233+
#### Deprecations
234+
235+
A number of existing functions only make sense for legacy colors, and so are
236+
being deprecated in favor of color-space-friendly functions like
237+
`color.channel()` and `color.adjust()`:
238+
239+
* `color.red()`
240+
* `color.green()`
241+
* `color.blue()`
242+
* `color.hue()`
243+
* `color.saturation()`
244+
* `color.lightness()`
245+
* `color.whiteness()`
246+
* `color.blackness()`
247+
* `adjust-hue()`
248+
* `saturate()`
249+
* `desaturate()`
250+
* `transparentize()`/`fade-out()`
251+
* `opacify()`/`fade-in()`
252+
* `lighten()`/`darken()`
253+
254+
## Let Us Know What You Think!
255+
256+
There's lots more detail to this proposal, and it's not set in stone yet. We
257+
want your feedback on it! Read it over [on GitHub], and [file an issue] with any
258+
thoughts or concerns you may have.
259+
260+
[on GitHub]: https://github.com/sass/sass/blob/main/proposal/color-4-new-spaces.md#deprecated-functions
261+
[file an issue]: https://github.com/sass/sass/issues/new

0 commit comments

Comments
 (0)