Design Systems Hot Takes

Interoperable tokens

3 min read

In my design system playground, one of my small goals was to have the ability to import a design token identically between CSS and JavaScript. Here’s an example of the developer experience I was expecting and succeed in making: to use tokens.$action_primary_backgroundColor as a value reference in both .scss and .js files identically.

Writing (S)CSS.

@use '../tokens.module';

button:where([data-priority="primary"]) {
    background-color: tokens.$action_primary_backgroundColor;
}

Writing JS(X).

import tokens from './_tokens.module.scss';

function PrimaryButton(props) {
  return (
    <button
        {...props}
        style={{ 
            backgroundColor: tokens.$action_primary_backgroundColor
        }}/>
  );
}

The important part is that the intention of using the background color for primary buttons is shared between the CSS and the JavaScript. tokens.$action_primary_backgroundColor is identical between these examples while being applied in different languages. This method helps reduce the context switching between writing CSS and writing JS to get the expected styling value.

Working source

I’ll cut to the chase, the following is a _tokens.module.scss file that all the components have access to. Here’s the file I use in my system. You’ll want to generate this file based on how you create and manage tokens in your system. You should expect many, many more entries in this file than what is shown below. This example is only supporting tokens.$some_token between .scss and .js files.

/** For .scss, $some_token does not need to match the --some_token  */
$some_token: var(--some_token);

/** For .js, the #{'$some_token'} needs match $some_token */
:export {
    #{'$some_token'}: $some_token;
}

Prerequisites

As you might tell, there are some requirements to this in order for the DX to work well.

Required characters

There’s lots of token naming constructions out there that delimit the sections of the token. For this to work, the underscore (_) is one of the only characters that will work between (S)CSS and JS.

When you generate the _tokens.module.scss file, you’ll want to convert any delimiter to an underscore. As an example, if you have a token button.primary.backgroundColor, you’ll want to prepare that as button_primary_backgroundColor to be used in source files. This makes the name usable between .scss and .js files in an identical way.

The leading $ is expected for the SCSS ecosystem to be used as a variable. This is carried over into the JS ecosystem for consistency.

You can review the source of my system to find additional details:

Your first component