Developer Docs
This documentation is still being finalized and may change as the product evolves.
This page stays practical: install, configure, connect a file, bootstrap Path A or Path B, apply contexts, and export CSS. If you want the short mental model first, start with How it works. If you are handing off from Figma, use Designer Docs.
1. Prerequisites
- A frontend that can import an npm package (Next.js, Remix, Vite React/Vue, …)
- TokenIgnite Figma plugin streaming for the file (designer signed in)
- The TokenIgnite file id from the plugin (e.g.
tokenignite-096149) — not a Figma file key - UI already using
var(--…)where you want live updates — TokenIgnite injects variables; it does not rewrite components
npm i -D tokenignite
npx tokenignite init
npx tokenignite --helpinit only writes tokenignite.config.json. It does not install the package.
2. Configuration
{
"attributeNames": {
"context": "data-ti-context",
"active": "data-ti-active"
},
"bridgePort": 5678,
"files": [
{
"id": "tokenignite-096149",
"rootSelector": ":root",
"name": "global-foundation",
"exportPath": "./app/styles"
}
]
}| Field | Purpose |
|---|---|
attributeNames.context | Active design contexts (default data-ti-context) |
attributeNames.active | Active stream name (default data-ti-active) |
bridgePort | Local CLI ↔ browser port for session status only. Live tokens still stream directly into the browser. |
files[].id | TokenIgnite file id (literal) or "process.env.VAR_NAME" |
files[].rootSelector | Where runtime CSS applies (e.g. :root) |
files[].name | Name for tokenignite run <name> and as data-ti-active value |
files[].exportPath | Export directory → {exportPath}/{name}.css |
Prefer env references for files[].id in public repos; literals are fine in private repos.
3. How live updates reach the browser
- Figma plugin publishes onto the live sync stream.
- Browser SDK connects after a one-time public bootstrap.
- SDK injects/updates a runtime
<style>tag with CSS custom properties. - Optional CLI (
tokenignite run <name>) mirrors tokens in the terminal and can export. The bridge port is not for token delivery.
4. Client bootstrap — two paths
Path A — Local: initTokenIgnite() + CLI run
Needs an active tokenignite run <name>. The bridge only reports that the session is alive. initTokenIgnite takes optional { bridgePort } only (default 5678) — not the full config. File id and stream name come from the CLI session.
// Vite: if (import.meta.env.DEV) {
if (process.env.NODE_ENV === "development") {
import("tokenignite").then(({ initTokenIgnite }) => initTokenIgnite());
// optional: initTokenIgnite({ bridgePort: 5678 })
}tokenignite run global-foundationAfter starting the CLI: click your app’s browser tab once (or switch away and back). Path A often waits for that focus before connecting — avoids console noise while no run is active.
Path B — Closed staging: runTokenIgnite(target, config)
Closed staging = production-built, access-controlled (not public production). No terminal run. Do not wrap in NODE_ENV === "development" — that would disable TokenIgnite on a production-like staging deploy.
Pass files[].name, a literal file id, or an env value the bundler can see:
import config from "../tokenignite.config.json";
import("tokenignite").then(({ runTokenIgnite }) =>
runTokenIgnite("global-foundation", config)
);
// or: runTokenIgnite("tokenignite-096149", config);
// or: runTokenIgnite(process.env.NEXT_PUBLIC_FILE_ID, config);Env refs in config resolve in the browser only when the bundler exposes them (e.g. NEXT_PUBLIC_*). Otherwise pass the resolved id as target. Keep Path B off public production via deploy/access control.
5. Terminal UI (tokenignite run <name>)
In-place dashboard. Compact pages keep scrollback; tall pages (especially CSS) expand for native scroll.
[TokenIgnite | Public Beta] Run Mode | Name: global-foundation | File-ID: tokenignite-096149 | Variables: 128
⚠ 12 number variables have Figma scopes that do not resolve to a unique unit → rendered unitless (fallback).
Live Feed (last 100 changed CSS variables):
[CHANGED] spacing:default --spacing-gap-2xl: 32px;
[CHANGED] color:light --color-brand-accent: #f2e932;
Run Mode (2 = View Contexts, 3 = View CSS, 4 = Exit Run Mode & Export CSS, 5 = Exit Run Mode):| Key | Page |
|---|---|
1 | Live Feed (default) — last 100 changes ([ADDED] / [CHANGED] / [RENAMED] / [DELETED]) |
2 | Contexts — copy-paste collectionName:modeName for data-ti-context |
3 | CSS — full live export preview |
4 | Exit and export to {exportPath}/{name}.css |
5 | Exit without export |
Variables in the header = unique Figma variable entities (same idea as Figma collection totals), not one row per mode value.
6. Contexts & CSS variables
Figma collections/modes become normalized kebab-case segments (leading 1- prefixes and special characters stripped). CLI page 2 and plugin filters show the same labels.
<html data-ti-context="color-modes:dark-mode">
<html data-ti-context="color-modes:dark-mode spacing-scale:comfortable">Multiple contexts are space-separated. Matching uses CSS ~=.
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" data-ti-context="color-modes:dark-mode spacing-scale:comfortable">
<body>{children}</body>
</html>
);
}Optional: follow the system color scheme
TokenIgnite does not emit prefers-color-scheme queries. Drive modes yourself with the exact labels from CLI page 2:
const mq = window.matchMedia("(prefers-color-scheme: dark)");
function applySystemContext() {
document.documentElement.setAttribute(
"data-ti-context",
mq.matches ? "color-modes:dark-mode" : "color-modes:light-mode"
);
}
applySystemContext();
mq.addEventListener("change", applySystemContext);CSS variable names
Without WEB code syntax: --{group-path}-{variable-name} (collection/mode not in the name). Explicit WEB syntax in Figma wins.
--spacing-gap-2xl: 32px;7. Export CSS vs live injection
Live validation injects CSS for the active stream and sets data-ti-active to files[].name.
Exported CSS uses Smart Cascade: values identical across a collection’s modes lift into one :is(…[data-ti-context~="…"]…) common layer; only real per-mode diffs stay in explicit blocks. The :not([data-ti-active="<name>"]) guard prevents export rules from fighting live injection for that same stream.
:is(
:root:not([data-ti-active="global-foundation"])[data-ti-context~="color-modes:light-mode"],
:root:not([data-ti-active="global-foundation"]) [data-ti-context~="color-modes:light-mode"],
:root:not([data-ti-active="global-foundation"])[data-ti-context~="color-modes:dark-mode"],
:root:not([data-ti-active="global-foundation"]) [data-ti-context~="color-modes:dark-mode"]
) {
--space-md: 1rem;
}
:root:not([data-ti-active="global-foundation"])[data-ti-context~="color-modes:light-mode"],
:root:not([data-ti-active="global-foundation"]) [data-ti-context~="color-modes:light-mode"] {
--surface-container: #f5f5f5;
}
:root:not([data-ti-active="global-foundation"])[data-ti-context~="color-modes:dark-mode"],
:root:not([data-ti-active="global-foundation"]) [data-ti-context~="color-modes:dark-mode"] {
--surface-container: #211f26;
}Exported CSS is unlayered. With cascade layers:
@import "./tokens.css" layer(theme);Optional: strip data-ti-active guards in production CSS (Vite)
Keep the guards in the committed export so teammates’ live validation still works. This snippet only strips them from Vite’s production emit — it does not rewrite the file on disk:
import { defineConfig } from "vite";
export default defineConfig(({ mode }) => ({
css: {
postcss: {
plugins:
mode === "production"
? [
{
postcssPlugin: "remove-ti-active",
Rule(rule) {
if (rule.selector.includes("data-ti-active")) {
rule.selector = rule.selector.replace(/:not\(\[data-ti-active=.*?\]\)/g, "");
rule.selector = rule.selector.replace(/\s+/g, " ").trim();
}
},
},
]
: [],
},
},
}));8. End-to-end checklist
- Plugin streaming; copy TokenIgnite file id
npm i -D tokenignite→npx tokenignite init→ setfiles[].id/name- Path A: bootstrap only when
NODE_ENV === "development". Path B: no that guard; keep off public production - Path A:
tokenignite run <name>→ click the app tab once → header shows name, file id, Variables count - Change a Figma variable → browser style tag updates; Live Feed shows
[CHANGED]/[ADDED] - Set
data-ti-contextfrom Contexts page → mode overrides apply - Press
4to export when ready
9. Keep the SDK updated
npm install tokenignite@latestIf your install is below the server minimum, connect is blocked and the console tells you to upgrade.
10. Troubleshooting
| Symptom | Fix |
|---|---|
| Upgrade required / cannot connect | npm install tokenignite@latest |
| No live updates | Plugin streaming? Correct files[].id and name? Path A has tokenignite run running? |
initTokenIgnite waits forever | Start tokenignite run <name>; match bridgePort |
| Run is up, but no styles in the browser | Click the app tab once (Path A focus reconnect); keep CLI run active |
| Env id empty in browser | Expose with NEXT_PUBLIC_* (or equivalent), or pass a resolved id to runTokenIgnite |
| UI does not change | Inspect the injected <style> tag; components must use matching var(--…) / Figma code syntax |
| TokenIgnite in public production | Path A: keep behind NODE_ENV === "development". Path B: closed staging only. Prefer devDependency. |
| Port busy | Change bridgePort or free the port |
11. Accounts
- Consuming a live stream locally or on closed staging does not require a developer TokenIgnite account.
- You still need the designer’s file id and an active plugin stream.
- Workspace membership (sharing inside the product UI) is separate and may require sign-in even if you already know the file id.