Skip to main content

Quick Start

Add Bezel to an app you already have. By the end, your tokens come from your Bezel project, compile to CSS variables on every build, and, if you want it, update live in the preview panel while you edit.

You'll need Node 22+, a Bezel project with some tokens in it, and a Pro or Enterprise plan for MCP access.

Fastest path

With the MCP server installed, ask your agent to do the setup for you:

Set up Bezel in this project.

Set up this project to use my Bezel project named Acme Design System.

Configure Bezel Kit in this repo and generate my variables.css.

Use the Bezel MCP to set up this project with my Bezel project <uuid> at version 1.4.0.

Or run /mcp__bezel__setup-project (optionally /mcp__bezel__setup-project "Acme Design System").

The agent lists your Bezel projects, you pick one, and it completes steps 1–4 below, skipping anything the repo already has. It asks before editing package.json or your entry file. The manual steps below remain the reference for what it does.

1. Install

npm install --save-dev @bezel-labs/bezel-kit

2. Pull your tokens

Bezel reads one file: design-tokens.json at the root of your project. The name and location are fixed, so put it there.

Using the MCP server

Connect your editor once. In Claude Code:

claude mcp add bezel --transport http --url https://mcp.bezel.new/mcp

Run /mcp and authenticate. Bezel uses OAuth, so there's no API key to copy around.

Then ask for the project by name:

Sync my Bezel tokens from my project named Acme Design System.

That runs the sync-project prompt. Your agent resolves the name with find-project (asking if more than one matches), confirms the project with you, and writes its projectId and version to bezel.json. It then fetches the file with get-design-tokens-file and writes design-tokens.json exactly as the app's Download button would:

{
"$schema": "https://www.designtokens.org/schemas/2025.10/format.json",
"base": { "color": { "…": {} } },
"semantic": { "color": { "…": {} } }
}

Finally it runs bezel build so your generated outputs match.

Later runs don't need the project name. The agent reads projectId from bezel.json and just asks for the latest tokens. To pin a published release instead of latest, set version in bezel.json (see the next step) or ask for it: "Update my tokens to version 1.4.0."

If the repo isn't set up yet (no bezel-kit or no bezel.json), sync-project says so and runs the setup flow from the Fastest path tip at the top of this page first.

Without MCP

Click Download in the app header and save the file to your project root. Same result, done by hand.

3. Create bezel.json

npx bezel init

That writes the config for you, choosing defaults from your project:

{
"projectId": "0b5c3d1e-7f2a-4c9b-8e6d-1a2b3c4d5e6f",
"version": "latest",
"variablesOutput": "src/bezel/variables.css",
"contextsOutput": "src/bezel/contexts.ts",
"fontsOutput": "src/bezel/fonts.ts"
}

The first two fields are for the MCP server; bezel build ignores them. projectId is the Bezel project this repo tracks, so your agent never has to ask which one you mean. version is which tokens to pull: latest (the default init writes) means your current working tokens, and a published semver like 1.4.0 pins that release. Pass them on the command line to have init fill them in:

npx bezel init --project <uuid> --tokens-version 1.4.0

Both flags also work on an existing bezel.json without --force: each updates only its own key and leaves the rest alone.

Outputs go to src/bezel/ (or bezel/ if you have no src/). Everything Bezel writes is generated and gitignored, and bezel build overwrites without asking. Its own directory means it can never land on a file you wrote.

Only variablesOutput is required. The other two are small TypeScript modules listing your themes and the web fonts your tokens use, handy for theme switchers and font loading. init skips them if your project has no tsconfig.json.

Override anything inline, or just edit the file afterwards:

npx bezel init --dir packages/ui/src/bezel --no-fonts

Apart from --project and --tokens-version, init won't touch an existing bezel.json unless you pass --force.

Export names decide your CSS variable names

If any token in the file has an export name, Bezel switches to export-name mode: a token with exportName: ["primary"] becomes --primary, and any token without one emits nothing at all.

If no token has one, names come from the token path with base/semantic and default trimmed off, so semantic.color.primary.default becomes --color-primary.

If your generated CSS comes out emptier than expected, this is why. Set export names in the token editor first.

4. Wire it into your scripts

{
"scripts": {
"tokens": "bezel build",
"dev": "npm run tokens && vite",
"build": "npm run tokens && vite build"
}
}

bezel build reads design-tokens.json, picks up bezel.json automatically, writes your outputs, and adds them to .gitignore in a managed block. Generated files are build output, so don't edit them and don't commit them.

Import the CSS once in your entry file:

import './bezel/variables.css';

Run npm run dev and use the variables:

.button {
background: var(--primary);
color: var(--primary-foreground);
}

Each context becomes its own scope: the base context is :root, and others are classes (.dark, .light). Switch themes by toggling that class on <html>.

5. Live preview (optional)

This renders your app inside Bezel's preview panel, with token edits applied instantly. It's a development tool, so install it as a dev dependency and keep it out of production builds.

npm install --save-dev @bezel-labs/crystal

In your entry file, before you render:

/**
* Live preview (dev environment only example)
*
* Lets the Bezel preview panel push token edits into this app while you work.
* It plays no part in how the app is built or styled: your real styles come
* from the generated variables.css, so removing this block changes nothing
* except that the panel stops updating live.
*
* The dynamic import sits inside the guard on purpose. That's what keeps
* both the listener and the @bezel-labs/crystal code out of your production
* bundle, since the bundler drops the whole unreachable branch.
*/
if (import.meta.env.DEV) {
import('@bezel-labs/crystal').then(({ startLivePreviewReceiver }) => {
startLivePreviewReceiver({
allowedOrigins: ['https://app.bezel.new']
});
});
}

On Next.js or anything webpack-based, use process.env.NODE_ENV !== 'production' in place of import.meta.env.DEV.

Keep allowedOrigins explicit. The receiver validates every incoming message against it, then injects the CSS into a single <style> tag so it cascades over your generated variables.css.

Now point the panel at your app: in the preview panel, open Preview URL, enter your dev server URL, and save. http://localhost:5173 works fine.

Edit a token in Bezel and your app restyles without reloading.

Previewing a deployed build

The guard above means a production build won't respond to the panel. If you want to preview a deployed staging site, run the receiver there too, and list only the origins you trust, since it accepts CSS from anything in allowedOrigins.

Storybook and other nested iframes

If your app renders the real content inside a second iframe, the outer frame needs createLivePreviewRelay to forward messages one hop down. See Live Preview.

Command reference

CommandWhat it does
bezel initCreate bezel.json with defaults inferred from your project
bezel init --dir <path>Put the generated outputs somewhere else
bezel init --forceOverwrite an existing bezel.json
bezel init --project <uuid>Set projectId, the Bezel project the MCP syncs from. Updates an existing file in place
bezel init --tokens-version <v>Set version to latest or a published semver. Updates an existing file in place
bezel buildGenerate outputs from design-tokens.json + bezel.json
bezel build --stdoutPrint the CSS instead of writing files, good for a quick check
bezel build --color hexEmit hex colors instead of the default oklch
bezel build --unit remConvert px dimensions to rem
bezel build --no-gitignoreLeave .gitignore alone
bezel --helpEverything else
One config to leave alone

Don't set nameExtension in bezel.json if you're using live preview. The panel pushes CSS built with the default, so a custom value produces different variable names and live edits will quietly stop matching your build output.

Keeping tokens up to date

Once the repo is set up, refreshing design-tokens.json is one prompt. Any of these runs sync-project:

Update my design tokens.

Sync my Bezel tokens.

Pull the latest tokens from Bezel.

Or /mcp__bezel__sync-project. The agent reads projectId and version from bezel.json, fetches the file, writes it, and runs bezel build. No project name needed.

To point the repo at a different project, say so and it updates bezel.json after confirming:

Update my <app name> project to use my Bezel project named Acme Design System.

Switch this repo to Bezel project <name or uuid>.

To move to a published release:

Update my tokens to version 1.4.0.

Or /mcp__bezel__sync-project "Acme Design System" 1.4.0. More in MCP Workflows.

Next steps