2. Structure
📖 Articles about token structure
- Design Tokens beyond colors, typography, and spacing. - Cristiano Rastelli
- Creating Themeable Design Systems - Brad Frost
- Tokens in Design Systems - Nathan Curtis
- Tokenize it - Maya Hampton
In this section we will add more design tokens to our package to show different types of tokens and ways how to structure them.
If you skipped step 1, no worries, just check out the step-2 branch:
git fetch && git checkout step-2
Or if you haven't checked out the repository yet:
git clone -b step-2 https://github.com/dbanksdesign/clarity-2020-design-token-workshop.git
And then be sure to install the npm dependencies if you haven't:
npm ci
2.1 Structure
There are 2 types of structure we can talk about: file structure and token structure. With Style Dictionary you can split up your files in whatever way makes sense to you. Style Dictionary will merge all of the source token files together into one big dictionary object. File/directory structure doesn't matter as much when creating design tokens. So let's talk about token structure.
Flat v. nested
{"color-background-primary": { "value": "#fff" },"color-background-secondary": { "value": "#ccc" }}
{"color": {"background": {"primary": { "value": "#fff" },"secondary": { "value": "#ccc" }}}}
Whether you choose flat or nested, some integrations would need a flat list of variables. For example, if you want to output to CSS variables you need a flat list of variables. Really, there is not much difference between flat or nested token structures, both work just fine. I prefer the nested structure because that is how my brain thinks and it matches up with how I organize the files and directories too.
Theo uses a flat structure and you split your design tokens by file so you have one-file-in one-file-out.
Implicit v. explicit typing
One of the factors in deciding how to organize our tokens is whether we want to explicitly or implicitly type them.
Explicit typing
Here we explicitly call out what kind of token each design token is. Theo uses the "type" attribute
{"color-background-primary": {"value": "#fff","type": "color"}}
Implicit typing
Implicit typing relies on the transforms to understand the type of each token without explicitly defining it. The built-in transforms in Style Dictionary do this by looking at the token's object path and assigning a category, type, and item (CTI). For example, everything under the "color" top-level namespace has a category of "color". The next level in the structure is it's "type"
{"color": {"background": {"primary": { "value": "#fff" }}}}
That design token has
- category: color
- type: background
- item: primary
Style Dictionary also adds some other information on each token to help with transforming and formatting.
You could also have implicit typing in other ways. If you have a flat token structure rather than a nested one you could split the name and look at the first part to see what type of token it is.
{"color-background-primary": { "value": "#fff" }}
key.split('-')[0] === 'color'
Having all our tokens in a single JSON file is fine for a small number of tokens, but as we start to add more it becomes more necessary to organize our tokens. Let's create a directory called tokens. This is where we will put all of our design token files.
mkdir tokens
Now let's move our design tokens to this directory. We could just add the tokens.json file in there but that wouldn't be too helpful. Instead let's create color.json and size.json files.
Then put the color tokens in the color.json file and the size tokens in the size.json file.
{"color": {"background": {"primary": { "value": "#fff" },"secondary": { "value": "#eee" }}}}
{"size": {"padding": {"small": { "value": 0.5 },"medium": { "value": 1 },"large": { "value": 1.5 },"xl": { "value": 3 }}}}
Now we can delete the tokens.json file. Now run npm run build
and check out the dist/variables.scss file. Ooops! We need to update the configuration! Open sd.config.js and update the source
attribute. We want the source
to point to all JSON files in the tokens/ directory.
We can do that by using file wildcards, sometimes referred to as globs. Update the source to this:
source: ["tokens/**/*.json"],
What's a glob?
Globbing is a pattern to define "wildcards" in a path or URI. In the code above, "tokens/**/*.json" means look in the tokens directory, and any directories underneath "**", and match any files that end in ".json".Now run npm run build
again and things should be back to normal!
At this point you might be asking yourself, how does Style Dictionary know what is a color and what is a size?
This is where transforms and our implicit typing structure comes in!
So what does a larger scale token structure look like? The most common I have seen is the 3-tiered approach: core, theme, component.
This is the first layer of design tokens, they are all the options. For the most part, every other design token should reference one of these options somewhere down the reference chain. But you should almost never directly use these tokens outside of your token package because they have no semantic meaning.
In Brad Frost's article he calls the second tier "high-level application" tokens. Whatever you want to call it, it is the second layer or tier that references the first layer and adds semantic, but generic, meaning. For example, you generally wouldn't want to use color-core-grey-100
directly in an application because it has no meaning. But 'color-background-primary', now that has meaning!
Similar to how in a programming language you can create relationships by referencing other variables or constants, we can do the same thing with design tokens.
let colorCoreGrey10 = "#eeeeee";let colorBackgroundSecondary = colorCoreGrey10;let buttonDisabledBackground = colorBackgroundSecondary;
Now if we wanted to change colorCoreGrey10
it would affect every other variable that references it (and references of references). Maybe that isn't what we want. Maybe we want to make colorBackgroundSecondary
reference colorCoreGrey20
instead, that will have a more localized effect, but still affect any reference to colorBackgroundSecondary
. Finally the most localized change would be changing buttonDisabledBackground
as generally component tokens are not referenced.
2.2 Color tokens
So let's start by creating a directory in our tokens/ directory called color. Can you guess the first thing we will add in here?
That's right a 🎨 COLOR PALETTE! Create a file called core.json in the tokens/color/ directory.
Now in order to save everyone time, I have a pre-built core color palette ready to go. That's a lie. I'm borrowing the color palette from the Sprout Social Seeds Design System
{"color": {"core": {"green": {"0": { "value": "#ebf9eb" },"100" : { "value": "#d7f4d7" },"200" : { "value": "#c2f2bd" },"300" : { "value": "#98e58e" },"400" : { "value": "#75dd66" },"500" : { "value": "#59cb59" },"600" : { "value": "#2bb656" },"700" : { "value": "#0ca750" },"800" : { "value": "#008b46" },"900" : { "value": "#006b40" },"1000" : { "value": "#08422f" },"1100" : { "value": "#002b20" }},"teal": {"0": { "value": "#e5f9f5" },"100" : { "value": "#cdf7ef" },"200" : { "value": "#b3f2e6" },"300" : { "value": "#7dead5" },"400" : { "value": "#24e0c5" },"500" : { "value": "#08c4b2" },"600" : { "value": "#00a99c" },"700" : { "value": "#0b968f" },"800" : { "value": "#067c7c" },"900" : { "value": "#026661" },"1000" : { "value": "#083f3f" },"1100" : { "value": "#002528" }},"aqua": {"0": { "value": "#d9fcfb" },"100" : { "value": "#c5f9f9" },"200" : { "value": "#a5f2f2" },"300" : { "value": "#76e5e2" },"400" : { "value": "#33d6e2" },"500" : { "value": "#17b8ce" },"600" : { "value": "#0797ae" },"700" : { "value": "#0b8599" },"800" : { "value": "#0f6e84" },"900" : { "value": "#035e73" },"1000" : { "value": "#083d4f" },"1100" : { "value": "#002838" }},"blue": {"0": { "value": "#e9f8ff" },"100" : { "value": "#dcf2ff" },"200" : { "value": "#c7e4f9" },"300" : { "value": "#a1d2f8" },"400" : { "value": "#56adf5" },"500" : { "value": "#3896e3" },"600" : { "value": "#2b87d3" },"700" : { "value": "#2079c3" },"800" : { "value": "#116daa" },"900" : { "value": "#0c5689" },"1000" : { "value": "#0a3960" },"1100" : { "value": "#002138" }},"purple": {"0": { "value": "#f2f2f9" },"100" : { "value": "#eaeaf9" },"200" : { "value": "#d8d7f9" },"300" : { "value": "#c1c1f7" },"400" : { "value": "#a193f2" },"500" : { "value": "#9180f4" },"600" : { "value": "#816fea" },"700" : { "value": "#6f5ed3" },"800" : { "value": "#5e4eba" },"900" : { "value": "#483a9c" },"1000" : { "value": "#2d246b" },"1100" : { "value": "#1d1d38" }},"magenta": {"0": { "value": "#fef0ff" },"100" : { "value": "#f9e3fc" },"200" : { "value": "#f4c4f7" },"300" : { "value": "#edadf2" },"400" : { "value": "#f282f5" },"500" : { "value": "#db61db" },"600" : { "value": "#c44eb9" },"700" : { "value": "#ac44a8" },"800" : { "value": "#8f3896" },"900" : { "value": "#6c2277" },"1000" : { "value": "#451551" },"1100" : { "value": "#29192d" }},"pink": {"0": { "value": "#ffe9f3" },"100" : { "value": "#fcdbeb" },"200" : { "value": "#ffb5d5" },"300" : { "value": "#ff95c1" },"400" : { "value": "#ff76ae" },"500" : { "value": "#ef588b" },"600" : { "value": "#e0447c" },"700" : { "value": "#ce3665" },"800" : { "value": "#b22f5b" },"900" : { "value": "#931847" },"1000" : { "value": "#561231" },"1100" : { "value": "#2b1721" }},"red": {"0": { "value": "#ffeae9" },"100" : { "value": "#ffd5d2" },"200" : { "value": "#ffb8b1" },"300" : { "value": "#ff9c8f" },"400" : { "value": "#ff7f6e" },"500" : { "value": "#f76054" },"600" : { "value": "#ed4c42" },"700" : { "value": "#db3e3e" },"800" : { "value": "#c63434" },"900" : { "value": "#992222" },"1000" : { "value": "#6d1313" },"1100" : { "value": "#2b1111" }},"orange": {"0": { "value": "#ffede3" },"100" : { "value": "#fcdccc" },"200" : { "value": "#ffc6a4" },"300" : { "value": "#ffb180" },"400" : { "value": "#ff9c5d" },"500" : { "value": "#fc8943" },"600" : { "value": "#f57d33" },"700" : { "value": "#ed7024" },"800" : { "value": "#ce5511" },"900" : { "value": "#962c0b" },"1000" : { "value": "#601700" },"1100" : { "value": "#2d130e" }},"neutral": {"0" : { "value": "#FFFFFF" },"100" : { "value": "#f3f4f4" },"200" : { "value": "#dee1e1" },"300" : { "value": "#c8cccc" },"400" : { "value": "#b0b6b7" },"500" : { "value": "#929a9b" },"600" : { "value": "#6e797a" },"700" : { "value": "#515e5f" },"800" : { "value": "#364141" },"900" : { "value": "#273333" },"1000" : { "value": "#162020" },"1100" : { "value": "#040404" }},"yellow": {"0": { "value": "#fff8e2" },"100" : { "value": "#fdefcd" },"200" : { "value": "#ffe99a" },"300" : { "value": "#ffe16e" },"400" : { "value": "#ffd943" },"500" : { "value": "#ffcd1c" },"600" : { "value": "#ffbc00" },"700" : { "value": "#dd9903" },"800" : { "value": "#ba7506" },"900" : { "value": "#944c0c" },"1000" : { "value": "#542a00" },"1100" : { "value": "#2d1a05" }}}}}
A few things to point out here:
- I love that they use 'neutral', I always get 'grey' and 'gray' mixed up.
- Each color has a scale! Other color scale naming schemes I've seen: 1,2,3,4; darker, dark, light, lighter; and 0 - 100.
Does the file structure need to match the object structure?
Nope! Well at least not how Style Dictionary does things. It merges all the token files it finds together into one big dictionary object. The file name does not matter.The next type of colors I usually define are brand colors. Create a file called brand.json inside tokens/color/. This is where we will define our products branded colors. These brand colors can then get referenced in other tokens like button and link colors.
Let's start with the structure:
{"color": {"brand": {}}}
A lot of times you will have primary and secondary brand colors, so let's add those, but instead of putting a hex code for the value, let's reference a color from our core color palette...
{"color": {"brand": {"primary": { "value": "{color.core.pink.700.value}" },"secondary": { "value": "{color.core.aqua.700.value}" }}}}
This is the first time we are referencing other design tokens! I think this is one of the most important concepts in design tokens: being able to reference another token. And being able to have references of references of references! This allows you to be able to make changes with the right surface area. You will see some of these reference chains as we define more tokens. For example, you could have a core color palette defined that has a scale of neutral colors. Then you have font colors like color-font-primary
reference color-core-neutral-900
. One more level you can have component tokens like input-font-color
which references color-font-primary
. If you want to change all the neutral colors throughout all apps you can do that, or maybe you want to change color-font-primary
to be colore-core-neutral-800
now, or maybe you want input-font-color
to reference color-font-secondary
. Each of these will have different areas of effect.
If you are using Theo, I'm pretty sure you can't directly reference another design token, but you can define "aliases" which you import into your design token files.
Let's pause here and look at how token referencing works. Run npm run build
and take a look at the SCSS variables file. Notice that
$color-brand-primary: #ce3665;
Both Theo and Style Dictionary handle the reference resolution so there are no reference chains in generated files. This helps to ensure that generated files are always valid. If we didn't end up exporting certain design tokens that might cause issues.
Now what if we wanted to add "dark" variants of our primary and secondary brand colors? Could we do this?
{"color": {"brand": {"primary": {"value": "{color.core.pink.700.value}","dark": { "value": "{color.core.pink.900.value}" }},"secondary": {"value": "{color.core.aqua.700.value}","dark": { "value": "{color.core.aqua.900.value}" }}}}}
Let's try to build it and see what happens. Run npm run build
it should build just fine, but take a look at the dist/variables.scss file. Only color-brand-primary
and color-brand-secondary
are in the output!
This is because Style Dictionary will see an object with a "value" attribute and assume that object is a token, so nested tokens underneath won't be recognized. Instead what I used to do is have a token like "base" or "default":
{"color": {"brand": {"primary": {"default": { "value": "#ff9900" },"dark": { "value": "#ff0000" }}}}}
It works, but it might not be exactly how we want to use those tokens in our apps. Most of the time we will consume a flat list of tokens like CSS/SCSS variables so it would totally make sense to have
$color-brand-primary: #ce3665;$color-brand-primary-dark: #931847;
Here is the genius "hack" that the folks at Intuit showed me:
{"color": {"brand": {"primary": {"_": { "value": "{color.core.pink.700.value}" },"dark": { "value": "{color.core.pink.900.value}" }}}}}
Name transforms use string formatting functions like lodash's string methods:
// token.path refers to the token's object path, in the example// above it would be color, brand, primarykebabCase( token.path.join(' ') );
Methods like these ignore trailing and proceeding underscores, dashes, and whitespace so we will get the output we want:
$color-brand-primary: #ce3665;$color-brand-primary-dark: #931847;
Brilliant! Another way you could solve this issue is putting both "primary" and "dark" on the same object level like so:
{"color": {"brand": {"primary": { "value": "{color.core.pink.700.value}" },"primary-dark": { "value": "{color.core.pink.900.value}" }}}}
Again because name transforms use string formatting methods based on the token's object path we still get the output we desire:
$color-brand-primary: #ce3665;$color-brand-primary-dark: #931847;
Let's go with that last strategy and add "primary", "primary-dark", "secondary", and "secondary-dark" on the same level:
{"color": {"brand": {"primary": { "value": "{color.core.pink.700.value}" },"primary-dark": { "value": "{color.core.pink.900.value}" },"secondary": { "value": "{color.core.teal.700.value}" },"secondary-dark": { "value": "{color.core.teal.900.value}" }}}}
After Brand colors, next up is background colors. Create a background.json file in the tokens/color/ directory. What kinds of semantic backgrounds could we define?
The first that I usually start with is a progression. Generally there aren't more then 3-5 shades of background colors (outside of specific meaning which we will get to in a bit). So rather than having a background scale of 100, 200, 300, I usually opt for "primary", "secondary", "tertiary".
{"color": {"background": {"primary": { "value": "{color.core.neutral.0.value}" },"secondary": { "value": "{color.core.neutral.100.value}" },"tertiary": { "value": "{color.core.neutral.200.value}" }}}}
Can you think of other semantic, application-wide, theme-like background colors?
What about backgrounds that signify certain states like: danger, warning, success, or information? Let's add those!
{"color": {"background": {"primary": { "value": "{color.core.neutral.0.value}" },"secondary": { "value": "{color.core.neutral.100.value}" },"tertiary": { "value": "{color.core.neutral.200.value}" },"danger": { "value": "{color.core.red.0.value}" },"warning": { "value": "{color.core.yellow.0.value}" },"success": { "value": "{color.core.green.0.value}" },"info": { "value": "{color.core.blue.0.value}" },"disabled": { "value": "{color.background.tertiary.value}" }}}}
Now if we run npm run build
, notice there are some messages in the console output...
Property Value Collisions:Collision detected at: color.background.primary! Original value: #fff, New value: {color.core.neutral.0.value}Collision detected at: color.background.secondary! Original value: #eee, New value: {color.core.neutral.100.value}
This lets us know that we are accidentally defining design tokens in the same space so we don't get weird behavior in our application. Go ahead and delete the file tokens/color.json and run npm run build
again. Ah all better.
Now we can pretty much follow the same structure for font colors as well. Add a font.json file in tokens/color/ directory and copy + paste what we have in our background.json file. Then we can change "background" to "font" and change the colors so that they make sense for font colors.
{"color": {"font": {"primary": { "value": "{color.core.neutral.1100.value}" },"secondary": { "value": "{color.core.neutral.900.value}" },"tertiary": { "value": "{color.core.neutral.800.value}" },"danger": { "value": "{color.core.red.800.value}" },"warning": { "value": "{color.core.yellow.800.value}" },"success": { "value": "{color.core.green.800.value}" },"info": { "value": "{color.core.blue.800.value}" },"disabled": { "value": "{color.font.tertiary.value}" }}}}
There are still some more font colors we can add... interactive text! Also know as links and buttons.
"interactive": { "value": "{color.brand.primary.value}" },"hover": { "value": "{color.brand.primary-dark.value}" },"active": { "value": "{color.brand.secondary.value}" },"disabled": { "value": "{color.font.tertiary.value}" },
I chose "interactive" here. I used to use "link", but I think that is too specific. But whatever you think makes the most sense for you.
2.3 Size tokens
I like to use a "size" top-level namespace and then use a second-level to denote what kind of size: font, padding, border, offset, etc. This allows for certain languages to use the same type, for all size values, and differentiated ones on other languages. For example, in iOS you use CGFloat (Core Graphics Floating point number) for font size, width, padding, etc. On Android you use DP (density independent pixels) for paddings and widths and SP (scalable pixels) for font sizes so that font sizes can scale based on user preferences [Android docs].
Just like we did with colors lets add a tokens/size/ directory. Let's start with padding, create a padding.json file in the new size directory. Copy and paste the contents of tokens/size.json into this new file and delete the old one.
I have also seen padding tokens called "spacing" which I like because it is a bit more generic as it could do with margin or gap values. There was a great talk that mentioned how their design systems don't use margin at all because it is hard to reason about. Instead they only use padding because each component should only affect itself and not things around it.
There is some debate on t-shirt sizing (small, medium, large) v. a numbered scale like 1,2,3. The downside of t-shirt sizing is you can't add a new value in-between 2 values, if you wanted to add size between medium and large you are out of luck. I guess you could call it "medium-large", but that's kind of weird. With a numbered scale you can add "2.5" in-between 2 and 3.
The open-ended data structure of this pairing allows for future extension without modifying existing code or data. The key remains constant in the code, while the value can be changed.
Maya Hampton Tokenize it
We have a few other size tokens to add. Add a font.json file in the tokens/size/ directory. We will follow a similar t-shirt naming convention, but let's add a few more tokens here:
{"size": {"font": {"small": { "value": 0.75 },"medium": { "value": 1 },"large": { "value": 1.5 },"xl": { "value": 2.25 },"xxl": { "value": 3.5 }}}}
I am using REM based sizing because that is what I want to use on the web. When we get to the integration section I will explain how that works on Android and iOS.
2.4 Component tokens
The final layer!
The longer I do this, the more convinced I am that global design tokens and component design tokens are hugely different problems and the fact that everyone is treating them the same is why much of current solutions are brittle and rely almost entirely on convention
Sarah Federman via Twitter
First we should figure out where to put these component tokens. To do that I will take you on a bit of a journey how I initially started using design tokens in my organization. This was a few years ago and I really thought I had something with the CTI nested object structure thing. All colors live under the "color" namespace, cool! This works well for the first 2 layers of design tokens, but gets really messy when dealing with component tokens. Here is an example to hopefully make that clear:
{"color": {"font": {"base": { "value": "{color.base.squidink.140.value}" },"secondary": { "value": "{color.base.squidink.80.value}" },"tertiary": { "value": "{color.base.squidink.60.value}" },"quaternary": { "value": "{color.base.squidink.40.value}" },"link": { "value": "{color.base.blue.120.value}" },"active": { "value": "{color.base.orange.100.value}" },"accent": { "value": "{color.base.orange.100.value}" },"error": { "value": "{color.base.red.120.value}" },"warning": { "value": "{color.base.orange.140.value}" },"success": { "value": "{color.base.green.120.value}" },"disabled": { "value": "{color.font.secondary.value}" },"button": {"primary": {"base": { "value":"{color.font.base.value}" },"disabled": { "value":"{color.font.secondary.value}" },"active": { "value":"{color.font.inverse.base.value}" }},"secondary": {"base": { "value":"{color.font.link.value}" },"disabled": { "value":"{color.font.secondary.value}" },"active": { "value":"{color.font.inverse.base.value}" }}}}}}
I didn't really like the idea of having all the font colors in this file. Plus, the tokens for a button are split across different files 🤮. So I thought, because all the token files get merged anyways I will just create a file for each component, but follow the same structure. Which gave me this for our tab component:
{"size": {"dimension": {"tab": {"height": { "value": "40" },"max_width": { "value": "150" }}},"padding" : {"tab": { "value": "{size.padding.base.value}" }},"font": {"tab": { "value": "{size.font.base.value}" }},"border": {"tab": {"active": { "value": "2" }}}},"color": {"font": {"tab": {"base": { "value": "{color.font.link.value}" },"active": { "value": "{color.font.active.value}" }}},"background": {"tab": {"base": { "value": "{color.background.base.value}" }}},"border": {"tab": {"base": { "value": "{color.border.base.value}" },"active": { "value": "{color.brand.primary.base.value}" }}}}}
Yea, not great. It is because I was treating component tokens the same as theme/core tokens, which they clearly are not. These are 2 different sets of problems. Where I have landed now is something like this:
{"component": {"toast": {"background-color": { "value": "{color.background.low-priority.value}" },"color": { "value": "{color.font.inverse.base.value}" },"font-size": { "value": "{size.font.base.value}" },"padding": { "value": "{size.padding.base.value}" },"error": {"background-color": { "value": "{color.background.error.value}" },"color": { "value": "{color.font.inverse.base.value}" }},"success": {"background-color": { "value": "{color.background.success.value}" },"color": { "value": "{color.font.inverse.base.value}" }},"warning": {"background-color": { "value": "{color.background.warning.value}" },"color": { "value": "{color.font.inverse.base.value}" }},"info": {"background-color": { "value": "{color.background.info.value}" },"color": { "value": "{color.font.inverse.base.value}" }}}}}
Ah much better.
In this workshop we will build 2 components' tokens: button and badge. I picked these components somewhat arbitrarily, but they should be good representations. They have different variants and aren't too complex to build.
Button
Of course we are starting with a button. The humble button in our fake design systems has some variants: primary and outline.
Variants?
I have seen this called a few different things: variant, type, modifier, style, etc. The most common that I have seen is "variant" and "type", but whatever makes the most sense to you.What component design tokens do you think we should define for our button?
{"component": {"button": {"font-size": { "value": "{size.font.small.value}" },"padding": { "value": "{size.padding.medium.value}" },"color": { "value": "{color.font.interactive.value}" },"border-radius": { "value": "{size.border.radius.small.value}" },"primary": {"background-color": { "value": "{color.brand.primary.value}" },"color": { "value": "{color.font.inverse.value}" }},"outline": {"border-width": { "value": "{size.border.width.medium.value}" },"border-color": { "value": "{color.border.primary.value}" }}}}}
One thing to note here: we need to add a new token to our font colors, and "inverse" font color.
Badge
The humble badge. Similar to our button it has some variants as well: default, warning, danger, and success.
This is where our semantic background and font colors come in! We can reference warning, danger, success, and info colors here.
{"component": {"badge": {"font-size": { "value": "{size.font.small.value}" },"padding-horizontal": { "value": "{size.padding.medium.value}" },"padding-vertical": { "value": "{size.padding.small.value}" },"border-radius": { "value": "{size.border.radius.large.value}" },"background-color": { "value": "{color.background.info.value}" },"color": { "value": "{color.font.primary.value}" },"danger": {"background-color": { "value": "{color.background.danger.value}" },"color": { "value": "{color.font.danger.value}" }},"warning": {"background-color": { "value": "{color.background.warning.value}" },"color": { "value": "{color.font.warning.value}" }},"success": {"background-color": { "value": "{color.background.success.value}" },"color": { "value": "{color.font.success.value}" }}}}}
Notice the 'padding-horizontal' and 'padding-vertical'? These are just names of tokens so we can name them however we want! But other than that I usually stick to CSS naming like "background-color", "color", etc.
Typography
As you can probably guess by now, there is no right way to define typography in design tokens. There are 2 general camps of thought here:
- Typographic styles that compose multiple lower-level properties (tokens) are like components: they compose multiple design tokens.
- Typographic styles are compound tokens meaning their value is an object rather than a primitive string or number.
Neither one is right or wrong. So let's look at how to implement both.
Typography as a component
This is a bit easier to implement so we will start with it. We will define a typographic style like we would a component.
{"typography": {"h1": {"font-size": { "value": "{size.font.xl.value}" },"color": { "value": "{color.font.primary}" },"line-height": { "value": "{size.lineHeight.xl.value}" }}}}
Because most of the time design tokens are output as a flat map or array, this would produce 3 component tokens (example in SCSS):
$typography-h1-font-size: 3rem;$typography-h1-color: #000;$typography-h1-line-height: 2;
Now you would still need to then use these SCSS variables:
h1 {font-size: $typography-h1-font-size;color: $typography-h1-color;line-height: $typography-h1-line-height;}
We could build a custom format that generates that SCSS for us though!
Typography as a compound token
This one is a bit more difficult to handle.
On Android we would need to create a style object in XML and add the proper attributes to it.
I like keeping these as "components" or "token groups", while keeping tokens as primitive values. We can still create custom formats that output a full CSS declaration or Sass mixin on web, and a style resource for Android.
What shouldn't be a token?
In Nathan Curtis's article he give a general rule of 3, if it is used 3 times it should probably be a token. Really there is no hard-and-fast rule, but whatever you think makes the most sense.Phew. Hopefully you are still with me. But we covered a lot of important topics on how to build out your token structure. Remember: there is no right or wrong way to structure your tokens! Do you have other ways of structuring your tokens? I would love to hear about it! Hit me up on Twitter
Our journey is not over yet though. Now that we have systematized our design decisions it is time to integrate those decisions in our applications on each platform.