A practical, framework-agnostic guide to generating a **Chakra UI v3* theme from Figma design tokens — adaptable to any token format.*
Chakra UI v3 introduced a new theming system based on tokens, semantic tokens, conditions, and recipes. While the official docs are great, many teams struggle with automating theme generation from Figma in a scalable, maintainable way.
This guide walks you through:
- The exact structure Chakra UI v3 expects in
themeConfig.json - What goes into
tokensvssemanticTokens - How to support responsive values, multiple themes, and text styles
- How to integrate the generated theme into your React app
No Figma plugin or proprietary format required — just JSON design tokens and a Node.js script.
Let's start by creating a json called themeConfig.json which defines the final chakra theme.
Important: Chakra UI v3 Uses Panda CSS Under the Hood
Chakra UI v3 is built on top of Panda CSS — a CSS-in-JS engine that powers its styling, theming, and component system.
This means:
- Your
themeConfig.jsonis not just Chakra-specific — it's a Panda CSS config. - The
tokens,semanticTokens,conditions, andrecipesyou define are fully compatible with Panda CSS. - You can reuse the same theme in non-React projects (Vue, Svelte, etc.) via Panda.
Official Docs to Bookmark
| Resource | Link |
|---|---|
| Chakra UI v3 Docs | https://chakra-ui.com |
| Panda CSS Docs | https://panda-css.com |
| Panda Theme Tokens | https://panda-css.com/docs/concepts/theme-tokens |
| Chakra + Panda Integration | https://chakra-ui.com/docs/styled-system/theming |
Pro Tip: Read both docs — Chakra handles the React layer, Panda handles the CSS engine.
Critical Rule: No Token References in Final Output
themeConfig.jsonmust contain only **fully resolved values — never{primitives.colors.blue.500}**
Wrong (Do Not Do This)
{
"colors": {
"brand": {
"500": { "value": "{primitives.colors.blue.500}" }
}
}
}
Correct (Required)
{
"colors": {
"brand": {
"500": { "value": "#0ea5e9" }
}
}
}
Why?
- Panda CSS does not resolve references at runtime
- Your build script must resolve all
{...}references - Final output must be static, predictable, and framework-ready
Your transformation script is responsible for:
- Detecting
{path.to.token}- Traversing the token tree
- Recursively resolving nested references
- Replacing with final value (
#0ea5e9,1.5rem, etc.)
function resolveReference(ref: string, tokens: any): any {
if (!ref.startsWith('{') || !ref.endsWith('}')) return ref;
const path = ref.slice(1, -1).split('.');
let value = tokens;
for (const key of path) {
value = value?.[key];
if (!value) throw new Error(`Unresolved token: ${ref}`);
}
return typeof value === 'object' && 'value' in value
? resolveReference(value.value, tokens)
: value;
}
This is a sample function for token value resolution. The logic will vary on your figma design tokens structure.
Always resolve before writing tothemeConfig.json
Prerequisites
- Node.js ≥ 18
- Chakra UI v3 (
@chakra-ui/react@3.3.1or later) - Design tokens exported as JSON (from Figma, Tokens Studio, etc.)
Step 1: Understand Chakra UI v3 Theme Structure
Chakra v3 uses a token-based system powered by Panda CSS. Your theme config must follow this shape:
interface ChakraThemeConfig {
breakpoints: Record<string, string>;
tokens: {
colors: Record<string, any>;
fonts: Record<string, string>;
fontWeights: Record<string, any>;
spacing: Record<string, any>;
borders: Record<string, any>;
radii: Record<string, any>;
shadows: Record<string, any>;
gradients: Record<string, any>;
letterSpacings: Record<string, any>;
};
semanticTokens: {
colors: Record<string, any>;
spacing: Record<string, any>;
fontSizes: Record<string, any>;
lineHeights: Record<string, any>;
grid: Record<string, any>;
};
textStyles: Record<string, { value: any }>;
themes: Record<string, string>; // for CSS variable scoping
}
Let’s break down each section.
breakpoints
Define your responsive breakpoints in rem.
"breakpoints": {
"base": "0rem",
"sm": "40rem",
"md": "48rem",
"lg": "64rem",
"xl": "80rem"
}
Use
min-widthvalues from your design system.
tokens — Primitive Design Tokens
These are raw, reusable values. Think of them as CSS variables.
colors
Primitive color palette (not semantic):
"colors": {
"brand": {
"50": { "value": "#f0f9ff" },
"500": { "value": "#0ea5e9" },
"900": { "value": "#0c4a6e" }
},
"gray": { ... },
"transparent": { "value": "transparent" }
}
Use
{ value: ... }wrapper — required by Panda CSS.
fonts
Font families:
"fonts": {
"heading": { "value": "'Inter', sans-serif" },
"body": { "value": "'Inter', sans-serif" },
"mono": { "value": "'Fira Code', monospace" }
}
Always wrap in
{ value: ... }
fontWeights, letterSpacings, spacing, borders, radii, shadows, gradients
All follow the same pattern:
"spacing": {
"4": { "value": "1rem" },
"8": { "value": "2rem" }
},
"radii": {
"md": { "value": "0.375rem" }
},
"shadows": {
"sm": { "value": "0 1px 3px rgba(0,0,0,0.1)" }
}
Convert px to rem using base 16:
px / 16 = rem
semanticTokens — Meaningful, Contextual Tokens
These are design decisions, not raw values.
Use when you want:
- Responsive values
- Theme-aware values (light/dark)
- Semantic meaning (
text.primary,bg.card)
Responsive Values (via object)
"fontSizes": {
"display.xl": {
"value": {
"base": "2.5rem",
"sm": "3rem",
"md": "3.75rem",
"lg": "4.5rem"
}
}
}
baseandsm= mobile
md= tablet (optional)
lg+ = desktop
Single Value (non-responsive)
"fontSizes": {
"body": { "value": "1rem" }
}
grid — Responsive Layout Tokens
"grid": {
"gutterSize": {
"value": { "base": "1rem", "lg": "2rem" }
},
"count": {
"value": { "base": 4, "lg": 12 }
}
}
textStyles — Reusable Typography
Define complete text styles:
"textStyles": {
"heading.h1": {
"value": {
"fontSize": "{fontSizes.display.xl}",
"lineHeight": "{lineHeights.tight}",
"fontWeight": "{fontWeights.bold}",
"fontFamily": "{fonts.heading}",
"letterSpacing": "{letterSpacings.tight}",
"textTransform": "uppercase"
}
}
}
Use token references with
{path.to.token}
UsetextCaseToTransform()to map FigmaUPPERCASEtouppercase
themes — Multi-Theme Support (Light/Dark/Minimal/Contrast)
Chakra v3 supports CSS-scoped themes via [data-theme] or css classes. Below is an example with data-theme.
"themes": {
"lightTheme": "[data-theme=light] &",
"darkTheme": "[data-theme=dark] &",
"minimalTheme": "[data-theme=minimal] &",
"contrastTheme": "[data-theme=contrast] &"
}
Same can be replicated using css classes:
"themes": {
"lightTheme": ".light &",
"darkTheme": ".dark &",
"minimalTheme": ".minimal &",
"contrastTheme": ".contrast &"
}
then set the defined data-theme or classname to the body tag for different views.
Use in
semanticTokens.colorslike:
"colors": {
"bg.page": {
"value": {
"_light": "#ffffff",
"_darkTheme": "#0f172a",
"_minimalTheme": "#f8f9fa",
"_contrastTheme": "#000000"
}
}
}
_lightis default
Use_themeNameThemefor custom themes
Final themeConfig.json Example
{
"breakpoints": { ... },
"tokens": {
"colors": {
"brand": {
"500": { "value": "#0ea5e9" }
}
},
"fonts": { ... },
"spacing": { ... }
},
"semanticTokens": {
"colors": {
"text.primary": {
"value": { "_light": "#1f2937", "_darkTheme": "#f1f5f9" }
}
},
"fontSizes": {
"display.xl": { "value": { "base": "2.5rem", "lg": "4.5rem" } }
}
},
"textStyles": {
"heading.h1": { "value": { ... } }
},
"themes": {
"lightTheme": "[data-theme=light] &",
"darkTheme": "[data-theme=dark] &",
"minimalTheme": "[data-theme=minimal] &",
"contrastTheme": "[data-theme=contrast] &"
}
}
No
{...}references — all values fully resolved
Step 2: Generate themeConfig.json (Any Format to Chakra)
Write a Node.js script that:
- Reads your Figma JSON
- Resolves all
{references} - Converts
px to rem - Maps to Chakra/Panda structure
- Outputs
dist/themeConfig.json
Your input format doesn’t matter — only the output does.
Example Script Skeleton
// generate-theme.ts
import fs from 'fs/promises';
import path from 'path';
const pxToRem = (px: number) => `${px / 16}rem`;
function resolveReference(ref: string, tokens: any): any {
if (!ref.startsWith('{') || !ref.endsWith('}')) return ref;
const path = ref.slice(1, -1).split('.');
let value = tokens;
for (const key of path) {
value = value?.[key];
if (!value) throw new Error(`Unresolved token: ${ref}`);
}
return typeof value === 'object' && 'value' in value
? resolveReference(value.value, tokens)
: value;
}
async function generateTheme() {
const input = JSON.parse(await fs.readFile('tokens.json', 'utf-8'));
const theme: any = {
breakpoints: { base: '0rem', sm: '40rem', md: '48rem', lg: '64rem', xl: '80rem' },
tokens: { colors: {}, fonts: {}, spacing: {} },
semanticTokens: { colors: {}, fontSizes: {} },
textStyles: {},
themes: {
lightTheme: '[data-theme=light] &',
darkTheme: '[data-theme=dark] &',
minimalTheme: '[data-theme=minimal] &',
contrastTheme: '[data-theme=contrast] &'
}
};
// Map your tokens to Chakra/Panda
// Use resolveReference() everywhere
// ...
await fs.mkdir('dist', { recursive: true });
await fs.writeFile('dist/themeConfig.json', JSON.stringify(theme, null, 2));
}
generateTheme();
Step 3: Use in React App
// theme.ts
import * as themeConfig from './dist/themeConfig.json';
import { createSystem, defaultConfig, defineConfig } from '@chakra-ui/react';
const customConfig = defineConfig({
theme: {
...(themeConfig as any),
recipes: { /* your component recipes */ },
slotRecipes: { /* your slot recipes */ },
},
conditions: themeConfig.themes as any,
});
export const system = createSystem(defaultConfig, customConfig);
export const { ThemeProvider, theme } = system;
App Entry
// App.tsx
import { ThemeProvider, theme } from './theme';
import { Theme } from '@chakra-ui/react';
export default function App() {
return (
<ThemeProvider>
<Theme accentColor="brand.500">
<YourApp />
</Theme>
</ThemeProvider>
);
}
Switching Themes at Runtime
<button onClick={() => document.documentElement.dataset.theme = 'minimal'}>
Minimal Mode
</button>
Chakra automatically picks up
[data-theme=minimal]
Best Practices
| Do | Don't |
|---|---|
Resolve all {...} references |
Leave references in output |
Use { value: ... } everywhere |
Use raw strings in tokens |
| Convert px to rem | Keep px values |
Use semanticTokens for responsive values |
Put responsive values in tokens
|
Use themes object for multi-theme |
Hardcode light/dark in tokens |
Reference tokens with {path} in textStyles
|
Duplicate values |
Tools & Resources
Conclusion
You now have a complete, scalable pipeline:
Figma to JSON Tokens => generate-theme.ts => themeConfig.json (resolved) to Chakra UI v3
This system:
- Works with any token format
- Supports responsive, multi-theme, semantic design
- Is fully automated and type-safe
- Scales to large design systems
- Powered by Panda CSS — future-proof and framework-agnostic
- Zero runtime token resolution
Share your ideas in the comments.
Happy theming!
Kiran 👋 👋
Top comments (0)