Open danlevy1 opened 1 year ago
I've thought about it in relation to the work I'm doing with Token Operations. From my perspective we could have a setup like the following:
{
"shadow1": {
"_color": {
"$value": "#00000088"
},
"_offset-x": {
"$value": "0.5rem"
},
...
"css": {
"$operations": [...] // concat all parts together into single CSS value
},
"android": {
"$operations": [...] // concat all parts together into single android appropriate value
}
}
}
So a final resolution for CSS would be available at {shadow1.css}
which could be written within the UI directly without further transformation. This would avoid the responsibility of the spec to outline all composite type schemas and put the responsibility on the folks who need specific values to have them available as needed. As an example, perhaps a tokens file is only used for android values. The system ingesting them won't need to know how to combine composite types; everything is already exactly made as needed when it is accepted as a value; right within the file.
I've been digging into these different options for managing composite design tokens, and here are my two cents.
Option 1, which decomposes each token into individual variables, offers granular control. This allows for fine-tuned customization but could lead to management complexities, especially when it comes to ensuring the proper combination of these decomposed tokens.
Option 2 encapsulates all attributes into a single object and provides a cohesive approach. It makes it clear which attributes are meant to be used together but may not offer the same level of granularity that might be required in some scenarios.
However, the 3rd approach suggested by @ddamato provides a balanced solution. It still allows for granular customization via sub-properties while providing a consolidated output tailored for specific platforms like CSS or Android through token operations. This modular approach not only streamlines the application of design tokens but also enables more efficient cross-platform compatibility.
Given the flexibility and scalability inherent in the third approach, it is a compelling solution for managing composite design tokens effectively.
Does the spec need to have an explicit view on this at all?
In my mind composite tokens are a bit like "styles" in Figma (or equivalents in other design tools). They're a structured set of values that, in design tools, are usually selected and applied as a single unit. I think they still make sense as "design tokens" though, as they're still named design decisions and likely to be part of a design system team's vocabulary.
When it comes to translating them to code, there are several ways they might be represented. On some technology platforms, there might be an analogous "group" construct that lets you apply them all at once, in others there may not. And sometimes both ways might be possible.
The 2 options in the OP demonstrate this exactly. I'd argue which one you choose (why not both?) is up to your team.
The same can be true in other programming languages. For example, the shadow token from the OP in CSS could be expressed as a single CSS var:
:root {
--shadow-token: 0.5rem 0.5rem 1.5rem 0rem #00000088;
}
/* application */
.foobar {
box-shadow: var(--shadow-token);
}
But it could just as well be "exploded" into its individual sub-values:
:root {
--shadow-token-offset-x: 0.5rem;
--shadow-token-offset-y: 0.5rem;
--shadow-token-blur: 1.5rem;
--shadow-token-spread: 0rem;
--shadow-token-color: #00000088;
}
/* application */
.foobar {
box-shadow:
var(--shadow-token-offset-x)
var(--shadow-token-offset-y)
var(--shadow-token-blur)
var(--shadow-token-spread)
var(--shadow-token-color);
}
Neither is right or wrong. It just comes down to individual teams' preferences and requirements.
While I'll admit that exploding the parts of the shadow token is something folks are unlikely to do as shadow values in CSS always seem to be applied as a unit, the same isn't necessarily true for something like a border which might be applied all at once as a shorthand value or individually using the long hand properties:
.shorthand {
border: var(--border-token);
}
.longhand {
border-color: var(--border-token-color);
border-width: var(--border-token-width);
border-style: var(--border-token-style);
}
Or indeed a typography token which, at least in CSS, would have to be exploded into individual values because there's no shorthand property that covers them all:
:root {
--typography-token-font-size: 1rem;
--typography-token-font-family: Comic Sans MS;
--typography-token-font-weight: bold;
--typography-token-line-height: 1.4;
--typography-token-line-height: 1.4;
}
.heading {
font-size: var(--typography-token-font-size);
font-family: var(--typography-token-font-family);
/* and so on */
}
For that particular type, you could event export the whole token as a CSS class (or a Sass mixin):
.typography-token {
font-size: 1rem;
font-family: Comic Sans MS;
/* and so on */
}
I suppose a DS team might even export grouped and exploded versions to give downstream product teams the freedom to choose which one(s) they want to use.
My point is, there are potentially many, valid ways to express the DTCG composite types in different programming languages. I don't believe the spec should mandate a particular way. I'd therefore argue it's fine to let that be a choice or a configuration option for tools.
nit:
Or indeed a typography token which, at least in CSS, would have to be exploded into individual values because there's no shorthand property that covers them all:
There is, the font
property is a shorthand for all of these:
My concern for the spec would be that any point some new property that requires composite tokens to appear, would require the definition of that composite to exist and then distributed across tools in varying amounts of support. It is fairly clear the reason why shadow has been given a special composite status is because it is not a shorthand in CSS, making many of the values required and therefore standardized. In the font example, while none of those values are required, I would say it is common for them to be used together. As an example defining a typographic style commonly expects a family and size together at least. In fact, the font
shorthand requires family and size at minimum, and additionally has a special ordering of values afterward.
I'd also be concerned about misunderstanding of use, where the shadow token is applied to the CSS filter: drop-shadow()
function which does not accept the spread value like the box-shadow
version. In the Token Operations would we (the author) define what the final value should be for the platform to use instead of the platform waiting for a specification decision and backlogged implementation. So we may provide the CSS filter version of the value and box-shadow version of the value as needed. In this way, the destinations are only reading primitive values with no black boxes or backlogs.
I have a very strong preference for keeping all of the values of a composite token together. This is faithful to the nature of a design token as being a design decision; there are some places where you have to make multiple decisions to get to the lowest level of fidelity, eg shadows or type. You can't just decide what a shadow's color is, you also have to decide what it's blur radius, x, and y offset are. Likewise you can't just decide what at piece of text's font family is, you also have to decide it's size, etc.
However, like @c1rrus said, I think that the decision as to how to transform the token is up to the transformer, and the spec shouldn't take a stance.
As for @ddamato's follow-up, I put some thoughts in #100 on the issue of platforms having multiple incompatible syntaxes for the same type of token (tl;dr, I think that's another translator concern that the spec shouldn't weigh in on).
Description
The latest editor's draft (as of August 2023) states that composite design token is "a design token whose value is made up of multiple, named child values."
Given the following example in the editor's draft, is there currently a recommendation on how to transform the token?
It seems like there are two options here:
value
property name is appended to the top-level token name to create a new token, and all of those tokens must be appliedvalue
refers to all sub-values that must be appliedIf we take a look at those two possibilities in action, here's what they might look like if we transformed them into JavaScript variables:
Tradeoffs
Option 1 Pros:
SHADOW_TOKEN_COLOR
, but don't applySHADOW_TOKEN_OFFSET_X
).Cons:
SHADOW_TOKEN
). This would require token users to be extra careful in order to ensure that all generated tokens coming from the one composite token are applied.Option 2 Pros:
Cons: