-
Notifications
You must be signed in to change notification settings - Fork 63
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
Pre-defined composite type for refering color modifications (e.g. rgba) #88
Comments
I really like this! I'm wondering if something like Similar to what you described, when I, as a designer, asked our dev's to include different opacities as design token options so that we can modify base colours. I was told that there wouldn't be an easy way to do this. An option was to provide all colour tokens with siblings that have the opacity defined, but that would just create a whole bunch of colour tokens that might not be necessary at the time and confuse designers/devs. In Figma, colour styles also have their opacity baked in. So, it seemed like an easy and quick solution at the time. However, thinking of future use and scale, this is definitely something we should think about. Thank you for sharing! |
Thank you very much for your feedback!
That's a great point! It might become probably a bit confusing what the expected result is, if users add e.g. Maybe it could make the implementation easier, if there were several
( With this scheme, it might also be possible to avoid the {
"background": {
"type": "color-rgba",
"value": {
"color": "{color.base.red}",
"alpha": 0.1,
}
}
} This could also become very powerful when these values could appear on all places where colors are defined, like e.g. the {
"alert": {
"opacity": {
"background": {
"value": 0.1
},
"border": {
"value": 0.3
},
},
"danger": {
"base": {
"type": "color",
"value": "{color.base.red}"
},
"background": {
"type": "color-rgba",
"value": {
"color": "{components.alert.danger.base}",
// Set opacity/alpha to 10%
"alpha": "{components.alert.opacity.background}",
// Reduce red by 10%
"red-relative": -0.1,
}
},
"border": {
"type": "border",
"value": {
// Using composite value instead of #RGBA
"color": {
"type": "color-hsla",
"value": {
"color": "{components.alert.danger.base}",
// Set opacity/alpha to 30%
"alpha": "{components.alert.opacity.border}",
// Increase saturation by 50%
"saturation-relative": 0.5,
}
},
"width": "{border.base.m}",
"style": "{border.base.style}"
}
}
},
}
} Advantages and DisadvantagesThe advantages with this versions could be:
Disadvantages are:
Open question: Value scaleOne open question would be, which units and scale would be most intuitive for relative and absolute values? A few options come to mind:
Open question: Nested color compositesThe second open question would be, is if the For example, should the following example result in {
"text-color": {
"type": "color-rgba",
"value": {
"color": {
"type": "color-hsla",
"value": {
"color": "#ff0000",
"hue": 180
}
},
"alpha": 0.5
}
}
} Unfortunately, this has potential for wrong inputs when non-color types would be added to the {
// Example with invalid content
"invalid-text-color": {
"type": "color-rgba",
"value": {
"color": {
"type": "font",
"value": "Fira Sans"
},
"alpha": 0.5
}
}
} In our opinion, it would be very powerful and flexible for designers to define colors like this. We could define some base colors and generate most other colors in the tokens based on these. If parsers would ignore invalid values such as
We're mainly seeing this from the perspective of Web development, but would love these definitions to be interoperable with all other platforms. Are there different usages and definitions for other platforms like iOS or Android, that would limit the functionality of these color composite types? |
The token JSON format that we're using at Microsoft has two uses of computed colors: Take a base color and overwrite its alpha value{ "Hover": { "computed": { "color": "Global.Color.AccentBase", "opacity": 0.05 } } } That's virtually identical to the solutions proposed above. Take a base color and generate multiple tokens from it{ "Color": { "Grey": { "generate": { "type": "lightness0to100by2", "value": "#808080" } } } } Both of those are pretty important for our scenarios. Our core color palette has dozens of base colors and each one of them has about ten lightness variations, and we want those variations to be expressed programmatically. For a few colors, like grey, we use far more variations. Without some way to express that programmatically, we would need to have a preprocessor that would generate a W3C token file from our "enhanced" token file: a Sass or TypeScript for tokens, effectively. (It's not critical to us right now that arbitrary design tools like Figma be able to perform the color ramp generation from that latter case—just that we can express that in a valid token file.) |
At EA our Opacity adjustments like this:
Though we don’t support it in our token system, this approach could be used in conjunction with transitive transforms so any aliased relationships are maintained through token output, like so in CSS:
Calculated colors look something like this:
The calculated colors
Unlike the alpha adjustments, I don’t think color operations can carry through transitively to token output. |
Lots of really interesting suggestions here. Thanks everyone for sharing! I'm not sure if additional types for modified/generated colors is the way to go though. My concern is that might make things more complex downstream if other things that expect color tokens want to reference those tokens. For example, if you wanted to use them in composite tokens like stops on a gradient or the color of a border. Granted, we could update the spec for those types to allow their sub-values to be Also, thinking ahead a bit, I can imagine other types besides colors also benefitting from some mechanism to manipulate their values. For example dimension tokens being calculated as multiples of other dimension tokens (maybe to generate a spacing scale or something like that). I'm wondering if we can come up with a syntax and mechanism that works for colors today but could be extended to other types too. I therefore really like the approach @jeromefarnum describes, where you can have an array of Let's imagine we could do something like this: {
"some-token": {
"$value": "#ff0000",
"$type": "color",
// list of operations that can modify
// the color value. They are applied in
// the order listed here
"$operations": [
{
"hue": -20
},
{
"alpha": 0.3
}
]
}
} When a token has E.g.: {
"some-token": {
"$value": "#ff0000", // input value
"$type": "color",
// list of operations that can modify
// the color value. They are applied in
// the order listed here
"$operations": [
{
"alpha": 0.5
}
]
// resolved value is #ff00007f
},
"alias-token": {
"$value": "{some-token}"
// value is #ff00007f
}
} Whatever operations we define in our spec would need to be type-specific. The color type might have operations like hue, lighten, darken, alpha, etc., the dimension type might have operations like add, multiply, etc. and some types might have no available operations at all. The rule would be that only compatible operations can be used. E.g. if your token is of type dimension and you use one fo the color operations, that would be considered invalid and tools should reject that token (and probably report an error to the user). However, each operation can have parameters and can defined what types those parameters need to be. Furthermore, the parameters can be references to tokens of the required type. Let's imagine we have a color operation called {
"half": {
"$value": 0.5
},
"some-token": {
"$value": "#ff0000", // input value
"$type": "color",
"$operations": [
{
"alpha": "{half}"
}
]
// resolved value is #ff00007f
}
} So, for each operation we include in the spec, we'd need to define:
What do you think? |
I think this fits our usage of the tokens perfectly and will be especially powerful also when adding extensibility for other values, like dimensions. Thank you very much! Just one question with regards to the usage of the It would be great to be able to define not just the values of an operation in tokens, but also a whole operation itself. This would make it easy to modify or replace operations without the need to change the respective key(s) in all tokens that use these operations. For example: {
// "Global" constants
"constants": {
"mute-transparency": 0.25,
"mute-saturation": 0.15,
"colorize-hue": 0.2
},
// "Global" operations
"operations": {
// Specific "mute" operations (e.g. by transparency or saturation)
"mute-transparency": {
"$type": "operation"
"$value": [
{
"alpha": "{constants.mute-transparency}"
}
]
},
"mute-saturation": {
"$type": "operation"
"$value": [
{
"saturation": "{constants.mute-saturation}"
}
]
},
// "Default" mute
"mute": {
"$type": "operation"
"$value": "{operations.mute-transparency}"
},
// Other operation with multiple definitions...
"colorize": {
"$type": "operation"
"$value": [
{
"saturation": 1
},
{
"hue": "{constants.colorize-hue}"
}
]
}
},
"colors": {
"brand": {
"primary": {
"$value": "#ff0000",
"$type": "color"
}
}
},
"some-token": {
"$value": "{colors.brand.primary}",
"$type": "color",
"$operations": [
"{operations.mute}", // Default mute = {alpha: 0.15}
// Add specific operation
{"invert": 1},
"{operations.colorize}" // [{saturation: 1}, {hue: 0.2}]
]
}
} If we were to change the "default" mute in future to use the "saturation" mute, it could be as simple as changing the "mute": {
"$type": "operation"
- "$value": "{operations.mute-transparency}"
+ "$value": "{operations.mute-saturation}"
}, |
Hello 👋 Thanks very much for all the hard work you've placed into this spec, and specifically to @c1rrus for sharing at the Into Design Systems conf earlier today! I want to pick up on his comment above about generalising the approach to handle transformation of any type of value, not just colours. I think that would be amazing, and would support how we're currently working. In our internal token graph representation, we support intermediate transform nodes between token references in much the same way that is being proposed here, although our syntax is a bit...verbose: {
"opacity": {
"half": {
"value": 0.5
},
},
"color": {
"red": {
"value": "#F00"
},
},
"system": {
"color": {
"errorMuted": {
"value": {
"ref": "color.red",
"transforms": [
{
"type": "alpha",
"args": {
"alpha": {
"ref": "opacity.half"
}
}
}
]
}
}
}
}
} "alpha" here is just a function we registered with some named, typed arguments and a return type so we can validate the connections between nodes. I imagine there are lots of super useful out-of-the-box transformations that everyone would find useful, but the ability to register arbitrary custom transform functions in the parsing/resolution API would also be super useful. One difference that I think could be important between your proposed schema and the one I present here is that, in the example above, the Thanks very much again! Looking forward to following this project |
I played around with that idea for small design research project for a client of mine: I like the proposal @c1rrus made using I think it makes the communication across teams and roles a lot easier, because it would allow us to talk about the abstract idea rather than its implementation. Every designer I know, knows what a complementary color is, or the best contrasting color on a certain background. But how to actually implement that can be quite technical, and its implementation sometimes requires pretty complex functions. I tend to call this concept a "Rule Token" because I identified two main use cases: A function that produces a "value" just like the proposed operations, but also |
It's occurring to me that we might not have explicitly specified that any unrecognized nodes in the JSON starting with |
Something along the lines of what's being discussed here is critical to the work I'm doing right now as well. I'm working on FAST (Microsoft's web components foundation) including a design system capability we call Adaptive UI. As mentioned in #81, design systems are really a description of relative choices. Unfortunately, a lot of the ways we've been able to express this in the past have lost track of that fact, for instance, stateful color swatches being expressed as fixed indexes on a manually created color palette without the logic of how we got there. The system we've built (and are now evaluating how to evolve to this common format) is algorithm centric. Keeping with color, in our case a palette is generated from code and passed in whole to color "recipes", which operate on the palette and other information like contrast requirements or palette offsets to produce a color or set of colors, like for use in rest, hover, and active states. You define the rules of your design, the system figures out the values. The "adaptive" portion of this is that you can change one of the inputs and in real-time see the outputs update, for instance, for custom theme colors, lighter or darker, increased contrast, etc. These recipe functions have been written to be general-purpose with configurations, but there's no way they could be replicated with a simple base library of included transformations. This will be the case for any complex design system that someone intends to implement and attempts to describe in a format that primarily supports only references / aliases. We extend the recipes model to other areas as well including typography, density (padding & margins), motion, etc. We were considering a structure using custom composite types to represent this, though that felt more like it was being forced into that structure than purpose-built for it. Linking to issue #54, which removed support for that citing ease of understanding and implementation. In the composite type model we considered defining a list of attributes that included both inputs and outputs, essentially flattening the structure of a function. We would later reference the output attributes in more specific tokens. See #148 for a related point to this. The In the spirit of principle two, being "focused, yet extensible", my primary concern with this draft is making sure the scenarios described here are not precluded. I share the concern in the original post that individual tools or products will work around this and will end up with models that can't universally be supported. It's likely the needs are going to be more complicated than can be expressed in simple terms that all tools can be built to support, but it seems preferable if we have a way to define the customizations we need without diverging from the standard format with the expectation that all tools won't be able to support all scenarios. This is going to be true with Building on the above suggestions, I support the idea of being able to register a custom function that a processor knows how to evaluate. Perhaps someday there is a standard on how to load those functions or reference them in the tokens doc similar to how a CSS Paint API worklet is registered. A more general word like In order to define this in a way that can be expressed and validated within the document, the format for the We'd need to be able to individually reference output attributes in subsequent tokens, as I've described separately as #148. In my stateful recipe example, that would allow for binding the I can work on some configuration samples if the base requirements make sense. I was going back and forth on how to express the token An alternative to this is we add language to the document to indicate that any tools must preserve anything they don't understand, allowing for custom definitions of types and functions in a non-standardized way, which may see some common patterns emerge. |
Rather than re-inventing your own color modification syntax, have you considered looking at the CSS Color 5 relative color syntax and I'm seeing some mentions of modifying |
From the Web development perspective as Design token users, the CSS Color 5 syntax and functions (like But with regards to this syntax there are a few questions open: 1. Where will the operations be defined?Currently, values of the
As it is already discussed in #137, the goal is both supporting colors in other color spaces than sRGB but also limiting the scope of the v1 spec, which might be conflicting. I'd like to avoid moving the discussion from that issue over here, but would like to just mention that maybe the decision of where we want to put the modifications might also influence the decision how we want to define colors in tokens themselves: 1.1. Do we need a new
|
There's been a few asks for the ability to refer to a token and transform it before finally resolving the value to downstream platforms. As an example, taking a primary color and adjusting the saturation for the hover color. I happened upon this very issue and saw the idea of an Operations should have a limited, low-level set of agreeable implementationsAn It would be more manageable to agree on low-level primitives, most of which already exist on Operations should be highly customizableColor isn't the only kind of token that people are looking to transform. Creating a typography scale could be done using this method by supplying a base and scale. Other tokens could be computed if given the ability to do so. Collections of operations should be reusableWith a low-level sort of language, we'll need to provide abstractions that can be reused with a reference to those operations. Importing these operations can spread the steps in place. The following example is very verbose but the blog post above has a recommendation for a reusable abstraction. {
"primary-color-overlay": {
"$type": "color",
"$value": "#fffc00",
"$operations": [
0.5,
["String.match", "$value", "#([0-9A-Fa-f]{2})"],
["String.match", "$value", "#(?:[0-9A-Fa-f]{2})([0-9A-Fa-f]{2})"],
["String.match", "$value", "#(?:[0-9A-Fa-f]{4})([0-9A-Fa-f]{2})"],
["Math.parseInt", "$1", 16],
["Math.parseInt", "$2", 16],
["Math.parseInt", "$3", 16],
["String.concat", ",", "$4", "$5", "$6", "$0"],
["String.concat", "", "rgba(", "$7", ")"]
]
}
} |
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:
Now, in order to define a component which is based on these variables, would use references to these values:
But in our Design system, we'd like to reference the base color and modify it (e.g. add adding opacity to the value):
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 typecolor
.A potential workaround would be to use the calculated
#RGBA
value and add the reference to theextensions
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
: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):
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:
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: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: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
orcomputed-color
as values.One potential format could be for example:
This might eventually also enable further modifications to the color, like e.g.:
The text was updated successfully, but these errors were encountered: