fast icon indicating copy to clipboard operation
fast copied to clipboard

rfc: New Approach to Design Tokens

Open EisenbergEffect opened this issue 2 years ago • 18 comments

💬 RFC

This RFC proposes an alternative approach to Design Tokens. This would be a breaking change from 1.0 but I think it improves the ergonomics of tokens, simplifies the mechanics, and could yield better performance. I also think this would be easy to integrate with the W3C design tokens spec, server-side code, and build systems.

🔦 Context

The main things I'd like to accomplish are:

  • Improve general ergonomics.
  • Reduce the number of imports needed to use tokens in styles.
  • Improve performance.
  • Simplify the runtime mechanics of tokens.
  • Introduce the concept of a "system of tokens" and remove "free floating" tokens.
  • Make the concept of "derived" token systems more explicit.

The central ideas are as follows:

  • You would no longer create design token objects. Rather, you would create an explicit system of tokens that work together.
  • The system of tokens is then explicitly applied to a DOM node, which would then apply CSS custom property values to the selected node and keep those properties up to date as the system values change.
  • Updating the system's values would be as easy as setting a JavaScript property.
  • You would explicitly create a derived system by calling derive.
  • You would apply the values at different places in the DOM by doing one of the following:
    • Calling the apply(element) API. This would allow improved performance in the case where you know there will not be any intermediary overrides between your overrides and the parent.
    • Calling apply(element, { mode: "dynamic" }) API. This will then search the DOM tree for the proper parent system and register with it. It will also keep the inheritance relationship up to date if at any time in the future a new system is derived at a node between the current node and its parent.

💻 Examples

What follows are some usage examples, along with a description of what the code would be doing behind the scenes.

Root Systems

// Create a token system
export const system = tokenSystem({
  background: "black",
  get foreground() {
    return this.background === "black" ? "white" : "black";
  }  
});

// Apply a token system to a DOM node.
system.apply(document.body); // add --background and --foreground custom properties to the body

// Use a token in css.
const styles = css`
  .container {
    background: ${system.tokens.background}; /* Inserts var(--background) */
  }
`;

// Bind a token value in the UI
const template = html`
  The background token value is "${system.values.background}". <!-- Displays "black" -->
  <input type="text" :value=${twoWay(() => system.values.background)}> <!-- Enables updating the background in realtime. -->
`;

// Set the background in code.
system.values.background = "yellow"; // updates --background and --foreground custom properties on the body
console.log(system.values.foreground); // prints "black" to the console

Technical Notes

The tokenSystem function returns a strongly typed TokenSystem<T> where:

  • Its values property is the type T instance that was returned from the callback passed to tokenSystem.
  • Its tokens property is a mapped type, where each property is mapped to a string that contains the CSS var styntax.
  • Its vars property is a mapped type, where each property is mapped to a string that contains the css var name.

The tokenSystem factory:

  • Converts all the object's read/write fields to observables.
  • Creates a style sheet and caches it.
  • Creates an Observable.binding that walks the properties to set the CSS text on the style sheet.
  • Registers for changes to the observable and updates the style sheet on any changes.

When the system.apply API is called:

  • The cached style sheet is added to the adoptedStyles of the body or FASTElement.
  • The system registers for token system child events on the element.

Depending on runtime capabilities...

  • Uses an alternative code path based on a style element if constructible style sheets arent's supported.
  • If the element is not the body or a fast element, we could also use the style api to write the properties inline.

Static Derived Systems

// Create a statically derived token system
export const derivedSystem = system.derive(x => {
  x.background = "red";

  Object.defineProperty(x, "foreground", {
    get() {
      switch(this.background) {
        case "red":
        case "black":
          return "white";
        default:
          return "black";
      }
    }
  });
});

// Apply a derived token system to a DOM node.
derivedSystem.apply(myElement); // add --background and --foreground custom properties to myElement

// Use a token in css (not dependent on the derived system).
const styles = css`
  .container {
    background: ${system.tokens.background}; /* Inserts var(--background). */
  }
`;

// Bind a derived token value in the UI
const template = html`
  The background token value is "${derivedSystem.values.background}". <!-- Displays "red" -->
  <input type="text" :value=${twoWay(() => derivedSystem.values.background)}> <!-- Enables updating the background in realtime. -->
`;

// Set the background in code.
derivedSystem.values.background = "yellow"; // updates --background and --foreground custom properties on the body
derivedSystem.log(system.values.foreground); // prints "black" to the console

Technical Notes

The system.derive API returns a strongly typed TokenSystem<T> where:

  • Its values property is the same type as the parent but not the same instance.
  • Its tokens property is the same instance as the parent.
  • Its vars property is the same instance as the parent.

The system.derive API passes a prototype-less object to the callback. The callback cannot read from the object at this time, but can set its overrides.

When the callback returns, the system.derive API will:

  • Convert all the object's read/write fields to observables.
  • Set the _parent private observable field on the object so that it points to the parent token system.
  • Generates tracked getters for any fields that were not specified as overrides. Each getter will fallback to the values on the _parent if no local value is specified.
  • Generate observable setters for any fields that were not specified as overrides. Each setter will be able to shadow the _parent value.
  • Creates a style sheet and caches it.
  • Creates an Observable.binding that walks the properties to set the CSS text on the style sheet.
  • Registers for changes to the observable and updates the style sheet on any changes.

When the derivedSystem.apply API is called:

  • The cached style sheet is added to the adoptedStyles of the target element.
  • The system registers for token system child events.

I believe this will handle the majority of use cases. It allows us to avoid DOM walking when the developer knows the hierarchy of systems in the experience. It reduces the complexity to simple observables that are used to build style sheets.

Dynamically Applied Systems

// Create a derived system that is dynamically applied
system.derive(x => x.background = "yellow")
      .apply(myElement, { mode: "dynamic" });

// This could be shortcut like this.
system.dynamic(x => x.background = "yellow", myElement);

Everything from statically derived systems applies here, but some additional functionality is layered on top.

  • The apply API when used in "dynamic" mode adds a custom host behavior to the target element.
  • The custom host behavior performs the following.
    • Its connected callback fires an event to locate the parent system.
      • Once the parent is found:
        • It can update the _parent property. Prior to this, _parent will point to the static parent system just like a static application.
        • It then registers itself as a dynamic child of the parent.
    • It's disconnected callback:
      • Unregisters the child with the parent.
      • Sets _parent back to the static parent system.

When the child is registered with the parent as a dynamic child...

  • A depth number is included as part of the registration. This can be computed from the length of the composedPath() of the event that was handled by the parent.
  • If the depth number is less than any of the depth numbers of children already registered with the parent, then those children could be children of the new child.
    • The current parent then calls ping() on the child systems, which causes each one to raise a new event.
      • If the event is handled by the same system, nothing happens.
      • If the event is handled by a different system, unregister from the old and register with the new.

This last set of steps enables systems to be dynamically added between other systems in the DOM hierarchy.

Open Questions

  • Do we like the term derive or should we use something like override? I have started to lean towards override so that we could add a new API called extend that would create a new system based on the original but where you could add new properties.

Additional Advantages

  • The core system would have no dependencies on DOM.
  • The system could be used server-side or as part of a build process to generate static style sheets based on the algorithms and values.
  • It could also generate variations using the derive mechanism. So, static light mode and dark mode sheets would be possible as well as any form or server-driven personalization.
  • It would be pretty easy to write a bundler plugin that took the W3C format as input and then code generated an output based on this API, which could then be used at built time, server-side, or at runtime.

Additional Considerations

  • I think we should consider putting the new token tech into a @microsoft/fast-tokens package.
  • I think we should consider deprecating adaptive-ui.
  • Instead we should have the new fast-tokens package only have the core token system features and all the algo pieces.
    • This would now be dependent on fast-element and fast-colors only (maybe utils too???).
  • Component libraries, like adaptive-web-components, can then build their own systems based on their own methodologies.
    • Let adaptive-web-components own adaptive-ui built on top of fast-tokens. They will provide a specific system.
    • Any algos from adaptive-web-components's version of adaptive-ui should be PR'd back to the core fast-tokens package.
      • This allows sharing of the token infrastructure and algos while 3rd party libraries would hold the opinions on token methodology by the particular way they assmeble the fast-token building blocks.
  • Tools can then more easily generate code based on the W3C format (or other data) in a more light weight fashion.

Next Steps

If we are interested in this, I'd be happy to build it out. However, I don't have the bandwidth to do the perf testing against the current implementation. I would need to rely on Microsoft's core team to handle that. But if we want to explore this, I can handle the rest.

I think the folks we need initial agreement from are:

  • @EisenbergEffect - Initial implementation
  • @chrisdholt - Implications for Fluent UI and Microsoft partners
  • @nicholasrice - Performance testing.
  • @bheston - Commitment from adaptive-web-components

If Microsoft isn't interested in changing the implementation, I'm still interested in exploring this. So, I would be interested to hear if adaptive-web-components would be interested independent of that.

EisenbergEffect avatar Mar 11 '23 20:03 EisenbergEffect

Some initial thoughts after reading.

I really like where this is heading. I can't speak for Brian on the adaptive-ui stuff, but this already looks significantly easier to apply tokens to code from something like json or an exported design system from tooling like figma.

On the derive vs override + extend, to me derive seems more like a combination of override + extend. If the explicit separation of overriding tokens and extending a system is needed for something specifically, I think the override + extend APIs would be best. If that separation isn't needed than derive would work since the term works for both cases.

I pretty much agree with all of the additional considerations. If Microsoft isn't interested in changing the current implementation then I think this tech could easily find a home in adaptive-web. One of the things we are trying to accomplish is an easy to extend and customize system and components, and this new token tech is already a lot easier to grok than the current system. Additionally this would fit very well with our goals to enable static configuration of a design system.

I'm curious to hear Brian's thoughts on the implications this has for adaptive-ui, but I think this helps us achieve quite a few of the goals we have for that project.

KingOfTac avatar Mar 12 '23 00:03 KingOfTac

I think I might be able to make override/derive/extend all be the same thing. I probably need to play with that a bit if/when I get into the actual implementation work.

EisenbergEffect avatar Mar 12 '23 00:03 EisenbergEffect

Regarding packages, we could also keep adaptive-ui and move the token API there along with all the algos but just remove the specific tokens, leaving it up to libraries like adaptive-web-components to supply a particular token system.

EisenbergEffect avatar Mar 12 '23 01:03 EisenbergEffect

Creates a style sheet and caches it.

~~to clarify, does it mean token system could apply the same stylesheet of tokens to multiple elements for the price of 1 (adoptedStyles)? to shadow trees as well?~~

scoping tokens directly to a set of "hand picked" custom elements would help avoiding conflicts when registered under the same design system / custom elements registry. a scenario where different versions of the same library (e.g. components library integrated in a micro-frontend architecture) could require different tokens stylesheet in the same document. that's great!

considering the presented example, the foreground getter can be already be governed soon (with an extended heuristics) by the CSS color-contrast. in addition to that, background toggling between black and white colors actually implies a theme change, which most likely changes the WHOLE color palette references (to darker / lighter shades). Maybe it's worth analyzing a real world scenario first?

Its tokens property is a mapped type, where each property is mapped to a string that contains the CSS var styntax.

if this wraps the vars to provides a convenient way for devs to set the CSS custom property, it sounds like it adds ergonomics on the expanse of adding convolution.

personally I'd rather avoid adding this redundancy

.container {
  background: var(${system.tokens.background});
}

could this feature be leveraged even if using simple CSS? SCSS?

as token system need to programmatically apply a stylesheet, rendering of dependent custom elements will not be blocked to await tokens to be parsed. this will result in a FOUC

yinonov avatar Mar 12 '23 13:03 yinonov

In response to your questions:

  • Yes, you would be able to apply the same system to multiple elements.
  • The example of foreground/background is intentionally kept very simple. I am basing this on what I think would support the full adaptive and fluent scenarios we have had in the past. So, internal to a getter you could use the recipes just like we do in a computed token today.
  • I'd like to keep tokens emitting the full var syntax since that's what we have today with design tokens and I think removing that would cause a lot more typing for folks.
  • For the FUOC scenario, if that is a concern, you could generate the stylesheet on the server either ahead of time or during the request, and send that down statically, inline in the HTML payload. So, the vars would be there from the beginning. Then the JS implementation can pick up from there and enable runtime changes. So far, I haven't seen this issue though.

EisenbergEffect avatar Mar 12 '23 17:03 EisenbergEffect

FOUC - I'm wondering who's responsibility that would be to apply the suggested practices, library maintainers or its consumers? if it's consumers' it might make that over complicated task of simply loading CSS. as the task of loading css is not trivial, it may vary between projects. btw, for us, it backfired when we tried to enforce a practice of loading CSS by side effecting that stylesheet mount within the library imports.

I know this is a technical limitation but it bugs me that by programming it, we're loosing the CSS ability to select elements ahead of time.

anyway, waiting to see how this turns out...

yinonov avatar Mar 12 '23 21:03 yinonov

First, I think this all generally sounds like a good set of changes. In practice, I've seen a few projects where the existing DesignToken is assembled into a structure resembling DesignSystem you're proposing and to me, it makes sense as a good organizational tool for design-system authors.

A few thoughts, in no particular order:

  1. I would love to see if the dynamic portion of the system could be installable, or otherwise not bundled when not used. In general, I think most use-cases aren't going to use contextual value calculation and it would be great if those authors didn't have to pay the bundle tax.
  2. I personally like "override" over "derive". The function’s purpose is to "override" the value defined in the original system. While I think "derive" also works, "derive" to me implies a more ambiguous product of the operation.
  3. In my testing, using events to resolve the parent context was less performant than manual DOM walking. How much impact that has on app performance would obviously depend on the frequency of use, but it would probably be worth implementing that resolution portion with a strategy so that algo can be performance-tuned. Doing so also allows adjusting the implementations for non-DOM scenarios (like design tools) that work on different tree-structures.
  4. Tree-shaking ability could suffer with this model because un-used tokens will still be in the system. That could potentially be addressed by creating sets of smaller systems individual components import and apply to themselves. This also probably isn't a huge deal for most scenarios, but good to consider.
  5. As long as the implementation uses fast-element's observable, it will require certain DOM globals to exist during initial script evaluation (I know document, I think a few others but I don't recall specifics atm). So while the implementation may not use DOM dependencies, a few will still need to be available to run the script. DesignToken suffers from this today. Maybe we can make observable a stand-alone package, or an export path that doesn't access DOM globals?
  6. Authors often like to group tokens into categories such as color, sizes, shadows, etc. Is something this architecture could support? I expect it would, but maybe I'm missing why that would be a challenge?
  7. Can the css custom property emitted have collision avoidance? Are these generated and if so, can we have friendly-names somehow to aid debugging? DesignToken currently suffers from this, and it would be nice to avoid that hazard this iteration.

I'm definitely interested in hearing @bheston and @chrisdholt's thoughts, but to me, this looks like a solid evolution of the infrastructure and is IMO going to be more intuitive for the majority of scenarios.

nicholasrice avatar Mar 15 '23 00:03 nicholasrice

  1. Agree on the hypothesis. I think I would first build this with it all bundled together to see how it works out and then see if I can refactor to extract it. I don't know how much code that is going to be or what the level of effort is there. But I think it should be a "nice to have" goal.
  2. I think "override" describes things better as well.
  3. We could definitely factor out the ping strategy to use different approaches. I think we may be able to do some additional optimization there, such as caching on parent nodes with invalidation so we could do a sort of, short walk of the tree, and then fallback to event if a cached node isn't found. But that can all be in a strategy. I do want this to work in Figma if possible, so I think that's important.
  4. I'm less worried about tree shaking because I think you would build the systems you want with exactly the tokens you want. A system like adaptive web components could certainly group things in certain ways and then provide an API for composing the system. I need to think about the best way to support that but it seems doable. (Also, a tool could create the system for you with exactly what you want based on the W3C token json.)
  5. I'll look into the observable implementation and see what the DOM dependencies are there and how we can make that piece in particular smoother outside of the browser.
  6. I think we can enable token grouping. I think the code that builds the system object could take account of this and connect some things up. I need to think about how the TS types would work. Might need a recursive, conditional mapped type or something.
  7. I imagined the generated css custom properties would be derived from the property name. Maybe there can be a prefix options when you create the systems so it prepends that as well. Would that be enough?

Any objection to me doing some prototyping on this? I might have some time this weekend and could see how far I got and what issues came up.

EisenbergEffect avatar Mar 15 '23 16:03 EisenbergEffect

Performance is top of mind for me these days when it comes to how much we should spend on styling, so my greatest concerns here echo much of what @nicholasrice mentioned above. Most of the concerns above are shared and many of the "asks" would be part of a wish list if I had one (such as collision avoidance). Ultimately though, I think we need to be able to do complex things without requiring the same code or complexity in order to do simple things.

On semantics - I like the concept of override and extend but I'm not sure I could understand them as the same thing. I could see a use case for derive if there were some method which took a dependency on a token and then leveraged that to calculate another, but I'm not sure it would be core - seems more like an implementation detail to me of how one implements the tokens.

I think the biggest considerations to work through are in the additional considerations scenario. I think the token architecture should exist in FAST as it's an evolution of the existing architecture. I think the key consideration is whether the algo bits go with it or if that's a separate consideration. While I think they likely will pair nicely with this, it seems odd to pair them together unless we're implementing a system - meaning, I could see a separate @microsoft/fast-design-token package, but I don't know that it makes sense to include the algo's there and then have a token implementation somewhere else. I could see a world where we get a bit more abstract with the core parts of the algorithms and include those for creating tokens which are algorithmically driven, but the current implementation is that all the things are tokens and so I'm not sure I see that breakdown making as much sense as having the algo's stay with some kind of adaptive UI package. I'm open here, but I wouldn't want to include the current implementation of those as I see them as too prescriptive to a specific system.

As a final note, you probably noted I went with @microsoft/fast-design-token - a key consideration here is that fast-tokens implies that we're exporting a set of tokens which I don't think we would be or would want to. The above name would be clearer to me and would likely cause less confusion when considered alongside packages with similar names which do export actual token implementations and values, such as @material/tokens and @fluentui/tokens.

chrisdholt avatar Mar 15 '23 17:03 chrisdholt

Any objection to me doing some prototyping on this? I might have some time this weekend and could see how far I got and what issues came up.

None at all, I think it's probably necessary to find any dragons. While not final it could help with a baseline of performance or identify more optimization necessary, etc.

chrisdholt avatar Mar 15 '23 17:03 chrisdholt

Any objection to me doing some prototyping on this? I might have some time this weekend and could see how far I got and what issues came up.

Nope. I think I've still got the old perf branches laying around, so I'll run some tests when you get something working.

nicholasrice avatar Mar 15 '23 17:03 nicholasrice

While I find this updated structure interesting, I'm not sure it resolves some of the fundamental issues we've had around working with tokens. As I've been actively implementing about three years' worth of thought on evolving the Adaptive UI systems, I am concerned there might be conflicting patterns here. I'd like to pause and understand your immediate motivation more and coordinate our work if this is an area where you'd like to see progress.

EDIT: Perhaps a lot of what I've been working toward can sit on this new model, but I'm not sure based on the limited examples.

Wall of text warning, probably best to have a discussion or two:

My initial perception of system is that it could serve as a complete base design system definition, like that of Fluent UI. I agree with the need for some sort of structure grouping a set of tokens, which I have also started working on in Adaptive UI. Perhaps it's just the example, but is your thought on derivedSystem that it represents something like an "Office" derivation of the base "Fluent UI"? Or is it what you would do for different sections of an app that have a different background color or overridden token value? Or both?

The current fixed adaptive-ui tokens are not desirable, but I've been working toward a better system there. One motivation is to keep them around for easy migration. That's what my latest PRs in AWC have been for. The second is to create a robust model for a default configuration that maintains accessibility requirements while meeting visual desires. I have this in process but have not pushed the branch or PR yet. The final step of this plan is to convert the fixed tokens to json representations (following the W3c model as much as possible) and compiling that into code during build.

The new model I've been working toward I'm still referring to as "style modules". I think those follow the same goals as your example of pairing background and foreground into a system, but I think the modules are more flexible. For instance, building from my previous paragraph, you would join any tokens into a module that is then applied to any component part. A common set would be background, foreground, and stroke color. Make a set for a filled accent component and another for an outlined neutral component and apply them as desired.

I know you had comments on my AWC style modules PR. I haven't pushed any changes there since, but I have some updates based on what I've said above. We definitely need the ability to generate the tokens at build time and not rely on processing a json representation, so whatever part of that you were considering for this rfc I agree would be beneficial.

I've logically separated the model into three parts:

  1. Tokens (or systems) are created, either through a build step that produces output similar to what we manually maintain today, or by parsing json at runtime for fully dynamic scenarios. Or a combination to speed loading but have full control after load.
  2. The modules sit in the middle, they are the definitions for all visual styling that is applied to component parts. A module for corner radius, colors, type, whatever is needed to organize and convey your design. It's essentially a graph of declared visual choices.
  3. I've been referring to this step as the "style renderers". They convert the definitions of a module into styles. Here's where the binding update might come into play, but for a declarative system to work I believe we must move away from a model where we write bindings into css. That is, we cannot have a component expect that foreground exists.

Style renderers could produce full style sheets like we get through css right now, or there could even be an implementation that uses the new CSS typed object model and avoids parsing css. Finally, these renderers would be able to apply to any components, not just FASTElements.

The performance of the existing token model has been really good, and I'm not sure how a model that continues to use observable binding would be different. In most app scenarios the current derived tokens should not be evaluated more than once anyway, though I have seen some issues around default values that I've been hoping to resolve with the model I described here. Of course, if the tokens were declared as all fixed values, none of that would happen at load.

bheston avatar Mar 17 '23 22:03 bheston

@bheston I'd prefer not to bring all those other topics into this thread. We need to keep this focused on the model for creating tokens independent of adaptive UI.

For tokens, I'm just proposing a simpler runtime implementation that uses our existing reactivity infrastructure better. I think that will improve perf and reduce code/complexity. I also think it will be more ergonomic to work with.

Don't get hung up on foreground and background. I just used that to show simple and computed properties. The system could contain hundreds of interdependent properties if needed, some of which could be simple values and some of which could be computed values based on complex algorithms. It's just the view-model pattern with some special implementation changes in order to map it better to design token scenarios. That pattern can scale to any level of complexity.

A "derived system" could be the Office scenario, yes. But it could also be any runtime system based on another system where you need to change the rules or values. There's no difference in the model or implementation between those cases, which is something I see as a software design advantage.

EisenbergEffect avatar Mar 19 '23 23:03 EisenbergEffect

I mention the Adaptive UI points not to complicate this issue, but because the work is very much related. I understand we can improve the performance and usability of the underlying tokens infrastructure, but it’s not the biggest blocker I see right now as ultimately the goal of what I’ve been working on is to remove the need to manually write this code at all.

It’s important to note that “Adaptive UI” to me refers to everything about styling components, from design definition to element application. It’s not only for “adaptive” algorithms and could be used with a completely static definition (though I believe there are strong reasons this will continue to not meet UX needs).

Strengths

I'm all for improving performance and runtime mechanics. I've had trouble debugging the token infrastructure, so it would be nice if this were both simpler and if there was a way to understand what's going on. In practice on simple experiences there shouldn't be that many "changes" happening in the tokens, but somehow that's easy to make happen.

Having a container for tokens (the system) is imperative. I had to build something for this in the Figma designer and was migrating that up to adaptive-ui.

It's also great that the css property names will be created from the token object property name. That's been a lot of work to keep in sync, and ultimately unnecessary on the code side, aside from marking a token as not rendered for css.

For consideration

Regarding “override”, “extend”, and “derived”; “derived” is already used within the token system to mean a token value is calculated based on other tokens. I interpret that word to imply some sort of computation rather than simply changing values.

I don't understand the difference of dynamic mode. Is this so a component can be removed from DOM and added under a different parent, which might have a different system applied?

The current design token infrastructure separates generic tokens from css tokens. Only css tokens are rendered in a style sheet. Right now we control that by not setting the variable name for non-css tokens. Perhaps something as simple as a private member (preceded with an underscore) could signify that. This is particularly useful with derived tokens.

Grouping is useful, as Nick mentions. I’m not sure how it should affect the generated name though. For instance, there might be a number of things grouped under “color” but having all the tokens named “color-whatever” isn’t always desirable. Really, additional metadata accompanying the tokens, for instance, “description”, following the W3C model. I understand this is not core need for the implementation, but it would be nice to not have to bolt this on somehow.

Further, to have a reliable way to determine what type of value a token represents. There are some potential complications here depending on how much reuse of an existing system we can allow. For instance, a token value might be a plain color or a gradient. Both are valid for use as a fill but are different value types. This is a limitation today because a token might be declared as a Swatch and there’s no way to provide that gradient value aside from building a GradientSwatch which feels forced. There have been other cases with simple types as well.

One challenge with the setup today is that it’s very verbose when dealing with “sets”. A “set” is common for something like rest, hover, active, and focus states. Because of the need for css tokens in order to render values, we need to define those individual tokens everywhere. Perhaps this is a usage of the grouping model, where a set is just a group of tokens. Contrary to my example above, this would be a case to impact the generated name, like “my-color-set” plus “-rest”, etc. Here’s an example where I had to stitch these tokens back together. This is further complicated when using adaptive algorithms due to the desire for more parameter tokens as well as a “recipe” token to cache the calculated value.

I ended up teasing this apart, but the static derived example initially implied to me that in order to apply the values from the derivedSystem I also need to bind to the derivedSystem. What made me realize this was incorrect was the note about using the same instance in tokens but that led me to this next thought.

I find some challenges with this model when it comes to extending a system but I think they can be remedied by some changes to the example. If this makes sense, I think this model will work. From the perspective of implementing a single design system, the primary design token export would be just system, which all component styling would refer to. If that system were based on any other systems, those would be the baseSystem and system would be an override or extend of that. This way if the system added any tokens, the component styling could make use of those without having to mix styling from multiple systems, which while possible seems confusing.

Regarding the packaging of design tokens and styling algorithms, I’d like to keep these separate. I would like to see the token work go into a separate package than fast-foundation as it makes the intent of both packages clearer. I see the token work as an infrastructure, and while I see great strengths in using it through adaptive-ui based on my description of that intent above, I don’t see the algorithms as fundamental to the token system.

Replies

@yinonov, what you say about the redundancy of background: var(${system.tokens.background}); is exactly what adaptive-ui is solving right now. The larger theme here is that manually managing css is tedious and problematic. Proper tooling will be much better at managing styling decisions. Keep an eye on what I'm still referring to as "modular styling". The initial work is only the first step, but you can see that also cleans up the imports issue Rob mentioned.

bheston avatar Mar 23 '23 18:03 bheston

Having a container for tokens (the system) is imperative.

@bheston can you clarify this for me? Does this mean that having a DOM Element as a container for the system (similar to a prior concept of DesignSystemProvider)? One of the biggest strengths IMO is the ability to use a component without unnecessary DOM.

chrisdholt avatar Mar 29 '23 07:03 chrisdholt

Having a container for tokens (the system) is imperative.

@bheston can you clarify this for me? Does this mean that having a DOM Element as a container for the system (similar to a prior concept of DesignSystemProvider)? One of the biggest strengths IMO is the ability to use a component without unnecessary DOM.

@chrisdholt, I wasn't referring to DOM, I meant system in Rob's example, the output of tokenSystem as the container. At one point I've had something as simple as const tokens: Map<string, CSSDesignToken<any>> = new Map();.

bheston avatar Mar 29 '23 14:03 bheston

I've been working a bit on implementing this here and there. I'm about 80% done. A few notes:

  • I went with override() to enable the creation of a new system from an existing system, that overrides some of the existing system's tokens.
  • I created an API called overrideAt() that creates a new system from a system that is located through the DOM hierarchy.
  • I support nested objects in the tokens structure, so that you can arbitrarily group tokens together. The actual css variable names are derived from the nested property path.
  • I enable tokens that don't map to css variables by simply prefixing the name with an underscore.
  • I enable a prefix for the css variable names, defaulted to "fast".
  • I took a few different paths in the implementation that should improve performance over what I initially proposed, using our reactive APIs and prototype chains creatively.

The main thing I need to finish is the core logic of the overrideAt code flow. Then I need to do a bit of refactoring to make some things more modular and overridable, such as enabling different strategies for locating systems in the node hierarchy.

This isn't my main focus now. I'm primarily working on the new template compiler, which I'm super excited about. But I'm working on this a bit here and there. Probably a few more weeks, especially to get some tests in place.

EisenbergEffect avatar Apr 12 '23 03:04 EisenbergEffect

Following this one with interest. Any updates?

derekdon avatar Mar 05 '24 14:03 derekdon

Unfortunately due to our repositioning of the FAST project as of #6955, we will no longer be focusing on DesignTokens, similar work may exist in the Fluent UI project so for anyone interested that may be a good place to look for design system related work. The web components package is built on @microsoft/fast-element.

janechu avatar May 29 '24 20:05 janechu