Revolutionize Your UI: Automating Chakra UI v3 Theming from Figma with Panda CSS
Crafting a consistent and scalable user interface demands a robust theming system. Chakra UI v3 delivers this with its advanced token-based architecture, powered by the efficient Panda CSS engine. While the official documentation provides a solid foundation, many development teams encounter challenges in streamlining the theme generation process, particularly when design tokens originate from Figma.
This comprehensive guide will demystify the process, offering a practical, framework-agnostic approach to automatically generating your Chakra UI v3 theme directly from Figma design tokens, adaptable to any token format.
The Power Duo: Chakra UI v3 and Panda CSS
At its core, Chakra UI v3 leverages Panda CSS, a powerful CSS-in-JS engine. This integration means your theme configuration isn’t just Chakra-specific; it’s a Panda CSS config that ensures:
- Unified Styling: A single source of truth for all your design tokens.
- Framework Agnostic: The same theme can be reused across various frontend frameworks like Vue or Svelte via Panda CSS, offering unparalleled flexibility.
- Scalability & Performance: Zero runtime token resolution for optimized performance.
Understanding this synergy is key. While Chakra UI handles the React layer, Panda CSS manages the underlying CSS engine. It’s highly recommended to consult both the Chakra UI v3 Docs and the Panda CSS Docs for a complete picture.
The Cardinal Rule: Fully Resolved Values in themeConfig.json
The most critical principle in Chakra UI v3 theming is that your themeConfig.json must contain only fully resolved values. This means no {primitives.colors.blue.500} references are permitted in the final output.
Why is this non-negotiable?
Panda CSS does not resolve token references at runtime. Your build script is solely responsible for detecting and resolving all {...} references, transforming them into their final static values (e.g., #0ea5e9, 1.5rem). This ensures the final output is predictable, static, and ready for deployment.
Incorrect Example (Do NOT do this):
{
  "colors": {
    "brand": {
      "500": { "value": "{primitives.colors.blue.500}" }
    }
  }
}
Correct Example (Required):
{
  "colors": {
    "brand": {
      "500": { "value": "#0ea5e9" }
    }
  }
}
Your transformation script will need a function similar to this for recursive resolution:
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;
}
Always resolve all references before writing to themeConfig.json.
Prerequisites
To embark on this theming journey, you’ll need:
*   Node.js ≥ 18
*   Chakra UI v3 (@chakra-ui/[email protected] or later)
*   Design tokens exported as JSON (from Figma, Tokens Studio, etc.)
Understanding the Chakra UI v3 Theme Structure
Chakra v3’s theme configuration adheres to a specific structure. Here’s a high-level overview of the ChakraThemeConfig interface:
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 application’s responsive breakpoints using rem units, typically reflecting min-width values from your design system.
"breakpoints": {
  "base": "0rem",
  "sm": "40rem",
  "md": "48rem",
  "lg": "64rem",
  "xl": "80rem"
}
tokens — Primitive Design Tokens
These are the fundamental, raw, and reusable values, akin to global CSS variables.
*   colors: Your primitive color palette (e.g., brand colors, grays), not semantic names.
    json
    "colors": {
      "brand": {
        "50": { "value": "#f0f9ff" },
        "500": { "value": "#0ea5e9" },
        "900": { "value": "#0c4a6e" }
      },
      "gray": { /* ... */ },
      "transparent": { "value": "transparent" }
    }
    Remember the { value: ... } wrapper, as required by Panda CSS.
*   fonts: Define your font families.
    json
    "fonts": {
      "heading": { "value": "'Inter', sans-serif" },
      "body": { "value": "'Inter', sans-serif" },
      "mono": { "value": "'Fira Code', monospace" }
    }
*   fontWeights, letterSpacings, spacing, borders, radii, shadows, gradients: These follow the same { value: ... } pattern.
    json
    "spacing": {
      "4": { "value": "1rem" },
      "8": { "value": "2rem" }
    },
    "radii": {
      "md": { "value": "0.375rem" }
    },
    "shadows": {
      "sm": { "value": "0 1px 3px rgba(0,0,0,0.1)" }
    }
    Pro Tip: Convert px values from your design tokens to rem using a base of 16px (e.g., 16px / 16 = 1rem).
semanticTokens — Meaningful, Contextual Tokens
These tokens represent design decisions and contextual usage rather than raw values. They are perfect for:
*   Responsive values
*   Theme-aware values (light/dark modes)
*   Semantic naming (e.g., text.primary, bg.card)
- Responsive Values (via object):
 json
 "fontSizes": {
 "display.xl": {
 "value": {
 "base": "2.5rem",
 "sm": "3rem",
 "md": "3.75rem",
 "lg": "4.5rem"
 }
 }
 }
 Here,baseandsmtypically cover mobile,mdfor tablets, andlg+for desktop.
- Single Value (non-responsive):
 json
 "fontSizes": {
 "body": { "value": "1rem" }
 }
- grid— Responsive Layout Tokens:
 - json
 "grid": {
 "gutterSize": {
 "value": { "base": "1rem", "lg": "2rem" }
 },
 "count": {
 "value": { "base": 4, "lg": 12 }
 }
 }
textStyles — Reusable Typography
Define complete typographic styles by referencing other tokens.
"textStyles": {
  "heading.h1": {
    "value": {
      "fontSize": "{fontSizes.display.xl}",
      "lineHeight": "{lineHeights.tight}",
      "fontWeight": "{fontWeights.bold}",
      "fontFamily": "{fonts.heading}",
      "letterSpacing": "{letterSpacings.tight}",
      "textTransform": "uppercase"
    }
  }
}
Note: Use token references with {path.to.token}. You might also need a textCaseToTransform() helper to map Figma’s text case properties (e.g., UPPERCASE) to CSS values (uppercase).
themes — Multi-Theme Support
Chakra UI v3 facilitates CSS-scoped themes using [data-theme] attributes or CSS classes, enabling seamless light, dark, or custom themes.
Using data-theme attributes:
"themes": {
  "lightTheme": "[data-theme=light] &",
  "darkTheme": "[data-theme=dark] &",
  "minimalTheme": "[data-theme=minimal] &",
  "contrastTheme": "[data-theme=contrast] &"
}
Using CSS classes:
"themes": {
  "lightTheme": ".light &",
  "darkTheme": ".dark &",
  "minimalTheme": ".minimal &",
  "contrastTheme": ".contrast &"
}
You would then set the data-theme attribute or a class on your <body> or root element to activate a theme.
To apply theme-specific values, use these theme names within your semanticTokens.colors:
"colors": {
  "bg.page": {
    "value": {
      "_light": "#ffffff",
      "_darkTheme": "#0f172a",
      "_minimalTheme": "#f8f9fa",
      "_contrastTheme": "#000000"
    }
  }
}
Here, _light serves as the default, and _themeNameTheme corresponds to your custom themes.
A Glimpse at the Final themeConfig.json
{
  "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] &"
  }
}
Crucially, observe that all {...} references are resolved to their absolute values.
Step-by-Step: Generating themeConfig.json
The next step is to create a Node.js script that acts as the bridge between your Figma design tokens and the themeConfig.json expected by Chakra UI v3/Panda CSS. This script should:
- Read your Figma-exported JSON design tokens.
- Resolve all {...}references within the tokens.
- Convert pxvalues torem.
- Map your token structure to the Chakra/Panda themeConfig.jsonformat.
- Output the final, resolved themeConfig.jsonto a designateddist/folder.
The beauty of this approach is that your input token format doesn’t matter; only the output format to themeConfig.json is critical.
Example Script Skeleton (generate-theme.ts):
// 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] &'
    }
  };
  // Implement your logic here to map 'input' tokens to 'theme' structure.
  // Make extensive use of the 'resolveReference()' and 'pxToRem()' functions.
  // Example for colors:
  // theme.tokens.colors.brand['500'] = { value: resolveReference('{colors.primary.500}', input) };
  // ...
  await fs.mkdir('dist', { recursive: true });
  await fs.writeFile('dist/themeConfig.json', JSON.stringify(theme, null, 2));
}
generateTheme();
Integrating the Theme into Your React App
Once themeConfig.json is generated, integrating it into your React application is straightforward.
1. Define your system and theme:
// 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;
2. Wrap your application with ThemeProvider:
// App.tsx
import { ThemeProvider, theme } from './theme';
import { Theme } from '@chakra-ui/react'; // Note: This 'Theme' is from @chakra-ui/react, not the 'theme' from './theme'
export default function App() {
  return (
    <ThemeProvider>
      <Theme accentColor="brand.500">
        <YourApp />
      </Theme>
    </ThemeProvider>
  );
}
Switching Themes at Runtime
Thanks to the data-theme attribute (or CSS classes) and Panda CSS’s conditions configuration, switching themes dynamically is simple:
<button onClick={() => document.documentElement.dataset.theme = 'minimal'}>
  Minimal Mode
</button>
Chakra UI and Panda CSS will automatically detect the [data-theme=minimal] attribute on the <html> element and apply the corresponding theme definitions from themeConfig.json.
Best Practices for Optimal Theming
| Do | Don’t | 
|---|---|
| Resolve all {...}references | Leave references in the output | 
| Use { value: ... }everywhere | Use raw strings directly in tokens | 
| Convert pxtorem | Keep pxvalues | 
| Use semanticTokensfor responsive values | Put responsive values in tokens | 
| Use the themesobject for multi-theme | Hardcode light/dark in core tokens | 
| Reference tokens with {path}intextStyles | Duplicate values manually | 
Essential Tools & Resources
- Chakra UI v3 Docs
- Panda CSS Docs
- Panda Theme Tokens
- Tokens Studio
- Figma Tokens Plugin
- Style Dictionary
Conclusion
You now possess the blueprint for establishing a complete, scalable, and automated theming pipeline:
Figma Design Tokens (JSON) => generate-theme.ts script => themeConfig.json (fully resolved) => Chakra UI v3 React App
This powerful system ensures:
*   Compatibility with any design token format.
*   Full support for responsive, multi-theme, and semantic design principles.
*   A fully automated and type-safe workflow.
*   Exceptional scalability for large design systems.
*   Future-proof architecture, powered by Panda CSS.
*   Zero runtime token resolution for peak performance.
Happy theming!
Kiran 👋 👋