Hi,
Our Design and Development teams are currently trying to implement a new Design system based on the specified document as of 2021-12-13 (with the current proposal of limiting composite types (#86) in mind). The whole standard is awesome, and we'd like to thank everyone involved for all the great work done!
Problem definition
We're currently struggling to create a correct colors.tokens.json
with colors that use references parts of other colors, e.g. the RGB value or the opacity.
Our Design system consists of multiple levels of color definitions which reference to each other to provide consistency, but also long-term flexibility.
For example:
{
"base": {
"red": {
"type": "color",
"value": "#ff0000"
},
"green": {
"type": "color",
"value": "#00ff00"
},
"blue": {
"type": "color",
"value": "#0000ff"
},
"yellow": {
"type": "color",
"value": "#ffff00"
},
"background": {
"type": "color",
"value": "#ffffff"
},
"foreground": {
"type": "color",
"value": "#000000"
}
},
// ...
}
Now, in order to define a component which is based on these variables, would use references to these values:
{
// ...
"components": {
"alert": {
"error": {
"background": {
"type": "color",
"value": "{base.red}"
},
"foreground": {
"type": "color",
"value": "{base.foreground}"
}
},
// ...
},
},
// ...
}
But in our Design system, we'd like to reference the base color and modify it (e.g. add adding opacity to the value):
{
// ...
"alert": {
"modify": {
"background": {
"opacity": {
"value": 0.1
}
},
"border": {
"opacity": {
"value": 0.3
},
}
},
"error": {
"background": {
"type": "color",
// Invalid value
"value": "rgba({base.red}, {components.alert.modify.background.opacity})"
},
"border": {
"type": "border",
"value": {
// Invalid value
"color": "rgba({base.red}, {components.alert.modify.border.opacity})",
"width": "{border.base.m}",
"style": "{border.base.style}"
},
}
}
}
// ...
}
Unfortunately, this would result in an invalid tokens file: Currently only colors in the formats #RRGGBB
and #RRGGBBAA
are allowed at a value field when it has been defined as type color
.
A potential workaround would be to use the calculated #RGBA
value and add the reference to the extensions
field.
For us, keeping the references in a standardised way which will be supported throughout code and design is important. A custom composite type would cover this use case as well, but this part will not be part of the specification MVP.
Concerns
We're concerned that not covering this use-case early would cause fragmentation in the ecosystem over time. If there's no common style for modifying the alpha value (or maybe even hue, saturation or lightness as example), there might be plugins and converters requiring different formats, which make it hard to create an easy-to-write file that works everywhere.
Example: Fictional workflow with Figma, Style Dictionary and Storybook
(Just to clarify, this workflow is fictional - all formats and decisions listed here are just exemplary)
The designers are creating their designs with Figma tokens, which might in a future version for example use their own extension com.figma.tokens
:
{
"background": {
"type": "color",
"value": "{color.base.red}",
"extensions": {
"com.figma.tokens": {
// Note the opacity written as percentage (10% = "10") in this example
"opacity": 10
}
}
}
}
Another tool might use it's own extension or format to store the modification. For example, the documentation of style-dictionary's transitive transforms lists the following format in the examples (to provide compability with chroma-js):
{
"background": {
"type": "color",
"value": "{color.base.red}",
"modify": [{
"type": "alpha",
"amount": 0.1
}]
}
}
Generating the documentation from the tokens file in a tool like Storybook might get hard at this point, because there could be multiple sources of truth for opacities. If there was for example a generic <DesignTokens>
renderer, how could it know how to correctly display the defined color?
As a workaround, it would always be possible to write converters between these tools, formats, and extensions. Having the format for these standardised would greatly benefit interoperability for users.
Benefits of referenced opacity values in tokens files
Our main motivation for referencing colors or modifications is to retain the references in CSS variables. We'd like to use the design tokens to create CSS variables directly from the token files, for example with this result:
/* base.css */
:root {
--color-base-red: #f00;
--color-base-green: #0f0;
--color-base-blue: #00f;
--color-base-yellow: #ff0;
--color-base-background: #fff;
--color-base-foreground: #000;
/* Additional RGB values could be added by the CSS generator to support usage in rgba() */
--color-base-red-rgb: 255, 0, 0;
/* ... */
--color-alert-background-opacity: 0.1;
--color-alert-border-opacity: 0.3;
--color-alert-error-background:
rgba(var(--color-base-red-rgb), var(--color-alert-background-opacity));
--color-alert-error-border:
var(--border-base-m) var(--border-base-style) rgba(var(--color-base-red-rgb), var(--color-alert-border-opacity));
}
/* components/alert.css */
.alert-component--error {
background: var(--color-alert-error-background);
border: var(--color-alert-error-border);
}
We would benefit from using CSS variables instead of using the direct values once we enable other themes (e.g. a "dark" or "high contrast" version). It wouldn't be required to re-generate the whole file, but just to replace the some of the base variables in e.g. another dark.tokens.json
file:
{
"base": {
"red": {
"type": "color",
"value": "#200000"
},
"green": {
"type": "color",
"value": "#002000"
}
// ...
},
"components": {
"alert": {
"modify": {
"background": {
"opacity": {
"value": 0.2
}
},
"border": {
"opacity": {
"value": 0.4
}
}
}
}
}
}
Since the references in the second file would still be intact, it could generate a second file for (prefers-color-scheme: dark)
, which could only overwrite the new values and still work:
/* dark.css */
:root {
--color-base-red: #200000;
--color-base-green: #002000;
--color-alert-background-opacity: 0.2;
--color-alert-border-opacity: 0.4;
}
Proposal: Additional pre-defined composite type
We would like to propose adding basic color modifications to the MVP. This could help avoiding the need for parsing different color syntaxes (#79) and prepare for a future implementation of computed token values (#81).
We have no preferred syntax for this, but assume that an additional pre-defined composite type for "modified colors" would fit in best with the current state of the specification. This could also enable other composite types to use color
or computed-color
as values.
One potential format could be for example:
{
"background": {
"type": "modified-color",
"value": {
"color": "{color.base.red}",
"modify": {
"opacity": 0.1,
}
}
}
}
This might eventually also enable further modifications to the color, like e.g.:
- Hue
- Saturation
- Lightness
- Red
- Green
- Blue
- Tint
- Shade