Vanilla Extract

Generate Vanilla Extract themes from DTCG tokens.

Setup

Requires Node.js 20 or later. With that installed, run:

npm i -D @terrazzo/cli @terrazzo/plugin-css @terrazzo/plugin-vanilla-extract

Add a terrazzo.config.js to the root of your project with:

import { defineConfig } from "@terrazzo/cli";
import css from "@terrazzo/plugin-css";
import vanillaExtract from "@terrazzo/plugin-vanilla-extract";

export default defineConfig({
  plugins: [
    css({
      // Optional: control the final CSS variable names
      variableName: (id) => id.replace(/\./g, "-"),
      // Optional: pass `skipBuild: true` to not generate a .css file if only using Vanilla Extract.
      skipBuild: false,
    }),

    vanillaExtract({
      filename: "themes.css.ts",
      // Use global CSS vars (recommended). Your Vanilla Extract CSS is still scoped.
      globalThemeContract: true,

      // Option 1: scoped themes
      themes: {
        light: { mode: [".", "light"] },
        dark: { mode: [".", "dark"] },
      },

      // Option 2: global themes (in case you have code outside Vanilla Extract)
      globalThemes: {
        globalLight: { selector: "[data-color-mode=light]", mode: [".", "light"] },
        globalDark: { selector: "[data-color-mode=dark]", mode: [".", "dark"] },
      ],
    }),
  ],
});

Options

NameTypeDefaultDescription
filenamestringtheme.css.tsThe filename to generate.
globalThemeContractbooleantrueIf false, it will generate a scoped createThemeContract() instead.
themes{ [name: string]: { mode: string | string[] } }Generate one createTheme() per object key. The key is the variable name that will be exported.
globalThemes{ [name: string]: { selector: string, mode: string | string[] } }Generate one createGlobalTheme() per object key. The key is the variable name that will be exported.
themeVarNaming(name: string) => [class, vars][{name}Class, {name}]Change the naming strategy for the createTheme() API’s [class, vars] tuple.
tip

For many token setups in Vanilla Extract, you’ll usually have better results loading both the default token mode (".") and the target mode, e.g. mode: [".", "light"]. This just helps completeness and reduces type errors, and is fairly safe. Globs are NOT supported for modes—you must be explicit.

Interop with plugin-css

This plugin is a thin wrapper around plugin-css, which ensures the output is valid CSS. But since Vanilla Extract is an extension, only some settings matter:

OptionApplies to Vanilla Extract
variableNameYes (if globalThemeContract: true)
excludeYes
skipBuildYes
legacyHexYes
transformYes (if used)
modeSelectorsNo
utilityNo (use Sprinkles instead)
baseSelectorNo (overridden by Vanilla Extract themes)
warning

Though in most scenarios plugin-css agrees with Vanilla Extract (because Terrazzo manages both), some usages of modeSelectors could potentially conflict. If that happens, you may want to set globalThemeContract: false in Vanilla Extract.