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

Conditional token values #169

Open
romainmenke opened this issue Sep 13, 2022 · 17 comments
Open

Conditional token values #169

romainmenke opened this issue Sep 13, 2022 · 17 comments

Comments

@romainmenke
Copy link
Contributor

romainmenke commented Sep 13, 2022

see : #2

That issue was focussed on "theming" and already contains some great thoughts on that but I don't think that themes are the right abstraction. I also do not want to derail that thread.


The basic premise is fairly simple and shared with the concept of themes :

Change token values when X changes, where X is something external to the tokens file

Examples of factors that could affect token values :

  • screen size
  • ability to display certain colors on a screen (srgb, display-p3)
  • user preferences for color schemes (dark, light)
  • applying different branding (brand X, brand Y)
  • platform type (Android, iOS, Web)
  • media type (screen, print)
  • support for features

The most mature system/API that exists for this is most likely Conditional At Rules in CSS (@supports and @media specifically)

In CSS Conditional At Rules "wrap" around style declarations.

.foo {
  color: cyan;
}


@media (min-width: 300px) {
  .foo {
    color: purple;
  }
}

@media screen and (prefers-color-scheme: dark) {
  .foo {
    color: yellow;
  }
}

For Design Tokens I think the inverse makes the most sense.
A token describes a base value and a list of conditional values.

{
  "$value": "cyan",
  "$conditionalValues": [
    {
      "conditions": [{
        "name": "screen-min-width",
        "value": "300px"
      }],
      "value": "purple"
    },
    {
      "conditions": [
        {
          "name": "media-type",
          "value": "screen"
        },
        {
          "name": "color-scheme",
          "value": "dark"
        }
      ],
      "value": "yellow"
    }
  ]
}

The exact data shape for "conditions" is likely non-ideal for all possible cases.
But I think it illustrates the point.

Condition names would need to be a specced list.
Condition values per name would also need to be specced.


Design tools would provide UI to assign conditional values on tokens and would also use them.

  • Artboards might match certain conditions based on config (artboard size, color scheme, ...)
  • Certain actions might trigger certain conditions (printing a document)
  • ....

Translation tools could generate the code for the conditionals so that developers can use a single token name while still having a dynamic outcome.


Specialised tools could provide validation.
You might want to "lint" a tokens file and check that all colors have a dark and light variant.

@phun-ky
Copy link

phun-ky commented Sep 13, 2022

To be honest, I do not think logic or conditions should exist in the spec. Design tokens are meant to contain raw values to be processed.

@romainmenke
Copy link
Contributor Author

To be honest, I do not think logic or conditions should exist in the spec. Design tokens are meant to contain raw values to be processed.

I actually agree and should have included this in my initial statement.
I think this is best solved by having multiple token files.

But if something like "theming" is handled by the spec I think it should be more abstract and include the cases listed above.


In our PostCSS plugin for style dictionary we solve this outside of token files : https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-design-tokens#is

@design-tokens url('./tokens-light.json') format('style-dictionary3');
@design-tokens url('./tokens-dark.json') when('theme-blue') format('style-dictionary3');

.foo {
	color: design-token('color.background.primary');
}

The value of color.background.primary changes depending on user config when building the CSS.

The same mechanic can be expanded on later to include conditions typically found in @media or @supports.

@nesquarx
Copy link

nesquarx commented Sep 13, 2022 via email

@jeromefarnum
Copy link

At my last company I put together something similar to the $conditionalValues @romainmenke is proposing above, to solve for nearly the same requirements (if not an identical solution, the same in spirit). It required a great deal of additional complexity in our token translator, and all formatters had to accommodate these mystery box tokens.

As I am at a new company, and am yet again standing up some design token infrastructure; I’ll be opting to use the file switching method and perhaps some post-translation actions to generate additional token documents with media queries and responsive values.

In any case, I feel like the existing spec could handle the proposed approach by using a custom entry on the $extensions property.

@romainmenke
Copy link
Contributor Author

In any case, I feel like the existing spec could handle the proposed approach by using a custom entry on the $extensions property.

https://tr.designtokens.org/format/#extensions

In order to maintain interoperability between tools that support this format, teams and tools SHOULD restrict their usage of extension data to optional meta-data that is not crucial to understanding that token's value.

Using $extensions for this use case is discouraged by the spec.
It should be solved with multiple files or in the spec itself.

@romainmenke
Copy link
Contributor Author

At my last company I put together something similar to the $conditionalValues @romainmenke is proposing above, to solve for nearly the same requirements (if not an identical solution, the same in spirit). It required a great deal of additional complexity in our token translator, and all formatters had to accommodate these mystery box tokens.

@jeromefarnum Forgot to ask, but did this work well once the tooling was there to support it?

@jeromefarnum
Copy link

Using $extensions for this use case is discouraged by the spec. It should be solved with multiple files or in the spec itself.

Very fair point.

At my last company I put together something similar to the $conditionalValues @romainmenke is proposing above, to solve for nearly the same requirements (if not an identical solution, the same in spirit). It required a great deal of additional complexity in our token translator, and all formatters had to accommodate these mystery box tokens.

@jeromefarnum Forgot to ask, but did this work well once the tooling was there to support it?

@romainmenke let me try to answer this in short, and also with more detail should it help others.

TLDR; it worked well enough in the areas of our workflows where we implemented it, however as we didn’t have it implemented fully in our design tools it became a source of confusion and human error. Our team started with many conditional values as we initially built our design system, but over time greatly reduced the number of conditional tokens as the cost of system complexity wasn’t returned in added value (design systems have a natural inertia towards this, I think). At the time of my departure we were in the process of fully stripping out these conditional tokens in favor of a file swapping approach to handle the relatively few conditional tokens we had.

Functionally, after doing all the work to implement the feature, it worked well enough in the token translator. Though the additional complexity did make adding some features later more difficult.

We also had to add support for the feature to our token documentation site so designers could understand that SOME tokens had different values under SOME conditions. Frankly, as the person that was responsible for the token documentation site, I think how I communicated that wasn’t as successful as it should have been. We regularly had designers getting confused as to which value should be used for a token.

Part of the problem was that we didn’t have a proper plugin to connect our tokens to our design tools; or more precisely our plugin was in beta and wasn’t feature complete enough for our team to daily drive it. IIRC these responsive tokens were one of the more complex features to add to the plugin, where many of the other features were completed but this feature was one of a small set that was causing the plugin development to stretch out.

One aspect that made it challenging to implement in design tools was that we initially thought to use the artboard size to determine which conditional value to use, but we discovered that a large portion of the time tokens where being used was in component masters (pretty sensible) which nearly always used arbitrary artboard sizes. In those cases the token value that was selected due to artboard size was often not as desired; requiring the designer to manually configure which condition to use for the artboard.

In the end designers needed to intervene so often to select which conditional values should be used, IMO, it’s simply easier to use a file swapping method, use whatever complex conditional logic to swap files as needed and generate as many themes as needed at the token-translator stage to provide designers with the appropriate options they need (probably a matrix of light/dark and device/platforms covers the large majority of teams.) This approach likely covers most teams’ needs without having to clutter the specification or adding undue complexity for tooling builders.

/rant

@romainmenke
Copy link
Contributor Author

@jeromefarnum Thank you for sharing this experience.

From this I read that it makes the simple things a little bit easier while making the hard things much harder.
That designers and developers end up having to work around the system instead of using it.

The benefits of solving it outside the spec with multiple files:

  • we can always revisit this later if someone has a really good idea
  • each context (design program, translation tool, ...) can create something to handle multiple files that works well for them

@kevinmpowell kevinmpowell added this to the Next Draft Priority milestone Oct 3, 2022
@kevinmpowell kevinmpowell removed this from the Next Draft Priority milestone Oct 17, 2022
@ilikescience
Copy link

I agree that tokens files aren't the right place to store conditional logic that applies on a platform level — the place to solve that problem is on the platform!

@Sidnioulz
Copy link

Commenting here with a similar issue and a slightly different proposal made on amzn/style-dictionary#909. I believe there's room in the spec for tokens to have multiple value properties, though without embedding the logic as to what they mean.

I have developed multi-brand white-label token systems before and it made perfect sense there to use multiple files, but I believe the use case brought up by @romainmenke is legitimate and distinct.

In my current org, we have responsive size tokens, responsive typography tokens (size and leading), typography fonts that depend on country, dark mode, some token values changing for web vs mobile, and soon high-contrast too. If we were to have a files-based approach,

  • we'd have 8 or 9 token files with lots of repetition
  • we'd need to write completely custom code to deliver tokens for some platforms by aggregating the 8 or so files (some declarative styling frameworks like Tailwind require us to declare the whole CSS class at once)
  • we'd find it harder to check our source of truth to understand the possible values a token can take

For those who don't know, SD processes tokens with two layers: transforms first process the value and attributes of individual tokens, and formats then aggregate a group of tokens to produce a platform-specific output.

I've come up with a proposal to only add extra value fields to a token because:

  • It would allow us to apply transforms consistently to all value properties of a token
  • It would allow us to encode arbitrary logic, potentially platform-specific at times, in our SD formats

If I were to generalise, additional $value fields in the spec would:

  • Allow value processing / display / editing tools to handle multiple value fields with little impact (just need to declare the value field names)
  • Let end-of-the-pipeline output tools encode arbitrary logic, possibly with manual coding or possibly with proprietary conventions developed by such tools

I believe this proposal could provide much of the value you seek, @romainmenke, whilst avoiding the complexity linked to having logic management in a spec. I'm curious to have your opinion on it.

@romainmenke
Copy link
Contributor Author

@Sidnioulz Can you give a concrete example of how a tokens json file would look like under your proposal?

@Sidnioulz
Copy link

In SD format:

{
  "color": {
    "value": "#444",
    "value_hc": "#222"
  },
  "size": {
    "value": 24,
    "value_breakpoint-md": 32
  },
  "fontFamily": {
    "value": "Scto Grotesk",
    "value_lang-el": "Noto Sans",
    "value_lang-jp": "Noto Sans"
  }
}

In W3C token format, I'm not sure what the best would be. I could have the same naive approach:

{
  "color": {
    "$value": "#444",
    "$value_hc": "#222"
  },
  "size": {
    "$value": "24px",
    "$value_breakpoint-md": "32px"
  },
  "fontFamily": {
    "$value": "Scto Grotesk",
    "$value_lang-el": "Noto Sans",
    "$value_lang-jp": "Noto Sans"
  }
}

Or one with an $extraValues that simplifies tooling support for such extra values, and also saves tool users from declaring the names of the custom value fields:

{
  "color": {
    "$value": "#444",
    "$extraValues": [
      "high-contrast": "#222"
    ]
  },
  "size": {
    "$value": "24px",
    "$extraValues": [
      "breakpoint-md": "32px",
    ]
  },
  "fontFamily": {
    "$value": "Scto Grotesk",
    "$extraValues": [
      "lang-el": "Noto Sans",
      "lang-jp": "Noto Sans"
    ]
  }
}

Or this could technically be hosted under $extensions and be invisible for the token spec itself. Because the W3C spec has a wider range of consumers than SD, I wouldn't necessarily propose the same format, but I haven't yet given it enough thought to have an opinion.

@romainmenke
Copy link
Contributor Author

I think there is little difference between my original schema and this proposal :)

It is a bit terser because it groups conditional feature names and values in a single string. That makes it seem simpler but it will complicate things for tools.

@Sidnioulz
Copy link

Yes, the goal is to allow tools to process what the conditions mean as a black box. It's easier for tools to display a name for an extraValue / conditionaValue if it's a simple string than if it's a more advanced format. If it's a more advanced format, with predefined conditions/values defined in the spec, then tool makers will need to continually add support to the specified conditions. Making it more basic, in my opinion, makes it easier to ignore for tools that don't need to have an opinion on it.

Though, to clarify, I'd also be very happy if what you proposed initially made its way into the spec :)

@romainmenke
Copy link
Contributor Author

Ideally tools only hide or ignore parts of a tokens file because they recognize the content and know that it is irrelevant for their context.

hidden because known to be irrelevant vs. hidden because unknown

If it's a more advanced format, with predefined conditions/values defined in the spec, then tool makers will need to continually add support to the specified conditions

Initially this can be a major effort because the web and other targets are mature platforms with many conditionals.

But after the initial implementation this is something that moves exceedingly slow.
It is very rare for a new dimension to display devices to be specified.

With this in mind it makes more sense to have a format that is more powerful in the long term.

@Sidnioulz
Copy link

Ideally tools only hide or ignore parts of a tokens file because they recognize the content and know that it is irrelevant for their context.

hidden because known to be irrelevant vs. hidden because unknown

The problem with that in a library, API or standard maintenance context is that it means many consumers must be aware of changes made by one upstream.

If a tool doesn't support a new feature, and finds unknown attributes on a token, it would be better if that tool didn't suddenly implode. If the tool's user community finds it irrelevant for the tool maker to code that the new unknown property is indeed irrelevant, that's time saved. We know how hard it is for FOSS devs to find time to maintain their code, so removing the need for tool makers to target the full tokens spec feels like a healthy thing to me.

Applied to this situation, I think it's ok if there's no fixed standard for what conditional / contextual token values can exist. Token writers will come up with ideas. Tool makers too. Of course, there could be a preset in the standard that grows as token usage matures, but I wouldn't make it a necessary thing to support for tool writers.

Newcomers who want to produce a new tool will also be better off if they can focus on supporting the core of a token and set aside advanced features. Having fewer mandatory features in the spec makes the barrier to entry for new players lower.

@romainmenke
Copy link
Contributor Author

romainmenke commented Jan 19, 2023

We know how hard it is for FOSS devs to find time to maintain their code, so removing the need for tool makers to target the full tokens spec feels like a healthy thing to me.

I strongly disagree with this statement. I am a FOSS dev and I've wasted so much time on software that didn't follow a specification. Either because there wasn't a specification, or because the original author of the software had opinions that differed from the specification.

The healthy thing for maintainers is often money and that a community can grow around a tool. For this to happen a tool needs to be reliable and useful in a wider ecosystem. Following a shared specification also ensures your tool has interoperability with other tools. That is the entire point of this specification.

If you need 5 tools that each interact with your tokens but each of these 5 only implements 80% of the specification, how much of the design token specification will be implemented and useful to you? You will only be able to use 0%-80% of the design tokens feature set.


I think you misread my comment :)

Ideally tools only hide or ignore parts of a tokens file because they recognize the content and know that it is irrelevant for their context.

I did not mean that tools can not have forwards and backwards compatibility by having good error recovery.

They must have these aspects.
A tool can not implode when it encounters something unexpected.


I believe there's room in the spec for tokens to have multiple value properties, though without embedding the logic as to what they mean.

But your proposal is to obfuscate important and (initially) structured data by applying a string encoding. $value_lang-el

Tools still need to have general understanding that $value_ + suffix is a conditional.
But by specifying the suffix part as blackbox it is no longer possible to have interop between tools.

Tools can not assign meaning to lang in your proposal.


Token writers will come up with ideas. Tool makers too.

This is fine and that is why $extension exists.
It allows anyone to experiment.

But following the standards process is important.
If someone has a good idea and would like to see wider support across multiple tools then they should open an issue in the DTCG and request the specification to be amended. This benefits everyone.

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

No branches or pull requests

7 participants