Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Legacy color #137

Open
o-t-w opened this issue Jun 14, 2022 · 62 comments
Open

Legacy color #137

o-t-w opened this issue Jun 14, 2022 · 62 comments

Comments

@o-t-w
Copy link

o-t-w commented Jun 14, 2022

From the part of the spec about color types: “Represents a 24bit RGB or 24+8bit RGBA color in the sRGB color space. The $type property MUST be set to the string color. The value MUST be a string containing a hex triplet/quartet including the preceding # character. To support other color spaces, such as HSL, export tools SHOULD convert color tokens to the equivalent value as needed.”

Chrome is currently working in implementing LCH, LAB, OKLCH and OKLAB color. Safari already supports them. Although support is currently rarer among design tools it still seems sad and backwards-looking to standardise and require a legacy color format.

It is impossible to transform a hex color into a modern color. It is overwhelmingly likely that the modern color formats will become ubiquitous on the web within the next year or two. I'm sure design tools will quickly follow along.

My suggestion would be to standardize on OKLCH already and have design tools that don't yet support modern color transform the color into hex.

I'm already using LAB and LCH on a large web application with hex fallbacks. Standardizing on hex would make design tokens unusable for me.

@romainmenke
Copy link
Contributor

romainmenke commented Jun 14, 2022

The only format that makes sense to me is something like this :

{
  "colorSpace": "sRGB",
  "channels": [0.1, 0.1, 0.1],
  "alpha": 1.0
}
{
  "colorSpace": "lab",
  "channels": [0.1, 0.1, 0.1],
  "alpha": 1.0
}

The different representations of a color space (hsl for rgb, lch for lab, ...) are less important in a transfer format like design tokens.

@sebfriedrich
Copy link

Fully agree @o-t-w , I don't use wide gamut color but have my thoughts sorted on how I would want to have it.

For the ones who aren't convinced here: What Chrome and other browsers implement there right now dates back 90 years from now to color theory research about human perception. Also design systems are not meant to cover a subset of what CSS can help one to build, it is the other way around. It is also worth noting, that a design system shall help manage media breaks more reliability than without such a system in place. Delegating color conversion, which shall be assumed lossy by default, to a random tool at sort of runtime is accepting that applying color via tokens will have different and unforseeable deviations.

CieLCh or similar might be a good start for thinking. Ultimately I would hope for the ability to describe any custom color space by an 3x3 conversion matrix and for better usability hard code the most common ones as named presets to use out-of-the-box.

The work of Bruce Lindbloom might be a good read to get into the math of the topic: http://www.brucelindbloom.com/

@NateBaldwinDesign
Copy link

I agree that there should be more flexibility built-in. Considering tokens are an interchange format I would not necessarily expect them to house transformations. But as a way to express "design decisions", it seems backwards or outdated to not support a method for defining decisions as decisions are made. In other words, I as a designer should be able to specify a color like

{
  colorSpace: 'CAM16',
  channels: [76, 100, -56],
  alpha: 1
}

Then it's a matter of tools being able to support a color space or not. However, looking at a bit more likely scenario, we can see how a fallback may be necessary for non-24bit sRGB colors, like with DCI-P3/Display-P3:

{
  colorSpace: 'Display-P3,
  channels: [1, 0.5, 0],
  alpha: 1,
  fallback: "#FF8800'
}

At which point, forcing the fallback value to meet the earlier requirement makes sense.

@kaelig
Copy link
Member

kaelig commented Jun 15, 2022

tl;dr – IMHO the proposed solution (in the Second Editors' Draft) satisfies most of today's needs – later on, we can work on getting a commitment from the entire toolchain to support more color spaces.


Some personal views / notes / insights (hope this helps understand how the spec was written):

  1. While I agree with the points made in this discussion, please be mindful of current toolchain capabilities.
  2. In our quest to write an acceptable "v1" of the spec, we reached out to folks who work on the CSS Color Module specification, and their willingness to see progress from design tools and browsers was palpable.
  3. IMHO it's too early for the DTCG to push for toolchain-wide changes.
  4. Eventually, I'd love for the DTCG to be in a position to influence the entire toolchain!

@kaelig kaelig added dtcg-format All issues related to the format specification. To be reviewed by editors Issues that need to be reviewed in an upcoming meeting between editors. dtcg-color All issues related to the color module specification. labels Jun 15, 2022
@romainmenke
Copy link
Contributor

romainmenke commented Jun 15, 2022

While I agree with the points made in this discussion, please be mindful of current toolchain capabilities.

IMHO it's too early for the DTCG to push for toolchain-wide changes.

This I do not fully understand.
Storing colors in a format that supports wide gamut color spaces does not require tools to actually adopt wide gamut color spaces.

Every design tool can keep using sRGB (or no color space as some do).
The only reason to chose non-hex is so that the design token file format doesn't require a spec change later.

It could be part of the current specification to limit this to the sRGB color space to ease adoption.

Choosing hex color notation will make it considerably harder to do this later.

#808080 is equivalent to :

{
  "colorSpace": "sRGB",
  "channels": [128, 128, 128],
  "alpha": 1.0
}

The only difference between these two is that the latter is compatible with future additions (color spaces).

A variable number of channels is also very handy for print applications.

@NateBaldwinDesign
Copy link

I also don't quite understand nor agree. Hex/sRGB is a fine fallback for legacy or small gamut support, and can simply be a required field of the token. Display-p3 is already supported by Safari, and used in all retina displays. Design tokens capture design data that align with intent. Intent, as a designer, is not always to define the lowest common web-friendly denominator. Considering the spec already supports fallback notions (an array for fontFamily), this decision seems a bit off base.

My position on this is not that the tool chain should support these formats. They should, but it's not the design token spec's purpose to influence that. Tokens are codification of design decisions, I can't repeat that enough. Some people choose fonts that may fail to load on the browser (so fallbacks are recommended). Similarly some people choose colors tailored for wide gamut displays or based on a specific device-independent color space because of relationships between parameters in a perceptually uniform space.

@nesquarx
Copy link

nesquarx commented Jun 15, 2022 via email

@danvpeterson
Copy link

danvpeterson commented Jun 15, 2022

Very much agreed on hoping for support for colors beyond hex values, this is the primary thing that jumped out to me as dangerous when reading the newest proposal. I already use LCH wherever I can in our system's documentation, even though it means more work for me because Figma doesn't support it yet. But it makes it much easier to control perceptual color contrast across a palette, make adjustments to colors while keeping them in the same family, and "lightness" actually means something unlike others where a yellow and a blue with the same lightness or brightness value are so obviously different. Not to mention that it gives us access to so many more colors.

Having to set these in hex is incredibly limiting. We can pretty easily convert from a larger color space to a smaller one, but once it's moved into the smaller space you can't get those additional colors back. Limiting this to hex and sRGB only means that tools that use a larger space are going to necessarily have their colors altered when exporting into a token. And whichever format we go with in the token format can be converted to whatever format is needed by the tools that use them, without them having to actually support that larger color space in the tool. So shouldn't we try to go with a less limiting structure here so those of us that care to can use something that is easier to understand, supports a wider gamut (that can be restricted where necessary during conversion), and provides additional benefits like easier setup for perceptual color contrast?

@marcedwards
Copy link

marcedwards commented Jun 17, 2022

The different representations of a color space (hsl for rgb, lch for lab, ...) are less important in a transfer format like design tokens.

I wholeheartedly agree with this. RGB floats with support for popular colour spaces (sRGB, Display P3) makes a lot of sense as a base requirement. HSB, LCH, OKLAB etc are nice to have, but with the correct precision and colour space, those conversions can always be done at display time, and support for them may be better served by the design tool or colour picker.

HEX is very limited in terms of precision, and rounding errors can be a significant issue when doing conversions. The future is floats!

@kaelig kaelig added the Needs Feedback/Review Open for feedback from the community, and may require a review from editors. label Jun 22, 2022
@c1rrus
Copy link
Member

c1rrus commented Jun 26, 2022

Really interesting points. The syntax & permitted values for color tokens certainly seems to be the most controversial part of this spec! Some of the concerns raised here feel similar to the ones in issue #79.

I must confess my knowledge of the theory and terminology around colors is a bit limited compared to some of the posters here. In case it helps others, I've found this article about "Improving Color on the Web" on the Webkit blog useful as it has a handy "Definitions" section that explains things quite well IMHO. (If you know of other good learning resources on this stuff, please do share some links here!)

Based on my (limited) knowledge of this topic the main concern being raised here is that the current draft spec restricts colors to:

  • sRGB color space (or is that color profile?)
  • Color depth of 8bits per channel

Safari, soon Chrome and perhaps other browsers, platforms and tools support wider gamuts (i.e. more colors that don't exist in the sRGB space) and/or higher color depths (the precision with which colors within a given color space can be specified). Design systems wanting to use such wide gamut and/or higher depth colors therefore cannot accurately represent them using the DTCG format.

The syntax for color values proposed by @romainmenke could resolve these issues because:

  • The floating point numbers for each channel offer a much higher color depth than the 8bits afforded by the hex format the spec currently specifies
  • Explicitly stating the color space lets folks use different color spaces. Though, I'd imagine the spec would need to define a finite set of color spaces that can be used in order to enable interoprability.

Questions to y'all:

  1. Is it possible to do a lossy conversion from a higher color depth to a lower one?
  2. Is it possible to do a lossless conversion from one color space to another for colors where their gamuts overlap?
    • I'm assuming if a color in space A does not exist in space B's gamut, the conversion would necessarily be lossy. Right?

Assuming the answer to 1. is "yes", then any tools that only support 8bits per channel could simply do the lossy conversion from the token's "raw" floating point value. Depending on their needs, this could be done at the point at which they load a tokens file or at the point at which the value is displayed in the UI.

Going the other way around is fine too. A tool that can only generate 8 bit per channel colors can easily write out those values as floating point numbers. Such tools are just a bit limited in terms of the range of values they can produce.

The use-case that requires some further thought would be a tool that can import, manipulate and then export token files. Ideally any tokens in the original file that were imported, not modified and then included in the exported file would be perfectly preserved. However, if the import process inluded a lossy conversion, then the corresponding exported token would no longer be the same. I think that's a broader topic as it applies to other kinds of tokens too. For example dimension tokens with rem values. If they were to travel through a tool that internally only supports px values, there's the same kind of challenge. I think I'll therefore open a separate Github issue for this specific topic.

However, aside from that "preserving tokens when travelling through a tool" issue, I see no problem with adopting an array of floating point numbers instead of a hex string. As others have pointed out, this would not impose any obligation on tools to internally support higher depth colors. It therefore shouldn't be a blocker to existing design tools (or indeed any kind of tool) adopting the DTCG format.

If the answer to my 2nd question above is also "yes", then the same might be true for supporting additional color spaces beyond sRGB. Let's imagine the spec supported the Display P3 color space which, if I'm not mistaken, has a wider gamut than sRGB. If someone's specified a color token using Display P3 and a tool that internally only supports sRGB uses that token, then there are 2 possibilities:

  • The color is in gamut for sRGB, so the tool just performs a lossless conversion and uses the result
  • The color is out of gamut for sRGB, so the tool needs to do a lossy conversion to the closest color in sRGB and use that

The latter doesn't strike me as any different to a tool doing a lossy conversion from a higher color depth to a lower color depth. A nice to have might be to display a warning message to the user to inform them that the color they see in the tool's UI is an approximation of the actual color specified by the design token. But, I think that's quite acceptable.

Again, going the other way is fine too. Any color tokens generated within the tool, which are therefore necessarily confined to the sRGB color space, can be exported to the DTCG format without issue. Just because the spec can represent more colors than the tool, doesn't mean the tool needs to be able to generate all of those possible colors.

It seems supporting the sRGB space is a no brainer as that seems to be the de-facto standard one most current design tools support. It's also what all browsers support. I presume the same is true for native apps on various OSes (but please correct me if that's not the case).

The question therefore becomes, if we were to go down this route with the spec, which other color spaces should we support?

Bear in mind that every tool that supports the DTCG format would be expected to at least be able to convert from all of the specced color spaces to whatever it uses internally. So, the more spaces we require in the spec, the more complexity there might be for implementors.

Question: Is there a color space that has a gamut wide enough to encompass all other color spaces? If so - and assuming you can convert in gamut colors losslessly between spaces - then would it not suffice to only add that?

Btw, @NateBaldwinDesign, if the spec required tools to be able to convert incoming tokens to whatever (lesser) color space they support internally, wouldn't an explicit fallback color value as you've suggested above be redundant? Or, are there reasons why authors might want to specify one themselves rather than relying on automated conversions?

@c1rrus
Copy link
Member

c1rrus commented Jun 26, 2022

Another thing: While I do like @romainmenke's proposed syntax for color token values, it doesn't have to be mutually exclusive with the current spec draft's hex triplet or quartet string syntax. We could theoretically permit both. For example:

{
  "color-token-1": {
    "$type": "color",
    "$value": "#ff7700"
    // new rule: When using hex string, sRGB color space is assumed
  },
  "color-token-2": {
    "$type": "color",
    "$value": {
      "colorSpace": "sRGB",
      "channels": [1, 0.5, 0],
      "alpha": 1
    }
  }
}

In this example, both tokens have the exact same color value, just expressed in the different syntaxes. This would add a little bit of complexity to parsers as they'd need to add some logic along the lines of:

if (tokenType === "color") {
  if (typeof tokenValue === "string") {
    // it must be a hex triplet or quartet
    // (and therefore color space is sRGB)
    // ...
  }
  else if (typeof tokenValue === "object") {
    // it must be the object style
    // ...
  }
  else {
    throw new Exception("Invalid color token value");
  }
}

IF we decided that this was acceptable, then that might afford us some flexibility about when the format needs to support higher color depths and/or additional color spaces:

  1. We continue as is meaning that v1 of the spec only supports the hex strings (and is thus limited to sRGB and 8bits per channel). Later versions introduce the object syntax as an alternative way of expressing color values, including colors that use higher depths and other color spaces.
  2. We introduce both syntaxes right now, so v1 of the spec has them.
    1. For v1 we could restrict ourselves to only the sRGB space to keep things a bit simpler for implementors (in which case, perhaps we don't need the colorSpace property right now. That could be introduced in later spec versions when we add support for more color spaces. In that case, when that property is absence, parsers must assume the sRGB color space)
    2. OR, we go all the way and support multiple color spaces in v1

Personally, I'm leaning towards Option 1. or 2.1. I think adding the object style syntax sooner rather than later might be a good thing (and if we decided to do that, we might also want to consider ditching the hex strings altogether and making it the only syntax we use). While I'm totally sold on adding support for more color spaces eventually, I don't think their use in UI design is widespread enough today to make it a necessity for the v1 spec.

Bear in mind that $extensions could serve as an escape hatch for teams needing them sooner. You could, for example, do something along the lines of:

{
  "color-token": {
    "$value": "#ff0000", // sRGB 8bit-per-channel fallback color
    "$type": "color",
    "$extensions": {
      "com.example.originalColor": {
        "colorSpace": "displayP3",
        "channels": [1,0,0],
        "alpha": 1
      }
    }
  }
}

Btw, I know it's taking the DTCG quite a long time to get to a v1 spec. However, that doesn't necessarily mean subsequent versions will take as long. Especially, if they're mostly adding features. So, starting with just sRGB colors now, doesn't mean it has to be a really long time before support for other color spaces is added.

Also, if the v1 spec only supported sRGB teams wanting to use

@romainmenke
Copy link
Contributor

@c1rrus Thank you for these insights!

Is it not better to start with a data format that can support larger color spaces but limit this to srgb in v1?

That would create a much easier upgrade path.


{
  "color-token-1": {
    "$type": "color",
    "$value": "#ff7700"
    // new rule: When using hex string, sRGB color space is assumed
  },
  "color-token-2": {
    "$type": "color",
    "$value": {
      "colorSpace": "sRGB",
      "channels": [1, 0.5, 0],
      "alpha": 1
    }
  }
}

This feels like a very slippery sloop.
It seems to confuse "property color" with "type color".

A color property in a program might take one of more possible types for it's value but a color type should be unambiguous.

If type ambiguity is introduced the spec also needs to have a way to define type aliases.

The example above hints at these types :

  • color-hex
  • color-modern
  • color -> color-hex | color-modern

It also makes implementations in typesafe languages much more difficult.


In my opinion this favours short term convenience while introducing long term complexity.
One of the benefits of creating a brand new spec is that we can avoid these things and choose the best possible format from the start.

@romainmenke
Copy link
Contributor

romainmenke commented Jun 26, 2022

Is it possible to do a lossy conversion from a higher color depth to a lower one?

Yes, this is possible and mostly trivial.

One of the effects of a lower color depth can be banding in gradients.

Is it possible to do a lossless conversion from one color space to another for colors where their gamuts overlap?
I'm assuming if a color in space A does not exist in space B's gamut, the conversion would necessarily be lossy. Right?

This is possible and css-wg is working on non normative sections detailing how you might convert out of gamut colors.

https://drafts.csswg.org/css-color-4/#gamut-mapping

@marcedwards
Copy link

marcedwards commented Jun 27, 2022

  • sRGB color space (or is that color profile?)

Essentially the same thing. A color profile usually refers to the file that describes a color space.

Safari, soon Chrome and perhaps other browsers, platforms and tools support wider gamuts (i.e. more colors that don't exist in the sRGB space) and/or higher color depths (the precision with which colors within a given color space can be specified). Design systems wanting to use such wide gamut and/or higher depth colors therefore cannot accurately represent them using the DTCG format.

It’s complicated. Support for wide gamut images has been pretty good across the board on the web for ages, but support for wide gamut colors in CSS is newer and not as well supported. It’s absolutely the future though, and a new spec now should take it into consideration. The great news is that it’s not really wide gamut support we’re discussing, it’s just for the spec to be color space aware. If you’re taking colors seriously, you have to be aware of the color space they’re in.

For native apps, wide gamut support for colors has existed on iOS, macOS, and Android for many, many years.

The syntax for color values proposed by @romainmenke could resolve these issues because:

  • The floating point numbers for each channel offer a much higher color depth than the 8bits afforded by the hex format the spec currently specifies

Yep, floats are good idea. If only one format is supported, it should be floats. If supporting HEX can also be done, that’s great.

  • Explicitly stating the color space lets folks use different color spaces. Though, I'd imagine the spec would need to define a finite set of color spaces that can be used in order to enable interoprability.

A finite set of spaces would be totally fine. If no color space is specified, sRGB should be assumed (I think this should be explicitly stated in the spec).

Essential:

  • sRGB
  • Display P3

Bonus points:

  • Rec.2020
  • DCI-P3
  • Adobe RGB
  • sRGB Extended
  1. Is it possible to do a lossy conversion from a higher color depth to a lower one?

It’s possible to do lossy conversions between color space and color depths.

  1. Is it possible to do a lossless conversion from one color space to another for colors where their gamuts overlap?

There’s almost always a loss in precision, but if the color depth is enough and if the space you’re converting to is contained inside the color space you’re converting from, it’ll be fine.

  • I'm assuming if a color in space A does not exist in space B's gamut, the conversion would necessarily be lossy. Right?

That will typically result in colors being clipped/clamped. It would mean a color outside the destination color space will look different (often duller).

Assuming the answer to 1. is "yes", then any tools that only support 8bits per channel could simply do the lossy conversion from the token's "raw" floating point value. Depending on their needs, this could be done at the point at which they load a tokens file or at the point at which the value is displayed in the UI.

Yep, definitely. FWIW, color depth conversions are super easy. Color space conversions are non-trivial, and a library will often be needed.

The use-case that requires some further thought would be a tool that can import, manipulate and then export token files. Ideally any tokens in the original file that were imported, not modified and then included in the exported file would be perfectly preserved. However, if the import process inluded a lossy conversion, then the corresponding exported token would no longer be the same. I think that's a broader topic as it applies to other kinds of tokens too. For example dimension tokens with rem values. If they were to travel through a tool that internally only supports px values, there's the same kind of challenge. I think I'll therefore open a separate Github issue for this specific topic.

I think your assumptions are correct and imported colors could be clipped or have rounding errors. That’s on the tool though — if the spec defines a color space and colors are floats, best practices are being covered.

The question therefore becomes, if we were to go down this route with the spec, which other color spaces should we support?

Please see my list above. :) It’s a pretty non-controversial list!

Question: Is there a color space that has a gamut wide enough to encompass all other color spaces? If so - and assuming you can convert in gamut colors losslessly between spaces - then would it not suffice to only add that?

There’s a few answers to this. Rec.2020 is pretty huge, and that will cover quite a lot of future advancements. ACES is way bigger again. sRGB Extended allows for values beyond 1.0, which means it can also cover anything.

But, in all honesty, it makes sense to support Display P3 before other spaces.

@romainmenke
Copy link
Contributor

romainmenke commented Jun 27, 2022

I keep forgetting this but color space could also be something custom.
The format doesn't need to support this explicitly from the start, but keeping this in mind will easy future additions.

{
  "myPrintProfile": {
    "$type": "colorSpace",
    "some-way-to-embed-color-spaces": ""
  },
  "aColor": {
    "$type": "color",
    "colorSpace": "{myPrintProfile}",
    "channels": [0.5, 0.5, 0.5, 0.1, 1],
    "alpha": 1.0
  }
}

@kaelig
Copy link
Member

kaelig commented Jul 6, 2022

The proposals in this thread are great, keep them coming!

From a technical perspective, this is all solvable. There are canonical implementation of color space translation functions, and design tools as well as token translation tools could work around this when consuming tokens. That said, some tools are aim to not only consume tokens, but also manage them.

I'm really curious about the user experience for someone consuming/editing/exporting tokens in a design tool like XD, UXPin, Framer, Figma… which don't have full color space/profile management (yet).

Here's an example use-case, followed by a few questions about the possible flows:

Use-case

  1. import a token file
  2. edit a wide-gamut color
  3. save/re-export the token file

Questions

In the context of an sRGB-only tool...

  1. What should the editing flow/color picking UX be?
  2. When saving/exporting edited color tokens, what sort of prompts would you expect in relation to potential data-loss?
  3. What should the exported data look like (considering the user may have picked an sRGB color, overwriting a P3 color)?
  4. What about people working on monitors that don't cover sRGB? What should their editing experience be?

@romainmenke
Copy link
Contributor

romainmenke commented Jul 6, 2022

  1. What should the editing flow/color picking UX be?

Checking if a color token is out of gamut or has an unknown color space is trivial.
Each tool can scan a token file for tokens it can not represent faithfully or edit without altering the result.

If it is a translation tool this could be a warning that can be silenced.

For design tools this is problematic and I would see this as a destructive action.
Following usual UX patterns the user would be given a prompt to explain and warn about the destructive action.

  1. When saving/exporting edited color tokens, what sort of prompts would you expect in relation to potential data-loss?

see 1.

  1. What should the exported data look like (considering the user may have picked an sRGB color, overwriting a P3 color)?

Can you clarify or give a concrete example?
I do not follow the question.

  1. What about people working on monitors that don't cover sRGB? What should their editing experience be?

This is a problem that exists today and will continue to exist.
Even if we use hex and limit to sRGB, this problem will exist as even this smaller color space can not always be fully represented.


Aside from these points I still do not understand why it is not possible to use a data format that has a clear update path?

I don't want to have some legacy code that checks if $value for $type: color is of type string.

{
  "color-token": {
    "$type": "color",
    "$value": {
      "colorSpace": "sRGB",
      "channels": [1, 0.5, 0],
      "alpha": 1
    }
  }
}

The questions that surface from using hex are almost exactly the same as those you have for color spaces :)

  • are tools expected to support v1 forever? (both yes and no are problematic)
  • what if you open a file with an old version token in a new tool and save it?
  • what if you open a file with a new version token in an outdated tool?

@marcedwards
Copy link

marcedwards commented Jul 6, 2022

I'm really curious about the user experience for someone consuming/editing/exporting tokens in a design tool like XD, UXPin, Framer, Figma… which don't have full color space/profile management (yet).

They need to add support. There really isn’t any other way if they want to handle colours properly. At the very, very least, they should support a full sRGB workflow, with warnings and proper import conversion to sRGB.

Checking if a color token is out of gamut or has an unknown color space is trivial.
Each tool can scan a token file for tokens it can not represent faithfully or edit without altering the result.

Yeah, that feels like a very easy solution for the tool makers. If they do not want to add full colour management, they can warn about colours being changed on import.

@c1rrus
Copy link
Member

c1rrus commented Jul 6, 2022

(As promised in my earlier comment) I have opened a new issue #157 which proposes some generalised rules for what the expected behaviour for tools should be in these kinds of scenarios. As I explain there, I believe those behaviours will not only be relevant to colors (when support for more color spaces is added) but also other types of tokens.

I therefore suggest we discuss the expected behaviour of tools there and keep this thread focussed on the details of color values specifically (i.e. what the permitted syntax(es) should be and what color spaces should be supported).

@svgeesus
Copy link

svgeesus commented Jul 8, 2022

There’s a few answers to this. Rec.2020 is pretty huge, and that will cover quite a lot of future advancements. ACES is way bigger again. sRGB Extended allows for values beyond 1.0, which means it can also cover anything.

Rec BT.2020 is pretty huge indeed, and there are significant problems with it as a practical display space (observer metamerism, speckle, getting adequate brightness while keeping color purity) but as a space to specify colors it is fine and is widely used in industry (4k and up video content is delivered in BT.2020 or, for HDR, BT.2100 which has the same primaries). Rec BT.2020 does not cover all real-world colors but it covers a very substantial portion of them and is a significant increase over display-p3.

As for ACES, there are two sets of primaries - the AP0 set is enormous and not physically realizable, the AP1 set is similar to BT.2020 in coverage. ACEScg and ACEScc use the AP1 primaries.

@kevinmpowell
Copy link
Contributor

At a minimum, whatever capabilities we add to the Color Type in future iterations, token authors who hand-edit their files must be able to specify a color using only a HEX value represented as a string.

My view is that the current spec as authored (where Color is specified via HEX) satisfies a couple of key principles of this group.

Namely:

Inclusive: Allow anyone to become familiar with design tokens. Empower people, no matter what their skills and tool choices are, as they develop new mental models, acquire skills, and implement tools to scale design in their projects.

and:

Focused, yet extensible: Stay focused on the smallest surface area necessary to cover the most commonly referenced use-cases. Be a platform that opens the door to a wide range of possibilities. This small footprint helps maintain simplicity with zero dependencies. Extensibility allows the community to incubate new ideas that will define the future of design tokens.

My read of those principles as applied to the Color Type and some of the proposals in this thread:

First - Empower people, no matter what their skills and tool choices are
I'm very concerned about a spec change that would require a token author to specify a color space and a use a notation that may be unfamiliar to many. HEX, for all its shortcomings, is widely understood and standardized not just across tools, but also in the minds of many many designers and developers across multiple fields (not just web). It may be an insufficient format for what modern screens are capable of, but it's well established and that carries weight in the form of familiarity. That familiarity will aid in the spec's adoption by those who are familiar with HEX and have no need for colors in a wider gamut.

If we require specification of color spaces and array notation to specify color, we'll be placing an additional burden on many token spec adopters to not only learn the design token format, but possibly a whole new means of specifying colors they already have documented elsewhere in HEX. My concern is that this requirement would be so off-putting that it will limit the adoption of the specification as a whole.

To argue the counterpoint, you could say the current spec is not inclusive because it excludes those who wish to use colors from a wider-gamut. The lack of wider-gamut color space support is off-putting to those that wish to define their colors in something more powerful than HEX provides. For that group, I lean on the second principle:
Stay focused on the smallest surface area necessary to cover the most commonly referenced use-cases.

I believe we are doing that by norming on the most commonly used representation of color in our current ecosystem.

All that said, I want to be future-looking in a future version of the spec. I believe something like @c1rrus proposed in #137 (comment) would be the most inclusive syntax we could adopt - supporting both the today mindset of HEX and the future mindset of wider-gamut colors.

@romainmenke
Copy link
Contributor

romainmenke commented Jul 25, 2022

Stay focused on the smallest surface area necessary to cover the most commonly referenced use-cases.
I believe we are doing that by norming on the most commonly used representation of color in our current ecosystem.

But this is a self re-enforcing mechanic right?
It is the most commonly used representations because authors lack tools and now authors will lack tools because they haven't been authoring in more modern formats.

All that said, I want to be future-looking in a future version of the spec. I believe something like @c1rrus proposed in #137 (comment) would be the most inclusive syntax we could adopt - supporting both the today mindset of HEX and the future mindset of wider-gamut colors.

This will place significant burden on tool maintainers as they will need to support two formats indefinitely. I am not even starting on my planned implementation for PostCSS because of issues likes these. I do not want to add tech debt on day one.

@marcedwards
Copy link

marcedwards commented Jul 26, 2022

If we require specification of color spaces and array notation to specify color, we'll be placing an additional burden on many token spec adopters to not only learn the design token format, but possibly a whole new means of specifying colors they already have documented elsewhere in HEX. My concern is that this requirement would be so off-putting that it will limit the adoption of the specification as a whole.

As mentioned previously, sRGB can and should be the default when the color space is omitted, which lets people use a simpler format, if they’d like. It will cause massive issues if color spaces aren’t considered as part of the original spec. It’s just not a viable option to ignore color spaces in 2022. I’d go as far as saying I will not use a format that can’t specify the color space of a color.

@IanVS
Copy link

IanVS commented Feb 28, 2024

I have a related issue that I haven't seen discussed here so far. The colors for my design system are specified in hsl. If I have to convert them to hex for storage in the design token, and then convert them back to hsl during processing, I get slightly different hue and saturation values, which is disappointing. It would be great if I could store it directly as hsl to avoid the conversion losses.

@drwpow
Copy link
Contributor

drwpow commented Feb 28, 2024

@IanVS that will happen since HSL is a distortion of the sRGB/hex gamut. If you get any different values converting back, it just means the exact same color can be expressed in multiple ways (one of HSL’s many flaws). It shouldn’t affect the final output, but understand it can be annoying just dealing with the noise.

@kaelig
Copy link
Member

kaelig commented Feb 28, 2024

Personal take: after running into serious color conversion issues and broader team confusion, I consider the hsl() notation harmful, and would encourage practitioners to move away from it.

That being said, for those stuck using HSL (or manage to make it work for them), it'd be great to offer a format that helps them do the right thing.

@kaelig
Copy link
Member

kaelig commented May 16, 2024

@romainmenke's proposal is the closest to what we want, and here's what we have right now:

{
  "my-token": {
    "$type": "color",
    "$value": {
      "hex": "#xxxxxx", // Only HEX 6 supported (no HEX 3, 4, or 8)
      "colorSpace": "dci-p3",
      "channels": [0.1, 0.2, 0.3],
      "alpha": 0.6
    }
  }
}

We could borrow the pre-defined CSS spec spaces for absolute color(<color-space> ...) values with rectangular spaces, as in: srgb | srgb-linear | display-p3 | a98-rgb | prophoto-rgb | rec2020 | lab | oklab | xyz | xyz-d50 | xyz-d65.

That said, community feedback shows people want to write and use polar color spaces: hsl | hwb | lch | oklch.

My question to color experts here is: can we support both polar and rectangular color spaces with the spec showed above?

@romainmenke
Copy link
Contributor

My question to color experts here is: can we support both polar and rectangular color spaces with the spec showed above?

Yes

Both rectangular and polar color spaces use 3 channels each and you can define unambiguous value definitions for both rectangular and polar color spaces.

{
  "my-token": {
    "$type": "color",
    "$value": {
      "hex": "#xxxxxx",
      "colorSpace": "hsl",
      "channels": [270, 0.5, 0.3],
      "alpha": 0.6
    }
  }
}

Could be equivalent to hsl(270deg 50 30).

That is to say, these are all just numbers and as long as there is a clear definition of what the numbers mean it will work out fine.

I would suggest staying close to the CSS value definitions : https://drafts.csswg.org/css-color-4/#the-hsl-notation

Staying close to CSS makes it possible to use more of the shelf tooling around color and facilitates copy/pasting of values.


I would suggest using a different term than colorSpace if the intention is to adopt such a wide range of possible notations. Since not every notation might be a color space.

Maybe colorScheme?

@o-t-w
Copy link
Author

o-t-w commented May 16, 2024

The term color scheme is often used interchangeably with theme. E.g.: https://developer.mozilla.org/en-US/docs/Web/CSS/color-scheme

Maybe "syntax" or "notation"?

@romainmenke
Copy link
Contributor

romainmenke commented May 16, 2024

Right, color scheme is much more widely known in the context of dark/light mode.

notation works for me

@marcedwards
Copy link

marcedwards commented May 16, 2024

My question to color experts here is: can we support both polar and rectangular color spaces with the spec showed above?

Color space typically describes which colors are possible, and how they’re mapped within the available gamut.

It’s usually referred to as color model or color mode when talking about how the actual values describe the color (within the color space). With that in mind, RGB, HSB, HSL etc are all color models or color modes.

It’d be good to not conflate the two concepts in the spec. With that in mind, this could be good?

By default, sRGB should be assumed as the color space, and RGB should be assumed as the color mode.

{
  "my-token": {
    "$type": "color",
    "$value": {
      "channels": [0.1, 0.2, 0.3],
      "alpha": 0.6
    }
  }
}

But, a color space and color mode can be provided.

{
  "my-token": {
    "$type": "color",
    "$value": {
      "colorSpace": "display-p3",
      "colorMode": "hsl",
      "channels": [0.1, 0.2, 0.3],
      "alpha": 0.6
    }
  }
}

Personal take: after running into serious color conversion issues and broader team confusion, I consider the hsl() notation harmful, and would encourage practitioners to move away from it.

My personal stance on this is that RGB is the native format, and typically how the data is represented in textures on the GPU. RGB is the format with a lower risk of rounding errors and other weirdness, and given it’s the default assumption for color mode, it’s the safest. I try not to store colors in HSL or HSB, but… I do see the value in it and I bet lots of people will want it.


I guess another part of this conversation is that without color spaces, there are colors you can not describe and can not achieve. Color spaces are extremely important. If early spec versions are missing some color modes, I think that’s less of an issue — you can always just convert the values to RGB to get the correct color.

@kaelig
Copy link
Member

kaelig commented May 16, 2024

I would suggest using a different term than colorSpace if the intention is to adopt such a wide range of possible notations. Since not every notation might be a color space. – @romainmenke

It’d be good to not conflate the two concepts in the spec. – @marcedwards

The CSS spec seems to use "color space" to describe concepts like spaces, models, and notations, which has left me confused 😅 For example, I couldn't explain to someone why hsl is simultaneously a notation expressing colors in the srgb color space, and a radial color space (note that I'm not criticizing the spec, but rather pointing out how it can be confusing to some without the level of expertise as people who write the CSS spec).

@svgeesus your guidance on naming would be extremely appreciated 🙏🏻

@marcedwards
Copy link

I guess that just happened becuase these names are more of an industry convention that has evolved over time, rather than an agreed upon spec? For me, “color space” is something I try to avoid saying unless I’m describing gamut (and all the other things that make up a color space).

@marcedwards
Copy link

Also, maybe I’ve oversimplified things a little too much, because color spaces can also dictate the color model used.

@DominikDeak
Copy link

DominikDeak commented May 17, 2024

I think a good way to alleviate confusion is to look at what ICC Standard uses for terminology. In my opinion, following the convention used by the authority on colour management and being consistent with their vocabulary is good practice.

When most people speak of "colour spaces", they probably actually meant Colour Profiles, which define the following properties:

  • Colour Gamut - specifies the range of colours can be rendered by a device, or captured by a device, or a universal non-device specific colour range (such as sRGB, Display-P3, Rec.2020, etc).
  • White Point - specifies the colour temperature of the brightest representable colour.
  • Colour Space - specifies how the colour channels mathematically map the entire gamut range. Typically there are four main categories of this: 1.) trichromacity, such as RGB, CMY(K); 2.) colour opponents, such as L*a*b*; and 3.) hue models, such as HSL, or HSV; and finally 4.) monochromacity, such as Y (luminance).
  • Transfer Function - specifies how the numerical values of each individual colour component is encoded and stored in memory. For example, many colour profiles save colour components as non-linear values, dedicating more precision to darker tones, and less precision to lighter tones. Quite important for low-precision values (8-bits per channel). Sometimes you hear the term image "gamma", which is an oversimplified hand-wavy description of the Transfer Function used for encoding values.

Some special Colour Profiles do not have a Colour Gamut, nor do they use a particular Transfer Function (i.e. values are interpreted directly without gamma correction). Examples of this are CIEXYZ, CIELAB, and OKLAB, and they only specify a Colour Space, because they are gamut-independent absolute spaces.

So, if we were to follow ICC way of specifying properties, the following two attributes the should be present: the Colour Profile, and the Colour Space (if applicable). Using and modifying the aforementioned examples, my proposal would look like this:

{
  "my-token": {
    "$type": "color",
    "$value": {
      "colorProfile": "display-p3",
      "colorSpace": "hsl",
      "channels": [0.1, 0.2, 0.3],
      "alpha": 0.6
    }
  }
}

For profiles that have an absolute colour space, the colorSpace property can be omitted, as the space is implied by the colorProfile itself:

{
  "my-token": {
    "$type": "color",
    "$value": {
      "colorProfile": "oklab",
      "channels": [0.1, 0.2, 0.3],
      "alpha": 0.6
    }
  }
}

@Shrinks99
Copy link

Because @kaelig asked for comments, will just mention that I'm generally quite happy with where this is ending up. We seem to have rejected calls for simplification for simplification's sake and are instead converging towards a method that can correctly encode colour information. Good stuff! The future for design tokens looks bright :)

@romainmenke
Copy link
Contributor

romainmenke commented May 17, 2024

I said :

I would suggest using a different term than colorSpace if the intention is to adopt such a wide range of possible notations. Since not every notation might be a color space.

Maybe colorScheme?

I've changed my mind about this.
I think colorSpace is perfectly fine.

I don't want to derail this thread in pursuit of finding a perfect name that encompasses all possible values. I also do not want to kick-off a fresh design fase for the schema of color values.

I think everyone will understand the meaning and intention behind colorSpace even if there is support for a value that, from a color theory perspective, isn't really a color space.

@ilikescience
Copy link

I'm with @romainmenke here, I think colorSpace does a great job.

From Wikipedia:

Since "color space" identifies a particular combination of the color model and the mapping function, the word is often used informally to identify a color model.

@nesquarx
Copy link

nesquarx commented May 18, 2024 via email

@marcedwards
Copy link

Yep! I’m all for whatever gets it done. And maybe @DominikDeak ’s suggestion of colorProfile is the least ambiguous for that portion.

@danosek
Copy link

danosek commented May 30, 2024

I'm using Oklch as a primary space in our design system mainly used in web apps. We also need another spaces for Figma, MS Office etc. We don't use Design Tokes Format Module yet, just plain JS object. This is how it would looked like:

{
    "$name": "amber-30",
    "$humanName": "Amber 30",
    "$css": "--amber-30",
    "$modifier": "30",
    "$bgInteractionMode": "darken",
    "$type": "color",
    "$value": "oklch(0.84 0.1 82)",
    "$colorSpaces": {
        "oklch": "oklch(0.84 0.1 82)",
        "hex": "#ebc57d",
        "rgb": "rgb(240 200 130)",
        "hsl": "hsl(39 74% 71%)"
    }
}

What I would improve is that if $type: color has multiple values, the name of the key could represents name of color space. I don't think there is a need for hex to be split into channels. It is so widely used as is, so there is no need to complicate its definition.

{
    "$type": "color",
    "$value": {
        "oklch": {
            "channels": [0.84, 0.1, 82],
            "alpha": 0.5
        },
        "hex": "#ebc57d80",
        "rgb": {
            "channels": [240, 200, 130],
            "alpha": 0.5
        },
        "hsl": {
            "channels": [39, 0.74, 0.71],
            "alpha": 0.5
        },
    }
}

@drwpow
Copy link
Contributor

drwpow commented Jun 13, 2024

What I would improve is that if $type: color has multiple values, the name of the key could represents name of color space. I don't think there is a need for hex to be split into channels. It is so widely used as is, so there is no need to complicate its definition.

We considered that, but have decided to just have 1 token = 1 color (1 colorSpace) for now. It would be overly complex for tooling to look at a color and have to sift through which colorSpaces are / aren’t available for a given token, and “rank” what it wants to pull as the color (because, for example, many OKLCH colors have no parallel in HSL, and so it’s opinionated how you’d even go about converting one to the other).

hex is just a fallback for sRGB, not a competing colorSpace, meant to encourage wider support for older hardware where only sRGB is supported. For that reason it’s optional and can be omitted if you don’t need it. But many platforms will, and again, gamut clipping is opinionated and there’s no absolute way to do it.

Lastly, taking gamut clipping out of the equation, if you’re converting from one colorSpace to another, it’s trivial to do with libraries available like culori, so in a sense, declaring multiple colorSpaces on one token is redundant and unnecessary work. No matter what form your color tokens are in, you’ll still have to transform them anyway when delivering code to any platform (web, Android, iOS, etc), and make per-platform decisions on how to do so. A design goal of the DTCG spec is to have platform-agnostic tokens that you can apply platform-specific decisions to later; not to have to solve all your platform problems in one place (even assuming that’s possible).

@drwpow
Copy link
Contributor

drwpow commented Jun 13, 2024

Also, a few notes amended to @kaelig’s updated proposal:

Channel definition

It’s been raised “what are the channel values and how are they defined?” For example, is "colorSpace": "srgb", "channels": [64, 0, 128] valid? For clarity, we’ll use the same definitions as CSS Color Module 4’s color() function, which defines colorspaces and values in §10, such as srgb channels being red, blue, green in that order, and accepting only [0, 1] values.

The only clarification we’d make is that percentages wouldn’t be supported, since there is no percentage type (yet). Just normalize to 1 (0% = 0, 100% = 1).

Aliasing

Also a question of “what is aliasable” is inevitable, and we’d like to propose Any sub-value is aliasable if it resolves to a valid $type.. For example:

✅ Valid: Entire $value

This is supported today, of course

{
  "action": {
    "$value": "{color.blue.500}"
  }
}

✅ Valid: Aliasing part of a value is valid only if it aliases a valid existing $type

This is consistent with current docs for $type:

If the token’s value is a reference, then its type is the resolved type of the token being referenced.

In other words, this seems allowed by current usage of spec.

Further, referencing within "channels" should be allowed because even though there is no array of number types, individual values are all $type: number as well. Just as though gradient tokens’ stops aren’t a $type in and of themselves, they are allowed to alias color and number tokens.

{
  "action": {
    "$type": "color",
    "$value": {
      "colorSpace": "oklch",
      "channels": ["{lightness.70}", 0.153, 246.18],
      "alpha": "{alpha.100}",
      "hex": "#3ca5f7"
    }
  }
}

❌ Invalid: Aliasing part of a value that is not a valid $type

Since there is no valid types that "colorSpace" or "channels" could resolve to, they’re invalid. Further, since string types are not supported, and colors are now objects, technically "hex" could not resolve to a valid $type, either.

{
  "action": {
    "$type": "color",
    "$value": {
      "colorSpace": "{colorSpace.oklch}",
      "channels": "{channels.color.blue.500}",
      "alpha": 1,
      "hex": "{color.blue.500}"
    }
  }
}

❌ Invalid: Partially aliasing just a part of another $type: color’s value

This is another proposal entirely: #148

{
  "action": {
    "$type": "color",
    "$value": {
      "colorSpace": "oklch",
      "channels": [0.7, 0.153, 246.18],
      "alpha": 1,
      "hex": "{color.blue.500.$value.hex}"
    }
  }
}

@kaelig
Copy link
Member

kaelig commented Aug 22, 2024

Update: I just edited the comment below to mention we'd be only supporting HEX 6 as a hex fallback (not HEX 3, 4, 8), because the alpha is controlled by the alpha property.

#137 (comment)

@mck
Copy link

mck commented Sep 23, 2024

I'd like to propose adding support for the CMYK color model to the specification. This enhancement aligns with our goal of making the specification more flexible and future-proof by accommodating various color spaces.

Proposal Details:

  1. Extend the Color Token Format:
    • Introduce the ability to specify colorSpace as "cmyk".
    • Allow channels to accept an array of four values representing the Cyan, Magenta, Yellow, and Key (black) components.
  2. Example Syntax:
    Using decimal values between 0 and 1:
    {
      "$type": "color",
      "$value": {
        "colorSpace": "cmyk",
        "channels": [0.0, 1.0, 1.0, 0.0],
        "alpha": 1.0,
        "hex": "#ff0000"
      }
    }
    Or using percentages between 0 and 100:
    {
      "$type": "color",
      "$value": {
        "colorSpace": "cmyk",
        "channels": [0, 100, 100, 0],
        "alpha": 1.0,
        "hex": "#ff0000"
      }
    }

Benefits:

  • Print Design Integration:
    • Seamless Workflow: Facilitates a smoother integration between digital and print design processes by allowing designers to specify print-ready colors directly in design tokens.
    • Accuracy: Reduces the need for manual color conversions and minimizes discrepancies between on-screen designs and printed materials.
  • Specification Flexibility:
    • Future-Proofing: By supporting CMYK, we make the specification more adaptable to various mediums and industries.
    • Consistency with Ongoing Discussions: This proposal aligns with the existing discussions about supporting multiple color spaces through the colorSpace and channels properties.

@romainmenke
Copy link
Contributor

Important to then be able to specify which cmyk profile.

@drwpow
Copy link
Contributor

drwpow commented Sep 23, 2024

Very much in favor of supporting CMYK but yes would have to specify the profile as @romainmenke said. We wouldn’t have cmyk on its own, but following the other colorspace syntax, it would be cmyk-swop or cmyk-fogra32, etc. Would love for someone who works more regularly with printing to propose what a reasonable set of all those would be.

@svgeesus
Copy link

Uh, before going off and inventing a new syntax, perhaps look at 5.4. CSS and Print: Using Calibrated CMYK and Other Printed Color Spaces in CSS Color 5, which already does calibrated CMYK?

@svgeesus
Copy link

Introduce the ability to specify colorSpace as "cmyk".

That doesn't mean anything, without saying which cmyk colorsdpace (what inks, what paper, what printing conditions, what level of GCR, maximum coverage, etc). All fo which is defined in an ICC profile.

@svgeesus
Copy link

svgeesus commented Sep 23, 2024

{
  "my-token": {
    "$type": "color",
    "$value": {
      "hex": "#xxxxxx", // Only HEX 6 supported (no HEX 3, 4, or 8)
      "colorSpace": "dci-p3",
      "channels": [0.1, 0.2, 0.3],
      "alpha": 0.6
    }
  }
}

Is the sRGB hex a fallback, or the main color?

Is DCI-P3 (a projector space for digital cinema, to be viewed in an entirely dark room) really, means, or display P3?

@drwpow
Copy link
Contributor

drwpow commented Sep 23, 2024

Ah sorry @svgeesus this issue is out-of-date. We’re currently working on updating this PR after some internal discussion, but TL;DR we’re going to align around CSS Color Module 4 (and potentially Module 5). So we’re aligning with display-p3 over dci-p3, e.g.

Is the sRGB hex a fallback, or the main color?

Optional fallback for support on more devices (since color clamping is opinionated)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests