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.
- You’ll need to ensure that the CSS custom property
--some_token
has a value and is present on each page such thatvar(--some_token)
is later resolved. You could opt to not use a CSS custom property here if the value don’t change. However, if you have or plan to have dark mode in your system, then you don’t want to hardcode the values here. - You’ll need to be using CSS modules to get the JS import working, using the
:export
pseudo selector. Most JS frameworks support CSS modules with minimal configuration. The wordmodule
in the file name is required for the CSS Modules processing to pick this up as a CSS Module, so it can be imported into.js
files. - You’ll need to be using SCSS to get the
@use
and$
variable syntax working in your.scss
files. The leading_
in the file name_tokens.module.scss
is required for the@use
syntax to work. This will expose atokens
key in the SCSS based on the file name. In other words, to get thetokens.
syntax in your files, your file name should start with_tokens.
.
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.
- The dot delimiter (
.
) will not be valid in.scss
files. - The hyphen delimiter (
-
) will not be valid in.js
files.
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: