Closed EisenbergEffect closed 5 months ago
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.
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.
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.
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
In response to your questions:
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.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...
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:
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?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.
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.
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
.
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.
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.
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:
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 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.
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).
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.
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.
@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.
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.
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();
.
I've been working a bit on implementing this here and there. I'm about 80% done. A few notes:
override()
to enable the creation of a new system from an existing system, that overrides some of the existing system's tokens.overrideAt()
that creates a new system from a system that is located through the DOM hierarchy.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.
Following this one with interest. Any updates?
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
.
đź’¬ 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:
The central ideas are as follows:
derive
.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.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
Technical Notes
The
tokenSystem
function returns a strongly typedTokenSystem<T>
where:values
property is the typeT
instance that was returned from the callback passed totokenSystem
.tokens
property is a mapped type, where each property is mapped to a string that contains the CSS var styntax.vars
property is a mapped type, where each property is mapped to a string that contains the css var name.The
tokenSystem
factory:Observable.binding
that walks the properties to set the CSS text on the style sheet.When the
system.apply
API is called:Depending on runtime capabilities...
Static Derived Systems
Technical Notes
The
system.derive
API returns a strongly typedTokenSystem<T>
where:values
property is the same type as the parent but not the same instance.tokens
property is the same instance as the parent.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:_parent
private observable field on the object so that it points to the parent token system._parent
if no local value is specified._parent
value.Observable.binding
that walks the properties to set the CSS text on the style sheet.When the
derivedSystem.apply
API is called: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
Everything from statically derived systems applies here, but some additional functionality is layered on top.
apply
API when used in "dynamic" mode adds a custom host behavior to the target element._parent
property. Prior to this,_parent
will point to the static parent system just like a static application._parent
back to the static parent system.When the child is registered with the parent as a dynamic child...
composedPath()
of the event that was handled by the parent.ping()
on the child systems, which causes each one to raise a new event.This last set of steps enables systems to be dynamically added between other systems in the DOM hierarchy.
Open Questions
derive
or should we use something likeoverride
? I have started to lean towardsoverride
so that we could add a new API calledextend
that would create a new system based on the original but where you could add new properties.Additional Advantages
derive
mechanism. So, static light mode and dark mode sheets would be possible as well as any form or server-driven personalization.Additional Considerations
@microsoft/fast-tokens
package.adaptive-ui
.fast-tokens
package only have the core token system features and all the algo pieces.fast-element
andfast-colors
only (maybeutils
too???).adaptive-web-components
, can then build their own systems based on their own methodologies.adaptive-web-components
ownadaptive-ui
built on top offast-tokens
. They will provide a specific system.adaptive-web-components
's version ofadaptive-ui
should be PR'd back to the corefast-tokens
package.fast-token
building blocks.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:
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.