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)