-
Notifications
You must be signed in to change notification settings - Fork 63
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
How to do tokens that share a name with a group #97
Comments
To get the ball rolling, I'll outline a workaround and some potential approaches: Work around {
"color": {
"accent": {
"type": "color",
"value": "#dd0000"
},
"accent-light": {
"type": "color",
"value": "#ff2222"
},
"accent-dark": {
"type": "color",
"value": "#aa0000"
}
}
} While this works, it's not ideal. Part of the benefit of groups is that avoids having to repeat common prefixes in names, such as "accent" in this example. By using this work-around authors miss out on that benefit. Also, groups can have their own descriptions (and potentially other properties future spec versions may add), so by using this work-around authors lose the ability to add such descriptive information to all the "accent*" tokens as there is no "accent" group anymore. Suggestion 1: Empty name token It turns out that an empty string {
"color": {
"accent": {
"": {
"type": "color",
"value": "#dd0000"
},
"light": {
"type": "color",
"value": "#ff2222"
},
"dark": {
"type": "color",
"value": "#aa0000"
}
}
}
} Since this apprach retains the "accent" group, authors can add things like a description to that group and that would be indendent of any description they give the "" token itself. For example: {
"color": {
"accent": {
"description": "Our brand's accent color and its tints & shades",
"": {
"type": "color",
"value": "#dd0000",
"description": "Our brand's accent color"
},
"light": {
"type": "color",
"value": "#ff2222",
"description": "Tinted version of our brand's accent color"
},
// ...
}
}
} There is actually nothing in the current spec draft that forbids this. However, if you wanted to alias the "color" / "accent" / "" token, the reference would need to be Such reference might look a bit odd and are perhaps unintuitive. For convenience we may therefore want to modify our spec to have a rule stating that a reference that points to a group should be interpreted a pointing to a token with an empty name ( Suggestion 2: Token and group hybrid In the current spec draft, groups and tokens are two very distinct concepts. Only tokens have values and only groups can contain tokens and other groups. What if we allowed hybrids that are both token and group at the same time. They'd have a value (like a token), but could also contain other tokens or groups. Our example might then become something like this: {
"color": {
"accent": {
"type": "color",
"value": "#dd0000",
"light": {
"type": "color",
"value": "#ff2222"
},
"dark": {
"type": "color",
"value": "#aa0000"
}
}
}
} While this may seem quite elegant, it does introduce some challenges:
You could resolve the first 2 challenges by have group-specific property names that don't clash with their token equivalents (e.g. For example, you might end up with something like this: {
"color": {
// "accent" is a group & token hybrid
"accent": {
// "accent" group special props
"group-description": "Our brand's accent color and its tints & shades",
// "accent" token special props
"type": "color",
"value": "#dd0000",
"description": "Our brand's accent color"
// "light", "dark" etc. are items in the "accent" group as per usual
"light": {
"type": "color",
"value": "#ff2222",
"description": "Tinted version of our brand's accent color"
},
// ...
}
}
} I'm sure there's other pros/cons and approaches too. So, let's hear 'em! :-) |
This is so interesting to see this suggestion because that exact idea has been something I’ve been mulling about for a while. I posted a comment in the theme discussion with a similar syntax, but I tried to format it according to previous comments. Here’s one idea for your “hybrid” suggestion but with a syntax I’d prefer: {
"color": {
"accent": {
"type": "color",
"value": "#dd0000",
"mode": {
"light": "#ff2222",
"dark": "#aa0000"
}
}
}
} Note: By namespacing it under |
If groups and tokens can share a name, my inclination is that the token/group hybrid is the right way to go. However, I think we ought to hash out if it makes sense to allow groups and tokens to share a name. I feel like we end up making naming tokens a little more flexible at the cost of a lot of complexity in parsing/reading. In our examples above, what is the value of I'd suggest that we keep things simple and lean on the requirement of unique keys within JSON to provide some guard rails. Say you define the group like this {
"color": {
"type": "group",
"value": {
"accent": {
"type": "group",
"value": {
"light": {
"type": "color",
"value": "#ff2222"
}, {
"dark": {
"type": "color",
"value": "#aa0000"
}
}
}
}
}
} The You do lose some flexibility in naming tokens, but gain a lot of precision. |
If all one cares about is the exported token's name, you could still achieve that pretty easily in the export process! Tokens are already necessarily getting renamed when exporting to code, to turn
One scenario that's somewhat important to me for the future is group aliasing. For example: {
"neutralcontrol": {
"foreground": {
"type": "color",
"rest": { "value": "{...}" },
"hover": { "value": "{...}" },
"pressed": { "value": "{...}" },
"disabled": { "value": "{...}" }
},
"background": {
"type": "color",
"rest": { "value": "{...}" },
"hover": { "value": "{...}" },
"pressed": { "value": "{...}" },
"disabled": { "value": "{...}" }
}
},
"button":
{
"text": {
"color": { "value": "{neutralcontrol.foreground}" }
},
"base": {
"color": { "value": "{neutralcontrol.background}" }
}
}
} (In that example, It's not possible with today's spec, but I want to try to make sure it's possible someday. If |
@drwpow: I think the Perhaps I chose the bad names for my example - the "light" and "dark" colors weren't intended as alternative values for light and dark mode, but rather just 2 extra colors. In hindsight, I probably should have used names like "tint" and "shade". I'm less keen on the But, we should totally look into it for theming, dark/light modes, etc. - feel free to open an issue for that if you like, btw. It's a topic we'll need to address sooner or later. |
@ilikescience So, if I've understood your example you're proposing that we alter the syntax of groups to be more token-like? I.e. rather than just a JSON object where any properties are treated as the names of the nested tokens/groups (with the exception of a few reserved ones like If so, I'm not sure what that provides over the current draft spec. I'd argue we already have a precise way of differentiating between groups and tokens - any object with a It doesn't appear to solve the issue - i.e. there's no way of have "color.accent" somehow be a color that is distinct from "color.accent.light / dark". Please do correct me, if I've misunderstood though! |
@TravisSpomer In light of the recent comments on the reserved words issue (#61), I wonder if combination of the prefix idea there and what you've suggested could work: Let's imagine for a moment we use a special prefix like So, it could look like this (if we assume {
"color": {
"accent": {
"_default": {
"_type": "color",
"_value": "#dd0000"
},
"light": {
"_type": "color",
"_value": "#ff2222"
},
"dark": {
"_type": "color",
"_value": "#aa0000"
}
}
}
} I've been thinking lately about the tree data model that our token files describe (similar to how HTML files define a DOM) and, in the context of this issue, was wondering if that default color token is logically a child of the accent group or a child of the color group. Personally, I'm leaning towards the latter. In this example it makes more sense to me to think of all 3 as accent colors, as opposed to 1 arbitrary color + 2 accent colors. It's just so happens that one of those accent colors doesn't need a further suffix appended to its name when exported as code. I therefore like this kind of approach, where that default token is still something within the group. The hybrid approaches lift it up a level in the hierarchy and that doesn't feel right to me. If we did something like But, I am intrigued by @TravisSpomer's desire to be able reference groups some day. I think there is a way that could co-exist with the So, rather than make the value of {
"neutralcontrol": {
"foreground": {
"_type": "color",
"rest": { "_value": "#..." },
"hover": { "_value": "#..." },
"pressed": { "_value": "#..." },
"disabled": { "_value": "#..." }
},
"background": {
"_type": "color",
"rest": { "_value": "#..." },
"hover": { "_value": "#..." },
"pressed": { "_value": "#..." },
"disabled": { "_value": "#..." }
}
},
"button":
{
"text": {
// The value is no longer an object with a token property and therefore
// not a token. So, when a "bare" reference like this is encountered parsers
// are required to interpret it as a *group* reference.
"color": "{neutralcontrol.foreground}"
},
"base": {
"color": "{neutralcontrol.background}"
}
}
} Even if That could actually be quite nice if you'd prefer to use {
"neutralcontrol": {
"foreground": {
"_type": "color",
"_default": { "_value": "#..." },
"hover": { "_value": "#..." },
"pressed": { "_value": "#..." },
"disabled": { "_value": "#..." }
},
"background": {
"_type": "color",
"_default": { "_value": "#..." },
"hover": { "_value": "#..." },
"pressed": { "_value": "#..." },
"disabled": { "_value": "#..." }
}
},
"button":
{
"text": {
// The value is no longer an object with a token property and therefore
// not a token. So, when a "bare" reference like this is encountered parsers
// are required to interpret it as a *group* reference.
"color": "{neutralcontrol.foreground}"
},
"base": {
"color": "{neutralcontrol.background}"
}
}
} An export tool outputting SASS might then produce something along the lines of: $neutralcontrol-foreground: #... ;
$neutralcontrol-foreground-hover: #...;
$neutralcontrol-foreground-pressed: #...;
// etc...
$button-text-color: $neutralcontrol-foreground;
$button-text-color-hover: $neutralcontrol-foreground-hover;
$button-text-color-pressed: $neutralcontrol-foreground-pressed;
// etc... What do you think? |
Going slightly (more) off-topic, it occurs to me that it might be nice to for "bare" references to also be usable as a shorthand for creating alias tokens. For example: {
"some-group": {
"original-token": {
"value": "2rem",
"type": "dimension"
}
},
// What we already know and love...
"long-hand-alias": {
"value": "{some-group.original-token}"
},
// ...could be equivalent to:
"short-hand-alias": "{some-group.original-token}"
} However, then we'd still need to find a way to reference groups. So maybe there's a slight extension to the reference syntax to allow that: {
"neutralcontrol": {
"foreground": {
"_type": "color",
"_default": { "_value": "#..." },
"hover": { "_value": "#..." },
"pressed": { "_value": "#..." },
"disabled": { "_value": "#..." }
}
},
"button": {
"text": {
"color": "{neutralcontrol.foreground.*}", // notice the .* at the end!
// and the above is basically a short-hand for doing this:
"color-longhand": {
"_default": { "_value": "{neutralcontrol.foreground}" },
"hover": { "_value": "{neutralcontrol.foreground.hover}" },
"pressed": { "_value": "{neutralcontrol.foreground.pressed}" },
"disabled": { "_value": "{neutralcontrol.foreground.disabled}" }
}
}
}
} Probably none of this stuff will be in our initial spec, but the more I think about it, the more I'd like something like this to be possible in the future! |
Yes, precisely. The system I'm using right now already has groups and aliases to groups implemented, and I'd say that it's partly-successful: It definitely does achieve its goal of allowing easier reuse of groups of tokens, but about half of people find the concept confusing, and it's not split down engineer-or-designer lines. Some designers find groups intuitive, but the one currently in charge of our design system finds them hopelessly complicated. Most engineers grasp the idea easily, but some are resistant because they keep thinking that it's something they'll need to support at runtime instead of getting "compiled out." |
I'm mostly just toying with two different approaches to the group syntax, which may necessitate a separate issue. So I'm gonna open up a new discussion to continue that thread to keep things on-topic here. I do want to be direct about the question: should we allow a group and a token to have the same name? In once sense, it makes it slightly easier to organize your tokens if you like to have things like However, it might make it very difficult to talk about (and parse) the thing called I'd love to try and motivate the requirement that tokens and groups should be able to share a name before going deep on solving it - but please excuse me if that conversation was had in an editors meeting. |
Good question. It certainly wasn't discussed in any depth and may well be an edge case we don't need to make special accommodations for. My (vague) recollection is that @dbanksdesign once mentioned folks trying to achieve something similar in Style Dictionary. I've just been through their old issues and found this one where someone wanted to do this (and interestingly used So, if this is something folks want to do, it would be nice if our format's spec had an official answer for how to do it - even it's just some kind of work-around rather than a first class feature of the format. At our last format editor's meeting I suggested opening an issue for this now as this discussion might help inform the ongoing reserved words discussion (#61). My hunch was this might highlight the need for an additional group-level property and that in turn would potentially be new reserved word we'd need to accommodate somehow. |
That's really interesting! Thanks for sharing. Makes me think there could be a market for DCTG file linters someday. People already use things like ESLint and Stylelint to enforce their organisations coding standards. Perhaps one day an equivalent will exist for our spec's file format. Then, if one organisation or team finds group aliases confusing they could discourage their use by configuring their linter to flag it (and suggest creating several token aliases as an alternative). |
@c1rrus thanks for the clarification! I see the use case and I also want to see if we can accommodate it if possible. The hard part for me is that the thing (say, So, say we were working with Next, we work with Thinking through this now, I see how some of the things we've discussed resolve the bulk of the issue. Especially I do think that these cases outline one argument for why groups should have |
Ok, returning to this after having some experience with trying to parse larger token files, I think some kind of explicit key is necessary. The main reason is that groups can have properties that are named the same as tokens. For example: {
"color": {
"$type": "color",
"$description": "lorem ipsum",
"$value": "blue",
"primary": {
"$value": "red"
}
}
} In this example, it's unclear if the "$type" at the root applies to the root token, "color" (blue) or is intended to cascade down to child tokens like "color.primary". Same with description; is it a description for the group? or a description of the root token? So, I'd like to recommend the following change to the group format:
I'm open to other names, but I chose "rootToken" to be very explicit that this should be a token that is associated with the root of the group. I also considered "root" and "base". I deliberately avoided "default", as this doesn't quite make sense grammatically — a default implies options, which isn't how groups work. |
Just wanted to chime in briefly here to say that I'm experimenting with setting up design tokens using the DTCG format, and our current color variables are set up something like this: :root {
--color-gray: var(--color-gray-50);
--color-gray-10: hsl(214deg, 15%, 10%);
--color-gray-20: hsl(214deg, 15%, 20%);
--color-gray-30: hsl(214deg, 15%, 30%);
--color-gray-40: hsl(214deg, 15%, 40%);
--color-gray-50: hsl(214deg, 15%, 50%);
} My first intuition was to try to replicate this by using {
"color": {
"$type": "color",
"base": {
"gray": {
"$value": "#6c7d93"
"10" : { "$value": "#16191d" },
"15": { "$value": "#21252c" },
"20" : { "$value": "#2b323b" },
"30" : { "$value": "#414b58" },
"40" : { "$value": "#576475" },
"50" : { "$value": "#6c7d93" }
},
}
}
} This does not work in style-dictionary, so I came here and found this issue. I think it would be great to have some kind of guidance on how to accomplish this. I don't have strong opinions on how it should be done or the various tradeoffs, but what I posted above was the most intuitive for me personally. I'm just getting started with this, so I thought I'd share my "beginner mindset" in case that's useful. :) |
@IanVS I don't think parsers like Style Dictionary support a top-level variable and nested variables inside. It's either one or the other. If I had to guess it's probably an issue with some exports, like the JS object syntax used by CSS in JS platforms. If you had a top-level variable defined, you couldn't also have an array of child elements, one would override the other. {
colors: {
// It can't be a string and object at same time
gray: "#6c7d93",
gray: {
10: "#16191d",
},
},
} You'd have to do something like this and make your top level gray something like {
"color": {
"$type": "color",
"base": {
"gray": {
"0": { "$value": "#6c7d93" },
"10" : { "$value": "#16191d" },
"15": { "$value": "#21252c" },
"20" : { "$value": "#2b323b" },
"30" : { "$value": "#414b58" },
"40" : { "$value": "#576475" },
"50" : { "$value": "#6c7d93" }
},
}
}
} This kind of pattern happens more often with variants and component/composite tokens. It's easy to want to do a To acknowledge the discussion: I'm not sure how having a group type would resolve the consolidation of the tokens into variables. Unless each variable is unique (like CSS exports, where you get individual For example, I wanted to theme the gray colors, this definitely works with individual variables: // Dark theme
export const colorGray = "#6c7d93";
export const colorGray10 = "#16191d"; Then I could use these inside my project: import lightTheme from "./dark"
import darkTheme from "./dark"
const theme = isDarkMode ? darkTheme : lightTheme; But if I had an object based syntax, this doesn't work. And ideally the goal of these tokens would be to be used across all -- if not most platforms? So I feel like this would just always conflict with dynamic languages like JS and the common practice of theming (like CSS in JS). If it were supported, there'd definitely have to be some sort of guidelines for parsers to understand that groups would only be supported in "split variable" contexts. This way they could throw an error to help user understand why the tokens aren't properly reflected in more dynamic contexts (e.g. JS object syntax, SASS maps, etc). |
A question that's come up various discussions and for which the current spec draft doesn't really have an answer is this: How can you have design tokens that share the same name as a group?
For example, imagine you wanted to author a token file that could be exported to SASS like this:
...and you wanted to use groups to organise your token file. You could create a
color
group, with anaccent
group inside it and put yourlight
anddark
tokens in there. But then, where does the token that gets exported as$color-accent
go?This issue came up at the last format editors' meeting and we feel like this is something our format should support in some way. Also, debating this issue may generate some ideas or highlight considerations that could be useful for the "reserved words" discussion over in issue #61.
We'd love to hear your thoughts and ideas on this!
The text was updated successfully, but these errors were encountered: