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

Prototype composite type in translation tools to ensure it is useful #126

Open
romainmenke opened this issue Apr 20, 2022 · 3 comments
Open

Comments

@romainmenke
Copy link
Contributor

romainmenke commented Apr 20, 2022

When comparing composite types with regular types the added value of composite types wasn't immediately clear to me.

If I read the draft correctly they are strictly defined groups.
The reason to strictly define which sub values are allowed is to make it possible for other tools to interpret the group as a whole.

So in a way they are a tool to make it easier to consume a collection of values.

{
  "type styles": {
    "heading-level-1": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Roboto",
        "fontSize": "42px",
        "fontWeight": "700",
        "letterSpacing": "0.1px",
        "lineHeight": "1.2"
      }
    }
  }
}
.foo {
  <something>
}

becomes :

.foo {
  font-family: "Roboto";
  font-size: 42px;
  ...
}

Only needing to type <something> is much quicker and shorter than needing to reference each value separately.

In theory this is awesome!


My concern with this is two fold :

  1. this is not implementable in certain contexts
  2. this must be implemented (contrary to specification wording)

Not always implementable

Grouping properties and values makes the assumption that multiple consumers can process this.
Even in CSS this is non-trivial and requires something like mixins.

@mixin type-styles.heading-level-1 {
  font-family: "Roboto";
  font-size: 42px;
}

.foo {
  @include type-styles.heading-level-1;
}

In a CSS stack setup without mixins a user needs to reference each value individually :

.foo {
  font-family: <something>('type styles.heading-level-1.$value.fontFamily');
  font-size: <something>('type styles.heading-level-1.$value.fontSize');
  ...
}

This is more elaborate than if design tokens didn't have composite tokens :

.foo {
  font-family: <something>('type styles.heading-level-1.fontFamily');
  font-size: <something>('type styles.heading-level-1.fontSize');
  ...
}

And brings me to my second point :

this must be implemented (contrary to specification wording)

https://design-tokens.github.io/community-group/format/#groups-versus-composite-tokens

Tools MAY provide specialised functionality for composite tokens. For example, a design tool may let the user pick from a list of all available shadow tokens when applying a drop shadow effect to an element.

This is not true.
There is no way for a tool to fallback to "unspecialised functionality".

If my tool doesn't handle the typography type there is no way to correctly consume any value contained in this token.

{
  "type styles": {
    "heading-level-1": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Roboto",
        "fontSize": "42px",
        "fontWeight": "700",
        "letterSpacing": "0.1px",
        "lineHeight": "1.2"
      }
    }
  }
}

In CSS for example it becomes impossible to correctly reference fontFamily using a tool that doesn't support typography.

  • unclear if this is a string or ident token Roboto vs "Roboto"
  • unclear what the id/path of the token is (type styles.heading-level-1.$value.fontFamily ?)

I think at least this specialised behaviour needs to become a requirement.
Which also brings me back to my first point, that it can not always be implemented...


One alternative is to see composite types as typed groups without altering how values/groups work or how they are written :

{
  "type styles": {
    "heading-level-1": {
      "$type": "typography",
      "fontFamily": { "$value": "Roboto", "$type": "string" },
      "fontSize": { "$value": "42px" },
      "fontWeight": { "$value": 700 },
      "letterSpacing": { "$value": "0.1px" },
      "lineHeight": { "$value": 1.2 }
    }
  }
}

Any tool that supports typography in a special way can do its own thing.

Any tool that doesn't will still be able to correctly handle the sub values as these follow regular group/value rules.


To sum up, I think this portion of the specification needs to be prototyped in translation tools to make sure it can be implemented or needs to be changed so that it can truly be optional.

@romainmenke romainmenke changed the title Prototype composite type usage Prototype composite type usage to ensure it is useful Apr 20, 2022
@romainmenke romainmenke changed the title Prototype composite type usage to ensure it is useful Prototype composite type in translation tools to ensure it is useful Apr 20, 2022
@romainmenke
Copy link
Contributor Author

@kaelig @c1rrus This is one of the biggest issues preventing us from prototyping something for PostCSS.

Do you have any thoughts or insights in how translation tools should expose composite tokens?

@c1rrus
Copy link
Member

c1rrus commented Aug 4, 2022

Sorry for the delayed response. You raise some valid concerns and I think our spec does perhaps need to be more explicit about expected fallback behaviours for tools that can't or don't want to process composite tokens as a single unit.

For tools that export to code, I had always imagined that an acceptable fallback would be to break up a composite type into individual variables. Using your earlier example:

{
  "type styles": {
    "heading-level-1": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Roboto",
        "fontSize": "42px",
        "fontWeight": "700",
        "letterSpacing": "0.1px",
        "lineHeight": "1.2"
      }
    }
  }
}

...and imagining this was being exported to, say, CSS custom properties, then I think this would be an acceptable output:

--type-styles-heading-level-1-font-family: Roboto;
--type-styles-heading-level-1-font-size: 42px;
--type-styles-heading-level-1-font-weight: 700;
--type-styles-heading-level-1-letter-spacing: 0.1px;
--type-styles-heading-level-1-line-height: 1.2;

In a way, I guess this is saying that (semantically) a composite token is a typed group (in my mind, they're a bit like interfaces in Typescript - i.e. objects that must have a specific "shape"). As you say, tools that know how to do something special with the group can do that, and other tools are welcome to just use the invidiual sub-values.

So a different CSS export tool (or perhaps just a different configuration?) could produce something like this from the same token:

.type-styles-heading-level-1 {
  font-family: Roboto;
  font-size: 42px;
  font-weight: 700;
  letter-spacing: 0.1px;
  line-height: 1.2;
}

Either of those (and maybe other things to) would be reasonable ways to export that token to CSS.

However, I think your proposed typed group syntax is a little problematic. Since tokens without an explicit $type inherit from their parent group, all the tokens in your example - except for the "font-family" one - would have a type of "typography". Sure, we could change the rules so that that inheritance behaviour doesn't apply for composite types, but I'd worry that would make the format more difficult for people to understand as it would be more rules to remember.

As I've outlined above, I don't think the current composite type approach prevents tools from "exploding" them into individual tokens / vars / whatever if that's needed for their use-case. The spec explcitly specifies what type each sub-value must be, so a tool can "know" that the fontFamily sub-value must have a fontFamily value, the fontSize sub-value must be a dimension value, and so on. Do you think there is additional info tools wuld need in order to "explode" them out as if they were individual tokens?

@romainmenke
Copy link
Contributor Author

However, I think your proposed typed group syntax is a little problematic. Since tokens without an explicit $type inherit from their parent group, all the tokens in your example - except for the "font-family" one - would have a type of "typography". Sure, we could change the rules so that that inheritance behaviour doesn't apply for composite types, but I'd worry that would make the format more difficult for people to understand as it would be more rules to remember.

Inheriting $type in groups would not be possible in this proposal, that is true.
It would be the tradeoff.

This is related to : #149
Is inheriting $type in groups syntactic sugar or is there a specific use case for end users that is only possible with inherited $type properties?


so a tool can "know" that the fontFamily sub-value must have a fontFamily value

That is the core of the issue :)
Any issue can be waved away by saying that the ecosystem can implement things correctly and that the ecosystem can be up to date with the latest specification, but that isn't how things work in reality.

In my opinion it is best to design a data format that is resilient and has desirable forwards and backwards compatible properties.

Adding in escape hatches for end users is critical to the long term success of a format.


You raise some valid concerns and I think our spec does perhaps need to be more explicit about expected fallback behaviours for tools that can't or don't want to process composite tokens as a single unit.

If I understand it correctly the current spec text doesn't cover the intended behaviour.
Especially with this statement :

Tools MAY provide specialised functionality for composite tokens. For example, a design tool may let the user pick from a list of all available shadow tokens when applying a drop shadow effect to an element.

The intention might have been for all tools and implementations to always have support for all specified composite types in a generic way:

  • must be able to parse and serialise composite types
  • may expose component values as regular tokens

These things are important to specify.
If you leave it up to implementers to invent workarounds for gaps in the specs we will end up with so many interop issues :/

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

No branches or pull requests

3 participants