Retrieving and working with the tokens
Last updated
Was this helpful?
Last updated
Was this helpful?
SDTFClient
The is a class providing numerous methods to work with the design data stored using the Specify Design Token Format (SDTF).
It's the way of interacting with your tokens and generating content.
You would want to grab the SDTF JSON token tree of a repository anytime you want to: send the token tree over the network or debug an intermediate manipulation.
Now that we know how to retrieve our SDTF, let's see how we can manipulate and retrieve our tokens.
There's multiple way to retrieve a token. If you want to work with a specific one you'll probably want to only get one, if you're developing a generic solution, you'll probably prefer iterating over many tokens. Let's have a look on how to do this.
To retrieve a single token, you can just pick it by its path:
If you need specifically some tokens, for example, based on the name, type, path, etc... You can perform a query and map over the results:
Or, you can only iterate over the results of your query if you need to perform mutations for example:
The TokenState
instance will be your main access to your tokens. Through this, you can retrieve your token data or perform updates. But first, let’s put some context and try to figure out what this TokenState
is trying to solve.
As you can see, there’s a lot of way to express a token, and dealing with all the possible cases is quite difficult. That’s why one of the goal of the TokenState
is to help you to deal with everything.
Before even thinking about retrieving the value of a token, we need to know what type of token we’re working with.
It’s actually quite easy to know as there’s a type
property in the TokenState
:
But even if we know the type, a problem will remain: Typescript doesn’t. TokenState
is quite a generic class as the default type is something that looks like this: TokenState<'dimension' | 'font' | 'breakpoint' | ...>
.
So the first mission is to be able to narrow down the type to be able to work with it. To do so, there’s a pretty convenient function:
You can match as much type as you need, and if some are not matched, they’ll be caught by the callback in the 2nd argument.
If you're working with Typescript, you may face some type issues while using matchByType
. A common example would be this:
If you copy/paste this code inside your editor, Typescript will complain and gives you the following error: Type '(_: TokenState<"breakpoint", Value, Mode>) => string' is not assignable to type '(token: TokenState<"breakpoint", Value, Mode>) => number | undefined'
.
Basically Typescript is unhappy that we return a number
and a string
. To fix the issue, there's 2 solutions:
Return the same type:
Excplicitely set the return type:
Depending on the use case, you'll prefer the first over the second solution, or the second over the first.
Now that we know how to get the TokenState
that we want, we want to extract the value from it. As mentioned before, a token can have a lot of places that can contains an alias, so when retrieving the value, there’s 2 ways of doing it.
If you only want to get the raw value of a token and ignore the aliases, you can use the following function:
Also, as combining matchByType
and getJSONValue
can become a bit heavy. For that case, you can use the following function:
If you want to support the aliases in your output, you’ll need to handle them when they are some at the top level, mode level, or value level. To do so, we provide the Stateful Value API that helps you to make sure you handle all the possible cases. Let’s see an example and then break down everything.
Although this API is a bit verbose, it’ll make sure that you cannot miss a case. So, what’s happening?
When you retrieve the value of a token, we put it inside a StatefulResult
, and it’ll expose the following type union at each level:
Top level: ResolvableTopLevelAlias | UnresolvableTopLevelAlias | TopLevelValue
Mode level: ResolvableModeLevelAlias | UnresolvableModeLevelAlias | RawValueSignature
Value level: ResolvableValueLevelAlias | UnresolvableValueLevelAlias | string | number | ...
Each mapping function is dedicated to handling one case.
First, you’ll need to handle the top level:
Note the unwrap
at the end which is the way to extract the value.
Then you’ll want to handle the mode level:
All you need to do for the mode level is to return a value, and it'll be mapped to the right mode.
And finally the value level (we take a dimension token as an example):
If you call mapPrimitiveValue
, it needs to be the first one to be called in order to avoid remapping results produced by mapUnresolvableValueLevelAlias
or mapResolvableValueLevelAlias
.
As you can see each step is always the same: handling unresolvable alias, resolvable alias and the value.
Sometimes, you need to use the precision of the stateful result API, but don't want to deal with the aliases at some levels. To avoid doing so, you can resolve an alias by calling resolveDeepValue
, and you'll be left with handling the unresolvable alias and value case. Here is an example:
Although we could get rid of the resolvable alias case, handling the unresolvable case might still be too verbose. So let me introduce you to a nice pattern to only deal with values:
We will check if the token is fully resolvable
We'll use resolveDeepValue
to remove the need of handling the resolvable aliases
We'll use unwrapValue
to remove the need of handling the unresolvable case at the type level
Thanks to this pattern, we went from a really verbose API to something lighter.
Looking for details about the query? 👉 heads up to the concept.
Looking for more methods to retrieve tokens? 👉 heads up to the reference.
If we take a look at , it’s basically a JSON value where a lot of keys can be an alias rather than a value. Let’s have a look at all the possible cases with an example. Note that the following examples are 4 different ways of achieving the same result from a value point of view.