Skip to content

Commit f32544b

Browse files
nex3mirisuzanne
andauthored
Write a blog post for Color Spaces (#671)
See sass/sass#2831 Co-authored-by: Miriam Suzanne <[email protected]>
1 parent 27c36b2 commit f32544b

File tree

1 file changed

+262
-0
lines changed

1 file changed

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

0 commit comments

Comments
 (0)