Skip to main contentSkip to docs navigation

Tokens Studio

How Chassis Tokens arranges its token sets and groups in Tokens Studio, how to connect the plugin to the repository, and how to add a brand.

This page is a work in progress. Parts of it may be incomplete or differ from the current version of Chassis Tokens.

Introduction

Chassis Tokens keeps its design tokens as Tokens Studio files in packages/tokens/source/. Tokens Studio is a Figma plugin that edits these files, syncs them with the Git repository, and exports them to Figma. The build reads the same files, so Figma and the code of every platform get their values from one place.

This guide connects the plugin to the repository, describes the token sets and the groups that select them, and adds a brand.

Prerequisites

The plugin needs access to Figma and to the repository, and the build needs Node.js:

  • The Tokens Studio plugin from the Figma Community, with a Pro license. Chassis Tokens stores its token sets in several files and selects them with the themes of Tokens Studio, and both features belong to the Pro plan.
  • A Figma account with edit access to the file in which you run the plugin.
  • A clone or a fork of the Chassis Tokens repository on GitHub, and a personal access token of GitHub that can read it. To push changes from the plugin, the token needs write access.
  • Node.js 22 or later and pnpm, to lint and build the tokens after a change.

Connect the repository

Tokens Studio reads and writes the token sets through a sync provider, which names the repository, the branch, and the folder of the token sets.

  1. Open Tokens Studio in a Figma file and go to Settings.
  2. Click Add new sync provider and choose GitHub.
  3. Enter a name for the provider and your personal access token.
  4. Enter the repository, chassis-ui/tokens or <owner>/<repository> for your fork.
  5. Enter the branch, main or the branch you work on.
  6. Enter the file path, packages/tokens/source.
  7. Save the provider, return to the Tokens tab, and pull the tokens from the repository.
  8. Select one option of each group: a brand, a theme, an app, and a screen.

The file path names a folder, so Tokens Studio keeps every token set in a file of its own. The remote storage docs of Tokens Studio describe the other providers and their settings.

Token sets

A token set is one JSON file of packages/tokens/source/. Its name is its path without the extension, such as base/brand-base. The folder base holds the sets that every brand shares, a folder brand-<brand> holds the sets of one brand, and screen-website holds the sets of the screens. The committed options select these sets:

Token setTokens
base/metric-sourcedimension.base.*, the scale that the sizes reference, and modify.*, the shares of the color modifiers
base/metric-basesize.unit.*, space.unit.*, opacity.level.*, opacity.context.*
base/brand-basecolor.base.*, typography.fontFamily.*, typography.fontWeight.*, typography.fontSize.*, typography.lineHeight.*, borderRadius.base.*
base/brand-defaultNo token except the switch of the default brand
brand-<brand>/brand-baseThe tokens of base/brand-base that the brand changes
base/theme-basecolor.primitive.*, color.context.*, color.shadow.*, color.utility.*, gradient.primitive.*
base/theme-lightNo token except the switch of the light theme
base/theme-darkThe colors of base/theme-base, with the references of the dark theme
base/effect-baseshadow.elevation.*, shadow.glow.*, bg-blur.*
base/app-basefont.*, grid.*, icon.*, the component colors, the context and component tokens of size, space, borderRadius, borderWidth, and shadow, and typography.letterSpacing.*, typography.paragraphSpacing.*, typography.textCase.*, typography.textDecoration.*
base/app-docsNo token except the switch of the docs app
base/app-democolor.page.bg-body, color.page.bg-section
screen-website/screen-basefont.website.*
screen-website/screen-<screen>typography.fontSize.website.*, space.website.*, size.website.*, figma.website.*

The committed brand sets are brand-chassis/brand-base, brand-sinefil/brand-base, brand-demo-a/brand-base, and brand-demo-b/brand-base. The switches are the boolean tokens figma.switch.<group>.*, which show and hide layers in Figma; the Figma Variables guide lists them.

Set order

When two selected token sets declare the same token, the set that comes later overrides the earlier one. In Tokens Studio, the order is tokenSetOrder of packages/tokens/source/$metadata.json. The build lists the sets of the selected options in the order of $themes.json. For the brand chassis, the app docs, the theme light, and the screen large, the list is:

TEXT
base/metric-source
base/metric-base
base/brand-base
brand-chassis/brand-base
base/theme-base
base/app-base
base/app-docs
base/effect-base
base/theme-light
screen-website/screen-base
screen-website/screen-large

base/brand-base declares color.base.primitive.light.primary.base, and brand-chassis/brand-base declares it again. The brand set comes later, so the token has the value of the brand set. Style Dictionary logs these overrides as token collisions in every build; they are expected.

A set only has to declare the tokens that it changes. brand-chassis/brand-base declares the base token of a palette color, and the steps of the color stay in base/brand-base, where they reference the base token.

Set status

An option gives each token set that it selects one of two statuses in selectedTokenSets of $themes.json. A set that the option does not list is disabled for that option.

StatusIn Tokens StudioIn the build
enabledThe tokens resolve references and become Variables and styles of the group in FigmaThe set is built
sourceThe tokens resolve references and create nothing in FigmaThe set is built

The build does not tell the two statuses apart. base/metric-source is a source set in every option, and the generated files still hold dimension.base.*. The token sets docs of Tokens Studio describe the statuses in the plugin.

Selected sets

The build and pnpm tokens:lint:source read the token sets that an option of $themes.json selects, with either status. A set that no option selects takes no part in a design or in the output until you add it to an option.

The folders hold such sets next to the selected ones: base/metric-rem, base/motion-base, and the app-* and metric-* sets of the brand folders, such as brand-chassis/app-base.

Groups

A group is a list of options, and an option is a list of token sets with their status. Tokens Studio calls them theme groups and theme options, and keeps them in packages/tokens/source/$themes.json. You select one option of each group, and the selected options together select the token sets of a design or of a build.

The token system is built on three core groups, brand, theme, and app. screen is an optional fourth group.

GroupIts options selectCommitted options
brandThe base tokens of a brand: palette, fonts, and border radiusdefault, chassis, sinefil, demo-a, demo-b
themeThe colors and shadows of a themelight, dark
appThe context and component tokens of an appdocs, demo
screen, optionalThe tokens that differ per screen sizelarge, medium, small

The options are the committed ones, and you can add your own. The build writes files for the names in chassis.build of packages/tokens/package.json. The committed configuration names the brands chassis and sinefil, and every committed theme, app, and screen; the brands default, demo-a, and demo-b are options in Tokens Studio only.

Brand group

The options of the brand group select the tokens that make up a brand. Every committed option selects the same sets and adds its brand set as the last one:

Token setStatus
base/metric-sourcesource
base/metric-basesource
base/brand-baseenabled
brand-<brand>/brand-baseenabled

The option default adds base/brand-default instead of a brand set, so it has the values of base/brand-base.

Theme group

The options of the theme group select the colors of a theme, and the shadows of base/effect-base.

Token setStatus
base/metric-sourcesource
base/metric-basesource
base/effect-baseenabled
base/brand-basesource
base/theme-baseenabled
base/theme-<theme>enabled

base/theme-base holds the references of the light theme: color.context.primary.bg-hover references {color.base.context.light.primary.bg-hover} there. base/theme-dark overrides the token with {color.base.context.dark.primary.bg-hover}. The options of the group must declare the same names, which pnpm tokens:lint:source checks.

App group

The options of the app group select the context and component tokens, which are the tokens that a design or an app uses. base/metric-base is enabled here, so its sizes, spaces, and opacities become Variables of this group.

Token setStatus
base/metric-sourcesource
base/metric-baseenabled
base/brand-basesource
base/theme-basesource
base/app-baseenabled
base/app-<app>enabled

The set of an app overrides the component tokens that the app changes. base/app-demo overrides color.page.bg-body and color.page.bg-section of base/app-base, and base/app-docs overrides none.

Screen group

The options of the optional screen group select the tokens whose value depends on the screen size. In the committed source they are the website tokens, which the Chassis website uses.

Token setStatus
base/app-basesource
base/brand-basesource
base/metric-basesource
base/metric-sourcesource
base/effect-basesource
base/theme-basesource
screen-website/screen-baseenabled
screen-website/screen-<screen>enabled

screen-website/screen-base holds the composite tokens font.website.*, and the set of each screen holds the tokens that they reference, typography.fontSize.website.*, next to space.website.* and size.website.*. The options of the group must declare the same names: pnpm tokens:lint:source fails when the set of one screen lacks a token of the others.

Change the groups

Chassis Tokens is meant to be owned and customized, so the groups are yours to change. The build makes the name of every list of token sets from one option of each group, as <brand>_<app>_<theme>_<screen>, and expects the groups in this order in $themes.json.

To remove the screen group:

  1. Remove the options of the screen group from $themes.json.
  2. Set chassis.build.screens to [], or remove the key, in packages/tokens/package.json.
  3. Move the tokens of the screen sets that you still need to a set of another group, such as base/app-base.

The build then names the lists <brand>_<app>_<theme> and writes one number file without the name of a screen: number.scss, Number.swift, and number.xml.

In Tokens Studio, a group of your own is a group like the committed ones. The build needs a change: planBuilds in packages/tokens/build/build.js makes the name of a list from the brand, the app, the theme, and the screen, and chassis.build has no key for another group. Until you change both, the build stops with No token sets for …, because the option of the new group is a part of every list name.

Brand tokens

The brand sets hold the base tokens of color, typography, and border radius. Designs and apps do not use these tokens directly; the tokens of the theme and app groups reference them, so a change of the brand reaches every token that follows from it.

Color tokens

color.base.* holds the colors of both themes, so that the option of the theme group only has to choose between them.

TokensContent
color.base.primitive.<theme>.*The palette: black, white, primary, secondary, neutral, danger, success, warning, info, brand, accent
color.base.context.<theme>.*The colors by role, which reference the palette
color.base.shadow.<theme>.*The shadow colors, which reference the transparent steps of the palette

Every step of a palette color follows from its base token. The steps 05 to 40 lighten the base color and the steps 60 to 95 darken it, with a color modifier of Tokens Studio whose share is a token of modify.*. The step 50 is the base color, and the steps t-5005 to t-5095 give it an opacity of opacity.level.*. The colors black and white have the steps with an opacity only, t-05 to t-95. The step 40 of primary in base/brand-base:

JSON
{
  "40": {
    "$extensions": {
      "studio.tokens": {
        "modify": {
          "type": "lighten",
          "value": "{modify.lighten.40}",
          "space": "srgb"
        }
      }
    },
    "$type": "color",
    "$value": "{color.base.primitive.light.primary.base}"
  }
}

A brand set declares the base tokens of the light palette, such as color.base.primitive.light.primary.base, each with a color of the brand. The dark palette references the light one: color.base.primitive.dark.primary.base is {color.base.primitive.light.primary.base}.

Colors with an opacity are in the brand sets too. Figma cannot change the opacity of a Variable that is an alias, so color.base.context.light.primary.fg-subtle holds the complete color, and color.context.primary.fg-subtle only references it. See the color modifier docs of Tokens Studio and the color tokens reference.

Typography tokens

The brand sets hold the four typography groups that the composite font.* tokens of base/app-base reference.

TokensContent
typography.fontFamily.*The families text, display, html, code, and icon
typography.fontWeight.*The weights of each family, such as normal, strong, mass, and elegant of text
typography.fontSize.*The sizes of each family, from 2xsmall to 5xlarge for text
typography.lineHeight.*The line height of each size

The brand sets of chassis and sinefil declare the font families and the font weights, and take the font sizes and the line heights of base/brand-base. A font family token holds a list of font families, with the font of the brand first and its fallbacks after it.

A font weight token holds the style name that the font has in Figma, and fonts spell the same weight in different ways, with or without a space between the words. The build reads every spelling as the same weight. Keep the spelling of the font, or Figma does not find the style.

The remaining typography groups (typography.letterSpacing.*, typography.paragraphSpacing.*, typography.textCase.*, typography.textDecoration.*) are in base/app-base, the same for every brand. See the typography tokens reference.

Border radius tokens

borderRadius.base.* holds a scale, borderRadius.base.context.*, and the radius of each component that has one, such as borderRadius.base.button.*. The tokens of base/app-base reference them one to one: borderRadius.button.medium is {borderRadius.base.button.medium}.

A brand set overrides the scale, the components, or both. The steps of the scale reference dimension.base.*, and the components reference a step of the scale. See the border radius tokens reference.

Token names

A token name is a dot path. The first segment is the category, the second is a level or a component, and the rest names the token in its group.

SegmentContentExamples
FirstThe categorycolor, size, space, font, typography, borderRadius, borderWidth, shadow, opacity
SecondA level, or a componentbase, unit, context; button, form-input
OthersThe variant, the state, or the partprimary, bg-hover, medium-padding-x

Some component groups have a segment for the variant or the state, and the last segment names the part and its state:

TEXT
color.button.primary.bg-hover       <category>.<component>.<variant>.<part>-<state>
color.form-input.focus.border       <category>.<component>.<state>.<part>

Other groups join the size and the part in the last segment:

TEXT
size.button.medium-icon             <category>.<component>.<size>-<part>
space.button.medium-padding-x       <category>.<component>.<size>-<part>

The build joins the segments to the name of each platform: color.button.primary.bg-hover is $cx-color-button-primary-bg-hover on the web, ColorButtonPrimaryBgHover on iOS, and color_button_primary_bg_hover on Android. A segment holds letters, digits, and single hyphens, and a name starts with a letter. pnpm tokens:lint:source fails on other characters and on two names that become the same platform name.

Token names are the public API of the package. Renaming or removing a token breaks every app that uses it.

Reference chains

A component token reaches its value through the token sets of the three core groups. Each line shows a token, its token set, and what the source holds for it, with the theme light. Every chain ends at a value of the brand.

The background of the primary button in its hover state:

TEXT
color.button.primary.bg-hover                base/app-base               {color.context.primary.bg-hover}
color.context.primary.bg-hover               base/theme-base             {color.base.context.light.primary.bg-hover}
color.base.context.light.primary.bg-hover    base/brand-base             {color.base.primitive.light.primary.40}
color.base.primitive.light.primary.40        base/brand-base             {color.base.primitive.light.primary.base}, lightened
color.base.primitive.light.primary.base      brand-<brand>/brand-base    A color of the brand

Another brand changes the last line only. The theme dark changes the second line to {color.base.context.dark.primary.bg-hover} of base/theme-dark, and the chain continues in the dark palette.

A color with an opacity takes both parts in base/brand-base:

TEXT
color.context.primary.fg-subtle               base/theme-base     {color.base.context.light.primary.fg-subtle}
color.base.context.light.primary.fg-subtle    base/brand-base     rgba({color.base.context.light.primary.fg-main}, {opacity.context.fg-subtle})
color.base.context.light.primary.fg-main      base/brand-base     {color.base.primitive.light.primary.60}
opacity.context.fg-subtle                     base/metric-base    A number from 0 to 1

The font of the medium button references a composite token, whose properties reference the typography tokens:

TEXT
font.button.medium         base/app-base   {font.text.medium.strong}
font.text.medium.strong    base/app-base
  fontFamily               {typography.fontFamily.text}              brand-<brand>/brand-base
  fontWeight               {typography.fontWeight.text.strong}       brand-<brand>/brand-base
  lineHeight               {typography.lineHeight.text.medium}       base/brand-base
  fontSize                 {typography.fontSize.text.medium}         base/brand-base
  letterSpacing            {typography.letterSpacing.base.zero}      base/app-base
  paragraphSpacing         {typography.paragraphSpacing.base.zero}   base/app-base
  textCase                 {typography.textCase.base.none}           base/app-base
  textDecoration           {typography.textDecoration.base.none}     base/app-base

The font family and the font weight are values of the brand. The font size and the line height are values of base/brand-base, which a brand set can override.

Add a brand

A brand needs a token set, an option of the brand group, and its name in the build configuration. You can make the first two in Tokens Studio or in the JSON files. Tokens Studio writes $metadata.json and $themes.json when it pushes; if you edit the JSON by hand, change them too.

The token set of a brand declares the tokens of base/brand-base that the brand changes, with the names that they have there:

TokensHolds
color.base.primitive.light.<color>.baseThe base color of a palette color
typography.fontFamily.*A list of font families
typography.fontWeight.*The style name of a weight, as the font spells it in Figma
borderRadius.base.context.*A reference to a step of dimension.base.*
borderRadius.base.<component>.*A reference to a step of borderRadius.base.context.*

With the token set written, the steps are:

  1. Add the token set as brand-<brand>/brand-base, which is the file packages/tokens/source/brand-<brand>/brand-base.json, and add its name to tokenSetOrder of $metadata.json, after base/brand-base.

  2. Add an option to the brand group with the token sets of the brand group. The option sinefil in $themes.json, without its Figma references:

    JSON
    {
      "id": "eb50659f7aae0533f750bbb36334a0c1fecc93c3",
      "name": "sinefil",
      "selectedTokenSets": {
        "base/metric-source": "source",
        "base/metric-base": "source",
        "base/brand-base": "enabled",
        "brand-sinefil/brand-base": "enabled"
      },
      "group": "brand"
    }
  3. Add the name of the option to chassis.build.brands in packages/tokens/package.json:

    JSON
    "chassis": {
      "build": {
        "brands": ["chassis", "sinefil", "<brand>"]
      }
    }
  4. Lint the source, build, and review the changes of the output:

    Shell
    pnpm tokens:lint:source
    pnpm tokens
    pnpm tokens:diff

    With the configured apps, the build writes dist/web/docs/<brand>/, dist/ios/demo/<brand>/, and dist/android/demo/<brand>/.

  5. Write the Swift package manifest again, which has one library for every app and brand with an iOS platform:

    Shell
    pnpm tokens:swift-package

    pnpm tokens:test fails while Package.swift differs from the configuration. The Android libraries need no such step.

An app or a screen follows the same steps with its own group and with chassis.build.apps or chassis.build.screens. A screen of an app with the android platform also needs a resource folder in chassis.build.options.android.screens. The tokens of a new component need no set and no option: add them to base/app-base, which every committed app enables. Changing tokens in the contributing guide lists what a pull request with a token change needs.

Sync to Figma

Tokens Studio exports every group as a Variable Collection and every option as a Mode. The tokens of the enabled sets become Variables, and the composite tokens, such as font.* and shadow.*, become styles. $themes.json records the Figma identifiers of all of them for each option, and they make up most of the file.

The Figma Variables guide covers the export, the Collections, and the publishing of the library.

Troubleshooting

The lint and the build name the token set and the token of a mistake in the source. Run pnpm tokens:lint:source after every pull or edit, before the build.

No token sets

The build stops with No token sets for <brand>_docs_light_large in source/$themes.json. chassis.build names a brand, an app, a theme, or a screen that is not an option of its group in $themes.json. Add the option, or write the name in packages/tokens/package.json as the option spells it.

Token missing in a screen

The lint reports <token> is declared by screen "small", not by screen "large", "medium" [same-names]. The options of the screen group, and those of the theme group, must declare the same names in their enabled sets. Add the token to the other sets, or fix the name that differs.

Unknown font weight

The lint reports <token> "<weight>" is not a font weight the build knows [font-weight]. A font weight token holds the name of a weight, with or without a space between its words, and a style word such as Italic may follow it. Use the style name of the font in Figma, and check its spelling.

Value is not a token

The lint reports <token>.value is not a token: it has no $value [token-type]. The token set holds value and type without $, the format before DTCG. Write $value and $type in the set, or remove the set from the option that selects it.

Next steps