As a starting point for building a test project, we used a Next.js Typescript template. This boilerplate implements core principles of JAMstack and allows to quickly create serverless applications. The project has a standard file structure. We placed design tokens and build.js file (that runs Style Dictionary) into styles directory.
.
├── src
│ ├── styles
│ │ ├── script
│ │ │ └── build.js
│ │ │ └── fns.js
│ │ ├── tokens
│ │ │ └── input
│ │ │ │ └── base
│ │ │ │ │ └── global.json
│ │ │ │ └── themes
│ │ │ │ │ └── dark
│ │ │ │ │ │ └── dark.json
│ │ │ │ │ └── light
│ │ │ │ │ │ └── light.json
│ │ │ └── output
│ │ │ │ │ └── dark.json
│ │ │ │ │ └── global.json
│ │ │ │ │ └── light.json
│ │ ├── dark.css
│ │ ├── global.css
│ │ ├── index.css
│ │ ├── light.css
The Figma project is synchronized with the repository, and when the designer pushes any changes, that triggers a pipeline, which transforms raw data into CSS variables.
Design Token files must go through several transformation steps to return valid style sheets. The token-transformer utility replaces the references with calculated values so that the JSON object conforms to the Style Dictionary standards. The --expandTypography option can be used to convert every font-related property into an individual object.
// package.json
"scripts": {
"transform-tokens-dark": "npx token-transformer src/styles/tokens/input/themes/dark src/styles/tokens/output/dark.json",
"transform-tokens-global": "npx token-transformer src/styles/tokens/input/base src/styles/tokens/output/global.json --expandTypography=true",
"transform-tokens-light": "npx token-transformer src/styles/tokens/input/themes/light src/styles/tokens/output/light.json",
"transform-tokens": "yarn transform-tokens-dark && yarn transform-tokens-global && yarn transform-tokens-light",
"tokens": "node src/styles/script/build.js"
}
yarn transform-tokens
yarn tokens
Style Dictionary allows us to define functions and then to modify input values. For example, you can specify the letter-spacing property in ems or turn color from Hex color-space into HSL color-space.
// build.js
function transformLetterSpacing(value) {
if (value.endsWith('%')) {
const percentValue = value.slice(0, -1);
return `${percentValue / 100}em`;
}
return value;
}
StyleDictionaryPackage.registerTransform({
name: 'size/letterspacing',
type: 'value',
transitive: true,
matcher: (token) => token.type === 'letterSpacing',
transformer: (token) =>
transformLetterSpacing(token.value)
});
The transform function can combine the number of parameters that define the appearance of a box-shadow into a CSS variable. The resulting value can be used in the theme configuration file after installing the tailwindcss-box-shadow plugin.
StyleDictionaryPackage.registerTransform({
name: "shadow/css",
type: "value",
transitive: true,
matcher: (token) => token.type === "boxShadow",
transformer: (token) => {
const shadow = Array.isArray(token.value) ? token.value : [token.value];
const value = shadow.map((s) => {
const { x, y, blur, spread, color } = s;
return `${x}px ${y}px ${blur}px ${spread}px ${tinycolor(color).toHslString()}`;
});
return value.join(", ");
},
});
Finally, during the building process, a set of CSS variables will be created. Style Dictionary will add selectors to style sheets as a combination of a :root pseudo-class and a themed class. These can be used later to swap from light to dark mode.
// build.js
files: [
{
destination: `${theme}.css`,
format: 'css/variables',
selector: `:root.${theme}`
}
]
The Tailwind configuration file stores the object that represents the current theme. And Design Tokens can be attached to component styles through CSS variables. Thus, we can update the external presentation of the User Interface by changing the values of the tailwind.config.js.
// tailwind.config.js
theme: {
colors: {
primary: {
DEFAULT: 'var(--color-primary-default)'
}
}
}
To maintain consistency in UI design, we created a ThemeProvider component and placed it at the top-level of the React tree. That wrapper uses the Context API to pass the current theme data down to child components.
// App.tsx
export const App = () => (
<ThemeContextWrapper>
{/* children */}
</ThemeContextWrapper>
);
Top comments (0)