Token to Tooling: Generating VS Code Snippets from Design Tokens

How to transform design tokens into useful VS Code snippets for faster, error-free coding

Eden Ella
Bits and Pieces

--

Styling using native CSS (or pre-processor languages compiled to CSS) can be great as it offers better performance than JavaScript-based solutions.

In addition, CSS is as “universal” as it gets. You won’t waste your time learning framework-specific styling solutions that might become obsolete or fall out of favor, leading to costly refactoring later on.

Having said that, some aspects of the dev experience are vastly improved when using CSS-in-JS libraries or frameworks, mainly type support and IntelliSense.

One place where CSS is lacking the most is when using CSS vars (or custom props) to theme your UI components. In this scenario, the CSS props are often not included in the CSS file used to style the components but are instead defined elsewhere. This can become problematic when working with teams or large codebases, as the lack of direct visibility of variable definitions can lead to confusion and inconsistency.

To help solve this issue, we can use various tools such as VSCode extensions, ESLint plugins, and a number of other solutions. However, I’d like to offer another tool to the mix: generating VS Code Snippets based on the design tokens or theme specifications your team uses.

These code snippets are a great way to improve our dev experience. They can list the available CSS variables and even provide short instructions on how and when to use them.

Bit Platform for design systems

Our demo solution was built using Bit, which allows us to create shareable components, render component “previews,” generate component docs, and so on.

Our solution consists of four Bit components:

Design tokens

The ‘design tokens’ component holds the list of tokens for our design system. For example:

/** @filename: design-tokens.ts */

import { DesignTokens } from './design-tokens.js';

const defaultValues = {
primaryColorBackground: {
value: '#568cb0',
description:
'Use this color as the background for key UI elements that need to stand out. It should be used to promote actions or areas of importance.',
},
primaryColorText: {
value: '#ebebeb',
description:
'Use this color as the text color for key UI elements that need to stand out.',
},
};

// augment the list of tokens with different methods for value retrieval
export const defaultDesignTokens = new DesignTokens(defaultValues);

// ...

In addition, since we want all our themes to follow the same theme schema, we’ll generate a type alias from this list of tokens to be used by future themes (‘light’ and ‘dark’, etc):

/** @filename: design-tokens/design-tokens.ts */

// ...

// export the a theme schema to standardize themes
type ExtractDesignTokenValues<T> = {
[P in keyof T]: T[P] extends { value: infer V } ? V : never;
};
type DefaultValues = typeof defaultValues;
export type ThemeSchema = ExtractDesignTokenValues<DefaultValues>;
https://bit.cloud/learnbit/style-snippets/design-tokens

Theme

Our theme is generated by passing the design tokens to this theme generator:

/** @filename: theme/theme.tsx */

// this is the theme generator (use 'bit fork' to create your own copy of it)
import { createTheme } from '@teambit/base-react.theme.theme-provider';
// the design tokens created above
import { defaultDesignTokens } from '@learnbit/style-snippets.design-tokens';
// re-export schema to standardize future theme extensions
export type { ThemeSchema } from '@learnbit/style-snippets.design-tokens';

/* generate a theme using the design token default values */
export const MyTheme = createTheme({
theme: defaultDesignTokens.listValues(),
});
The theme is generated based on the design tokens given to it

Generate design token code snippets

To generate design token code snippets, I’ve created a custom reusable React development environment that will auto-generate the snippet file when a relevant Bit component is created or imported into the workspace.

// the 'design tokens' Bit component
import { defaultDesignTokens } from '@learnbit/style-snippets.design-tokens';
// a custom "file config writer" to generate a code snippet file
import { DesignTokenCodeSnippetsWriter } from './design-token-snippets-generator.js';

export class MyReactEnv extends ReactEnv {

// ...

workspaceConfig(): ConfigWriterList {
return ConfigWriterList.from([
TypescriptConfigWriter.from({
tsconfig: this.tsconfigPath,
}),
EslintConfigWriter.from({
configPath: this.eslintConfigPath,
tsconfig: this.tsconfigPath,
}),
PrettierConfigWriter.from({
configPath: this.prettierConfigPath,
}),

// generate the code snippets when creating or importing components
DesignTokenCodeSnippetsWriter.from(
// the content of the code snippets file is based on the design tokens
defaultDesignTokens.generateCodeSnippets()
),

]);
}
}

export default new MyReactEnv();
The reusable development environment is dependent on the design tokens as it uses it to generate code snippets

Note that this part can also be achieved in “hybrid projects” that have Bit initialized in them by adding a package.json script that runs to generate or update the snippet file when triggered by a relevant event.

A themeable UI component

To create new Bit components using the reusable development environment from before, we’ll add it to the workspace.json :

/** @filename: workspace.jsonc */

{
"teambit.generator/generator": {
"envs": [
"learnbit.style-snippets/dev/my-react-env",
]
},
}

Now we can use the component templates offered by that env:

bit create react my-button

A new component directory with the pre-configured files was created.

└── my-button
├── index.ts <-- component main file
├── my-button.composition.tsx <-- component "previews" (isolated renders)
├── my-button.docs.mdx <-- component documentation
├── my-button.module.scss <-- component styles
├── my-button.spec.tsx <-- component tests
└── my-button.tsx

In addition to that, since our component uses the reusable dev environment, a code snippet file was auto-generated for it at the root of the workspace:

/** @filename: .vscode/scss.code-snippets */

{
"--primary-color-background": {
"prefix": "--primary-color-background",
"body": [
"var(--primary-color-background, #568cb0)"
],
"description": "Use this color as the background for key UI elements that need to stand out. It should be used to promote actions or areas of importance."
},
"--primary-color-text": {
"prefix": "--primary-color-text",
"body": [
"var(--primary-color-text, #ebebeb)"
],
"description": "Use this color as the text color for key UI elements that need to stand out."
}
}

Now, when we create styles for our components, we’ll be able to search for the custom CSS props that we need:

To complete our UI component, we’ll create a preview for it using our theme:

/** @filename: my-button/my-button.composition.tsx */

import { MyTheme } from '@learnbit/style-snippets.my-theme';
import { MyButton } from './my-button.js';

export const BasicMyButton = () => {
return (
<MyTheme.ThemeProvider>
<MyButton>hello world!</MyButton>
</MyTheme.ThemeProvider>
);
};

If we head over to Bit UI, we can see the button’s preview:

The component rendered in isolation in Bit’s UI (‘bit start’)

Sharing components

To share our components, we’ll create a new version of them and export them to Bit Platorm:

bit tag -m "new version"
bit export

The components are built on Ripple CI, the CI system for components, offered by the Bit Platform:

The Bit component being built with Ripple CI

As you can see, Ripple CI builds each component indepndently. Once a Bit component is built, the CI propagates to its dependents. When Bit components are siblings (one is not dependent on the other) Ripple builds them in parallel, to optimize the build’s performance.

Once the build is over, the Bit components are available on Bit Cloud.

https://bit.cloud/learnbit/style-snippets

--

--