bezel-kit
Turns a W3C Design Tokens (DTCG) file into a scoped variables.css, with one CSS scope per context and token references resolved to literal values. This is the package that gets your tokens into a build.
npm install --save-dev @bezel-labs/bezel-kit
CLI
Two commands. Most projects only ever use these.
bezel init # create bezel.json, with defaults inferred from your project
bezel build # generate the outputs
init picks src/bezel/ when you have a src/ directory (otherwise bezel/), and only scaffolds the TypeScript modules if you have a tsconfig.json. It won't overwrite an existing bezel.json without --force.
Two flags set the MCP fields: bezel init --project <uuid> writes projectId, and bezel init --tokens-version <v> writes version. On an existing bezel.json each updates only its own key, no --force needed.
build reads design-tokens.json from the project root (the name and location are fixed), picks up bezel.json automatically, writes your outputs, and adds them to .gitignore in a managed block.
Full flag list and the recommended package.json wiring are in the Quick Start.
Config
{
"projectId": "0b5c3d1e-7f2a-4c9b-8e6d-1a2b3c4d5e6f",
"version": "latest",
"variablesOutput": "src/bezel/variables.css",
"contextsOutput": "src/bezel/contexts.ts",
"fontsOutput": "src/bezel/fonts.ts"
}
Only variablesOutput is required. The other two output paths emit small TypeScript modules exporting CONTEXTS and FONTS, derived from your tokens. Useful for theme switchers and for handing fonts to loupe.
projectId (optional) is the Bezel project this repo tracks, and version (recommended) is which tokens the MCP server pulls: latest for your current working tokens, or a published semver to pin a release. Both are read only by the MCP; build ignores them.
Also accepts colorFormat (oklch | hex), dimensionUnit (preserve | rem), cwd, write, gitignore, and nameExtension.
API
The core is isomorphic, with no node:* imports, so it runs in the browser and on the edge as well as in Node.
import { tokensToCss } from '@bezel-labs/bezel-kit';
const css = tokensToCss(tokens);
const hex = tokensToCss(tokens, { colorFormat: 'hex' });
Core exports: tokensToCss, emitCss, resolveCssOptions, getContexts, formatContextsModule, getFonts, formatFontsModule, plus DEFAULT_CONTEXTS and DEFAULT_NAME_EXTENSION.
The Node entry adds the filesystem conveniences:
import { generateVariablesCss, initConfig } from '@bezel-labs/bezel-kit/node';
await generateVariablesCss(); // reads ./design-tokens.json, writes the configured output
await initConfig(); // writes bezel.json
How names are chosen
Worth knowing, because it decides what ends up in your CSS.
If any token declares an export name, the whole file switches to export-name mode: a token with exportName: ["primary"] becomes --primary, and a token without one emits nothing; it exists purely as a reference target. One token can emit several names.
If no token declares one, names come from the token path with base/semantic prefixes and default/$root suffixes trimmed, so semantic.color.primary.default becomes --color-primary.
Output
Each context becomes its own scope: the base context in full, others as override-only blocks:
:root {
--primary: oklch(0.8 0.18 151.7);
--foreground: oklch(0.31 0.082 298.1);
}
.dark {
--primary: oklch(0.75 0.12 200);
}