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, base and sm typically cover mobile, md for tablets, and lg+ 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:

  1. Read your Figma-exported JSON design tokens.
  2. Resolve all {...} references within the tokens.
  3. Convert px values to rem.
  4. Map your token structure to the Chakra/Panda themeConfig.json format.
  5. Output the final, resolved themeConfig.json to a designated dist/ 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 px to rem Keep px values
Use semanticTokens for responsive values Put responsive values in tokens
Use the themes object for multi-theme Hardcode light/dark in core tokens
Reference tokens with {path} in textStyles Duplicate values manually

Essential Tools & Resources

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 👋 👋

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.
You need to agree with the terms to proceed