Sam's fancy math equations in Tokens Studio
A walkthrough of some must-know math equations to avoid tokens that aren't whole numbers, the right units for responsive design and how to create a scale that snaps to a grid if you like.
Confession time, I'm a bit of a "type-A" person. When I see the border radius of something in Figma set to 13.34 I start to twitch a little bit. 😜
The good news is Tokens Studio has a pretty kick-ass math package working under the hood that I’ve used and abused pretty hard over the last couple of years.
Here are some of my favourite math equations I've used to build all sorts of dimension tokens and even a proportional scale that snaps to a 4/8 pixel grid!
I will use typography as an example in this guide, but the same ideas apply to sizing, spacing, etc.
Feel free to grab my Figma community file to check out the tokens I created
Map of contents
Some jargon to be aware of:
Raw value- exactly what is typed
Resolved value- the computed value of the equation
Particle token - the individual tokens that are combined to make a
composite token
Composite token - any token requiring more than 1 token, like
typography
Scale - all tokens in a range with values that chance proportionally
Step/steps - each token within the scale
Round to the nearest whole number
It's common when working with math equations to have values that are in decimal places, and a 21.44px
font size might be less than ideal.
To round to the nearest whole number, we can put our equation inside the roundTo
function.
If I create an other
token with this raw value:
roundTo(2 * 1.33, 0)
The resolved value will = 3
because 2.66 rounded to the nearest whole number is 3.
References work too
We can replace the raw values with references to other tokens if we wish.
Using the same example from above, the token value might be:
roundTo({token-name} * 1.33, 0)
Or
roundTo({token-name} * {multiplier-token-name}, 0)
Create a percentage from a unitless number
Have you ever had an engineer ask you why your line height tokens are in percentage instead of unitless numbers, and you had to google why they were asking? No, me either. 🙈
Responsive design wants a unitless number, but Figma takes every unitless number we pass it and turns it into a pixel value, and for line height, they prefer percentage as their "responsive" value.
So instead, create a number
or lineHeights
token with a unitless number as a value:
Token name =
lineHeight.unitless.heading.relaxed
Raw value = 1.2
Then create a typography
composite token and reference the unitless number token with this math equation as the value of the lineHeight
particle token:
{lineHeights-unitless.heading.relaxed} * 100%
Scales that round to a (4)pixel grid
I wanted to set up a scale with values that change proportionally between steps to have on hand as I build out my design system foundations.
For things like typography and icons, it's nice to have a range of sizes that look cohesive and intentional.
The goal is to have a scale that scales with me as my system scales... 😜
If I want to make changes in the future, I want to turn as few "knobs" as possible and have my whole design system change.
For example, if I'm working on a product that supports devices like smartwatches, mobile, web, and TV, I'd like to use as many of the same components as possible and adjust the scale for that device.
Or maybe I'm working on a white-label design system; I want to show the next client what their designs might look like using a major second scale instead of the golden ratio scale.
Truthfully, for this, I would likely use a tool like
https://proportio.app/
to do the exploration, then come back and add those values into my tokens and watch my existing design system change with the new decisions.
Token types
I decided to use the "other"
token type because it can be referenced quite easily in other tokens (dimensions, number, size, etc) and the math package Tokens Studio uses under the hood works well with it.
Token names
I'll be building this scale using T-Shirt sized names for ease of understanding. If you prefer a different method of naming the "steps" on your scale, feel free to adjust!
The math package
The math package Tokens Studio uses under the hood has a particular way of writing equations and it really does not like when we have units in the equation.
https://www.npmjs.com/package/expr-eval?activeTab=readme
So it's important to remember to create tokens being used in math equations to be unitless numbers.
Also recall that any unitless numeric token will be converted to pixels when the plugin passes the token to Figma.
We can work around this in the plugin to play nicely with Figma, and the Style Dictionary tool engineers use to work with our tokens in code can add units there for us if needed.
Step 1 - Scale utility tokens
The first tokens we create are our "utility tokens". They are referenced within each step in the scale.
In the future, if we want to change the scale, we only need to adjust these tokens instead of every step in the scale.
Base
The "base" token represents the "mid-point" of our scale.
Thinking of typography, our paragraph-2 (p2) font size is typically our "base".
Larger sizes (p1) will be to the right of the base on the scale
Smaller sizes will be on the left.
I use "mid-point" in air quotes here because it might not be in the literal middle of the scale. In the case of typography, there are usually many larger sizes, so the p2 token, which has the base value, sits close to the left side of the scale.
This concept of larger and smaller than "mid-point" will become more clear as we start to build the steps in our scale.
Multiplier
The "multiplier" token represents the value of the scale we are working with. For example, if we wanted to create a scale based on the golden ratio, our multiplier token would have a value of 1.33
.
A simple math equation might look like:
{base} * {multiplier}
Don't worry about the resolved value being a funky decimal number, we will write the equation to ensure every step in the scale is a whole number.
Factor
The "factor" token helps us (nit-picky designers) round the resolved value of our equation.
For those of us who are still working within a 4 or 8px grid, we set the value of the factor token to
4
or8
If you want all your values to be even number, you could set this to be
2
If you don't care at all (I'm envious) you can use a much simpler equation and leave this out.
Rem Base
The "rem-base" token represents the pixel value of a single rem unit.
In Tokens Studio, you can reference this token in the plugin settings page.
This is handy if you like the idea of being able to change the rem value by changing a token for a future project need.
The value of 1 rem unit is almost always 16
pixels.
Step 2 - Create the "mid-point" token
In our example, the scale.md
(medium) step in the scale will be the "mid-point" so we set its value to reference the "base" token by placing the name of the base token inside curly brackets {scale.base}
.
So we can see that the resolved value of the scale.md
token is 16
which it's getting from the scale.base
token value.
If I change the
scale.base
token value to18
thescale.md
token value will also be18
Step 3 - Create the "steps down"
In our example, the scale.sm
step will be one step down, or smaller than our scale.md
token.
So we need to write an equation that looks at the
scale.md
token, applies thescale.multiplier
value, and rounds down to the nearest whole number according to ourscale.factor
token value.
You can copy this code snip and paste it into any text editor to modify your token names if needed:
floor(roundTo({scale.md} / {scale.multiplier}, 0) /{scale.factor})*{scale.factor}
This becomes the raw value of our scale.sm
token, which has a resolved value of 12
.
So we write the equation using the "floor" and "roundTo" functions for all steps on the scale smaller than the "mid-point".
Create additional steps down
To create the next step down, we will use the same math equation, but we will swap out the name of the token we are referencing in the first set of curly brackets to the name of the token directly to the right in the scale.
In our example we want to create the "xs" step on the scale which should be 1 step smaller than "sm".
Our new token will be called scale.xs
and the raw value will be:
floor(roundTo({scale.sm} / {scale.multiplier}, 0) /{scale.factor})*{scale.factor}
And if we want to create the scale.2xs
token, it's raw value will be:
floor(roundTo({scale.xs} / {scale.multiplier}, 0) /{scale.factor})*{scale.factor}
And so on.
Step 4 - Create the "steps up"
In our example, the scale.lg
step will be one step up, or larger than our scale.md
token.
So we need to write an equation that looks at the
scale.md
token, applies thescale.multiplier
value, and rounds up to the nearest whole number according to ourscale.factor
token value.
You can copy this code snip and paste it into any text editor to modify your token names if needed:
ceil(roundTo({scale.md} / {scale.multiplier}, 0) /{scale.factor})*{scale.factor}
This becomes the raw value of our scale.lg
token, which has a resolved value of 24
.
So we write the equation using the "ceil" and "roundTo" functions for all steps on the scale smaller than the "mid-point".
Create additional steps up
To create the next step up, we will use the same math equation, but we will swap out the name of the token we are referencing in the first set of curly brackets to the name of the token directly to the left in the scale.
In our example we want to create the "xl" step on the scale which should be 1 step smaller than "lg".
Our new token will be called scale.xl
and the raw value will be:
ceil(roundTo({scale.lg} / {scale.multiplier}, 0) /{scale.factor})*{scale.factor}
And if we want to create the scale.2xs
token, it's raw value will be:
ceil(roundTo({scale.xl} / {scale.multiplier}, 0) /{scale.factor})*{scale.factor}
And so on.
Adjusting the scale
Looking at my resolved values, I notice that the sizes larger than my midpoint get really big, really fast. This might be ok if I am only using the scale for marketing material, but to use on digital designs suited for smaller devices, this might not have the flexibility I need.
So I want to adjust the value of my scale.modifer
token to be something a bit smaller. In my example, I'll switch the value from the golden ratio 1.33
to something closer to the major second of "1.125", 1.2
looks good to me!
By changing the value a single token, you can see the entire scale changes!
If you are into multi-dimensional theming, you could create a themes for different device sizes and adjust the scale.base
or scale.multiplier
tokens depending on the needs of your system.
What about rem units?
Great question!
The scale we just made is technically unitless, and we created it from other
tokens, which might not be the most useful to us all the time.
So let's add a token group to the naming convention of the tokens within our scale, so when we are referencing them elsewhere in our system, we know they are unitless.
Step 1 - rename scale tokens to include a group
I'm a bit fan of simple and obvious names, so I went with "unitless".
right click
on a token (I'm starting with md) and selectedit
Edit the name to read
scale.unitless.md
Repeat for each token in your scale.
If you are comfortable editing code feel free to use the JSON editor in the plugin!
Step 2 - duplicate the group
To save us from having to create new tokens for our rem version of the scale, we can duplicate the unitless token group, which will duplicate all the tokens in our scale.
right click
on the token group name; mine is "unitless"select
duplicate
give the new group an awesome name in the top input; mine is "rem"
the bottom input is the name of the token set where you want the duplicate to end up. I want mine in the same token set, so I don't change anything
select the
duplicate
button to confirm
Step 3 - edit token values with new equation
For this equation, we want to produce the rem value of the unitless token with the same name.
Starting with my scale.rem.md
token, I will replace the existing value of each token in the new scale to have this equation:
{scale.unitless.md} / {rem-base} *1rem
And the resolved value will be 1rem
The new value of my scale.rem.4xl
token had a unitless value of 48
{scale.unitless.4xl} / {rem-base} *1rem
Now has a resolved value of 3rem
Steps to edit token values
right click
on a token and selectedit
Select the current value and delete it
Paste the new equation in the value, adjusting for the token name in the first set of curly brackets.
Repeat for each token in your scale.
Using the scale tokens
I'm ready to create some typography design decisions which reference my scale tokens.
When I go to create a fontSize
or even a dimension
token, my scale tokens might not appear in the dropdown menu.
This is because they are different token types and Tokens Studio does not yet support showing all compatible token types in the dropdown menu, but they still work!
Forced reference tokens work
I can take the name of the scale token I want to reference and copy its name
right click
on the scale token and selectcopy token path
scale.rem.md
Then paste it between curly brackets in the new fontSize
token value {scale.rem.md}
I might even see an error message telling me it's not found, I'll ignore that and press the create
button
Voila!
I have a fontSize token that is referencing my rem-scale token!
Note on variables in Figma
Today Tokens Studio can only map select token types into variables in Figma. So if that is important to you, here's a little cheat sheet on what works today.
Number variables
The obvious choice given we just built a numeric scale.
You won't see the equations, only ever the resolved values
other
tokens won't be exported to Figmathis is why I chose to build the scale with other tokens, they are meant to be a tool for me as a design systems lead to use to make decisions that are referenced in other tokens (pun intended)
dimension
tokens will be exported to Figmathey require a unit, so if you are using
rem values
stick withdimension
tokens because the plugin converts them to the correctpixel
values in Figma!borderWidth
borderRaius
sizing
spacing
follow the same logic and work fine
number
tokens export to Figmathey ignore the unit and only pass the numeric value before it
so this is fine if you are intending to use unitless numbers or pixels but your
3rem
token will pass the value of3
to a number variable instead of48
fontSize
lineHeight
letterSpacing
andtypography
token types won't export to Figma yet, so if you are prepping for typography variables, you can create those asdimension
tokens and force reference them in yourtypography
tokens for now.
Color variables
color
tokens export to Figma
String variables (text)
text
tokens export to Figmanested
text
tokens also work which is awesome
Boolean variables
boolean
tokens export to FigmaThis only controls layer visibility, not component properties, as that's how variables work today.
Hope you found this helpful! Feel free to reach out if you have questions about the community file or this guide.
🫶 https://bento.me/samiam