Tab Completion

I'm Tab Atkins Jr, and I wear many hats. I work for Google on the Chrome browser as a Web Standards Hacker. I'm also a member of the CSS Working Group, and am either a member or contributor to several other working groups in the W3C. You can contact me here.
Listing of All Posts

The color() Function

Last updated:

(Updated Aug 19 - scroll to the bottom of this post.)

Since I wrote my last post about the Color Naming System, I've been thinking and reading about color a lot more.

Overall, what I wrote in that previous post still represents my current thinking - I think that we should have a better system of named colors, and it should look something like CNS. The "extended" CNS that I provide a grammar for at the bottom of the last post is a good start, but I'd like to make a few changes. Here's the full proposal - my rationale for the changes will be below.

Proposal for the color() function

The color() function takes a series of keywords naming a color, according to the grammar below.

<color-func> = color(<cns> <opacity>?)

<cns> = <tint> <hue> | <shade> <hue> | <tone> <hue> | <hue>

<tint> = whitish | pale | brilliant | vivid | tint(<percentage>)

<shade> = blackish | dim | deep | vivid | shade(<percentage>)

<tone> = <lightness> || <saturation>

<lightness> = very dark | dark | moderate | light | very light | l(<percentage>)

<saturation> = grayish | moderate | strong | vivid | s(<percentage>)

<hue> = [<base-color> | <splash-color>]{0,3} <base-color> | h(<angle>)

<base-color> = red | pink | orange | brown | tan | yellow | olive | green | blue | purple | white | black | gray

<splash-color> = reddish | pinkish | orangish | brownish | tannish | yellowish | olivish | greenish | bluish | purplish | whitish | blackish | grayish

<opacity> = <number> | <percentage>

For simplicity, the grammar isn't complete regarding <hue> mixing. The full rules are:

  1. When mixing colors for a <hue>, if you have two achromatic or two chromatic colors, they must be "neighbors". For the achromatic colors (black/white/gray), black and white are neighbors of gray, but not each other. For the chromatics (everything else), colors are neighbors of the colors on the same spot or adjacent to them on the color wheel. (Red and pink are on the same spot, orange, brown, and tan are on the same spot, and yellow and olive are on the same spot.) Chromatics and achromatics can be mixed freely. Colors are not their own neighbors, so no "reddish red".
  2. When mixing colors, <splash-color>s have to come before <base-color>s. (So, "reddish orange", not "orange reddish".) Order doesn't matter otherwise - "blue green" and "green blue" are the same hue.
  3. Mixing in an achromatic color limits what other keywords can be used. If you define a hue that includes white or black (or their splash equivalents), you can't use any other keywords - you're already defining a tint or shade directly. If you define a hue that includes gray (or its splash), you can't use the <saturation> keywords, but you can use the rest.
  4. When mixing colors for a hue, you can only have up to two chromatic and two achromatic. For example, you can't make a hue called "reddish orangish yellow". You can only make a three-color hue by throwing in one of the other types of color, like "bluish green gray", or "whitish grayish purple". Four-color hues must have a pair of chromatics and a pair of achromatics.

Reasoning

This proposal differs a little bit from the extended CNS in my last post. There are good reasons for this!

First, you may notice that it defines three additional base colors - pink, tan, and olive. A color naming study found that these are widely considered to be "basic colors" in English. The study was pretty simple and interesting - the researchers just asked people to list at least a dozen colors. The ten colors from my last post were definitely the top 10, but when they looked at the data, they found that the best cut-off for "commonly-named colors" actually included a few more - specifically, pink, tan, and olive. After that, the numbers dropped off much more sharply, indicating that there was much less agreement over what names were "basic colors" beyond that set.

Second, I've allowed colors on "the same spot" to mix with each other. In the grammar in my last post, only orange and brown were on the same spot, and they weren't allowed to mix. I decided this didn't make too much sense - if you can make a "reddish brown" and a "yellowish brown", why doesn't "orangish brown" make sense? I can definitely imagine that color. Similarly, "reddish pink" makes sense, and we might as well allow all the other combinations too.

Third, I've just thrown the achromatics into the bag of base colors. In my last post, they were segregated from the "normal" colors. However, I saw that the <tint>, <shade>, and <saturation> keywords all had a value that looked like they were just splashing an achromatic in, but they didn't fully work like a splash color. For example, you could say "whitish blue)", but not "bluish white)". The latter color seems perfectly reasonable, and I thought the fact that the achromatics were kinda like base colors but not quite the same would be confusing. An additional benefit of this is that mixing in an achromatic is basically just extending the <tint>, <shade>, or <saturation> keywords, so we get more colors right where we need them - the edges of white/black/gray are pretty noticable.

All these additions do complicate the system somewhat. The major benefit of the original CNS system is that it's trivial to figure out the name of the "next" color along any axis - if you want something lighter than "light green", just say "very light green". The only exception was orange/brown and their mixtures, since their ranges overlapped somewhat. Adding tints and shades, however, as the extended CNS in my last post did, makes it more complicated; "pale green" might sit between "light green" and "very light green", for example. The additional extensions I introduce in this post further the problem - there are more colors "on the same spot" as other colors, so more overlap, and mixing in the achromatics (particularly when you mix in two, like "whitish gray blue") overlaps with both the lightness keywords and the tint/shade keywords.

It's definitely not clear which named color is "next" to a given color any longer. However, it is always clear how to at least move some amount in the desired direction. I think that property is still sufficiently useful, and the gain from being able to name more colors, in a more natural manner, is worth the loss of complete knowledge of the system that one could achieve in the simplest CNS.

July 9 update

I fixed the grammar and the prose rules to allow up to 4 colors in a hue mixture, and for gray mixes to take <tint> and <shade> keywords. Disallowing them was inconsistent - "whitish gray" is basically a gray tint, so you should be able to apply the other tints as well.

July 15 update

I calculated the total number of colors expressible in the system a few days ago, and forgot to update the post with them.

10 base chromatic colors  
6 + 6 + 2 + 1 + 1 + 2 = 18 between-spot blends (red orange)  
1 + 3 + 1 = 5 within-spot blends (red pink)  
= 23 equal blends  
46 splash blends  
= 79 chromatic hues

 158 tints (ignoring vivid and whitish)  
 158 shades (ignoring vivid and blackish)  
1185 tones (ignoring grayish)  
= 1501 chromatic colors  

158 chromatic blends with white  
316 chromatic blends with white+gray  
4 achromatic white hues  
= 478 white colors  
= 478 black colors

159 hues containing gray  
316 gray tints  
316 gray shades  
795 gray tones  
= 1432 gray colors

= 3889 colors, total

This number excludes any duplicates due to order-insensitivity (such as "blue green" vs "green blue") and defaulted arguments (such as "red" vs "vivid red"). However, it doesn't attempt to exclude any colors which have different names but end up with the same actual color (such as "pale gray" vs "very light gray").

July 16 update

Removed the comma separating the color from the opacity in the grammar, because fuck commas. (More seriously, fantasai and I have been on a crusade to make new CSS functions only use commas when absolutely necessary. CSS only uses them to separate lists of parallel items, and uses spaces to separate what you might call "arguments" to properties.)

Added functional versions of each of the major grammar terms, so when you do need an exact value for one of the dimensions you can get it. One of the most annoying things for me anywhere is having to abandon a tool for a single case just because I fall out of the stuff it was designed for (example: having to switch from hex colors to rgba() when I need to add alpha, which is why we'll get 4/8 digit hex colors in Colors 4). If we can easily extend the tool to cover the full range of cases while preserving its easy-to-use-ness for the case it's designed for, we definitely should.

This officially raises the number of representable colors to infinity, but that's pedantic. ^_^

While this kinda just gives you a more verbose form of hsl() (color(saturation(100%) lightness(50%) hue(60deg))), it also effectively gives you the HWB color system (hue/whiteness/blackness) with the tint() and shade() functions. That's pretty useful! Evidence with SASS suggests that tinting/shading colors is a very useful primitive that people find intuitive.

I'm not sold on the names lightness() and saturation(). They're very long. Anyone have better name suggestions?

It may also be possible to "ease into" the full CNS system by first just letting you use base/splash color keywords to specify a hue as the first argument of hsl/a(). Heavy anecdotal evidence suggests that people are usually okay with using saturation/lightness, but they can't easily memorize the hue angles of colors. (I'm not convinced that most people can really use saturation/lightness either, though. Representing brown, olive, or tan are all non-trivial if you don't already know how to do it right, and those are important enough colors to be base colors in my CNS proposal.) The other downside of this is that you don't get the tinting/shading possibilities that I really want to introduce. But still, it would be better than nothing.

Aug 19 update

Changed the hue()/saturation()/lightness() names to just h()/s()/l().

(a limited set of Markdown is supported)

I'm not sure other than something like sat() and light()

Reply?

(a limited set of Markdown is supported)

Yeah, sat() and light() for the function names sounds good. I've updated the post.

Reply?

(a limited set of Markdown is supported)