Migrating from Chakra UI
Migrate your project from Chakra UI to Panda and see how style props, theme, and variants map across.
Chakra was designed to align with Panda, so @chakra-ui/panda-preset carries the whole theme over in one config
line; what's left is moving components off the Emotion runtime.
Panda styles, it doesn't ship components: pair it with Ark UI (opens in a new tab) to fully replace Chakra.
Keep the Chakra theme
@chakra-ui/panda-preset (opens in a new tab) mirrors Chakra's default theme:
tokens, semantic colors, recipes, slot recipes, and global styles.
panda.config.ts
import { defineConfig } from '@pandacss/dev'
export default defineConfig({
presets: ['@chakra-ui/panda-preset'],
include: ['./src/**/*.{js,jsx,ts,tsx}']
})Your existing token and recipe names (bg.subtle, red.500, button) keep working in css() unchanged. With
the preset in place, the theming sections below only matter where your config diverges from Chakra's defaults.
Theming
Chakra builds a system at runtime and hands it to a provider:
import { ChakraProvider, createSystem, defaultConfig, defineConfig } from '@chakra-ui/react'
const config = defineConfig({
theme: {
tokens: {
colors: {
brand: { value: '#0ea5e9' }
}
}
}
})
const system = createSystem(defaultConfig, config)
export default function App({ children }) {
return <ChakraProvider value={system}>{children}</ChakraProvider>
}The token shape is already Panda's. Panda needs no provider: the theme lives in panda.config.ts and resolves at
build time:
panda.config.ts
import { defineConfig } from '@pandacss/dev'
export default defineConfig({
theme: {
extend: {
tokens: {
colors: {
brand: { value: '#0ea5e9' }
}
}
}
}
})See Tokens.
css prop
Chakra v3's css prop carries over by name; only the component source changes:
// before
import { Box } from '@chakra-ui/react'
<Box css={{ color: 'white', bg: 'brand' }} />
// after
import { styled } from '../styled-system/jsx'
<styled.div css={{ color: 'white', bg: 'brand' }} />See JSX Style Props.
Variants
Chakra registers recipes on the system:
import { defineRecipe } from '@chakra-ui/react'
export const buttonRecipe = defineRecipe({
base: { fontWeight: 'bold' },
variants: {
variant: {
solid: { bg: 'brand', color: 'white' }
}
}
})Panda uses the same base/variants shape, in panda.config.ts (or colocated with cva/sva). The one new
field is className, the stable class name Panda emits for the recipe, which Chakra never needed because Emotion
generated hashed class names at runtime:
panda.config.ts
export default defineConfig({
theme: {
extend: {
recipes: {
button: {
className: 'button',
base: { fontWeight: 'bold' },
variants: {
variant: {
solid: { bg: 'brand.500', color: 'white' }
}
}
}
}
}
}
})import { button } from '../styled-system/recipes'
<button className={button({ variant: 'solid' })}>Click me</button>See Recipes and Slot Recipes.
Color modes
Both sides prefer semantic tokens: define the light/dark pair once, and every use site resolves per mode.
panda.config.ts
theme: {
extend: {
semanticTokens: {
colors: {
bg: { value: { base: '{colors.white}', _dark: '{colors.gray.800}' } }
}
}
}
}<div className={css({ bg: 'bg' })} />See Conditional Styles and Multiple Themes.
Global styles
Same key, same shape, no provider:
panda.config.ts
export default defineConfig({
globalCss: {
body: { bg: 'gray.50', color: 'gray.800' }
}
})See Global Styles.
Layout components
Chakra's Box, Flex, Grid, Stack map to Panda's patterns, generated into
styled-system/jsx:
import { Box, Grid } from '../styled-system/jsx'
<Grid gridTemplateColumns="repeat(2, 1fr)" gap="6">
<Box bg="brand.500">Box</Box>
</Grid>Unwind the runtime
The mechanical part of the migration is removing what Panda replaces:
ChakraProviderandcreateSystemgo away entirely. Nothing reads a theme from context; the theme is in the generated CSS.useColorModeValuecall sites become semantic tokens. The value branches in CSS, not in a hook, so the component doesn't re-render on mode change. Keepnext-themes(or your toggle of choice) for setting the.darkclass.
// before
const bg = useColorModeValue('white', 'gray.800')
// after: css({ bg: 'bg' }) with the semantic token aboveuseRecipe/useSlotRecipebecome direct imports fromstyled-system/recipes. Recipes are plain functions now; there is no system to look them up in.
// before
const recipe = useRecipe({ key: 'button' })
const styles = recipe({ variant: 'solid' })
// after
import { button } from '../styled-system/recipes'
const className = button({ variant: 'solid' })Interactive components
Panda doesn't ship interactive components (Menu, Dialog, Tabs). The replacement is wrapping Ark UI with a
slot recipe, and since Chakra v3 is built on Ark, the component anatomy is the one you already use:
// before
import { Menu } from '@chakra-ui/react'
// after: same parts from Ark, styled by your slot recipe
import { Menu as ArkMenu } from '@ark-ui/react/menu'
import { sva } from '../styled-system/css'
import { createSlotRecipeContext } from '../styled-system/jsx'
const menu = sva({
slots: ['root', 'trigger', 'content', 'item'],
base: {
content: { bg: 'white', rounded: 'md', shadow: 'lg' },
item: { px: '3', py: '2', _highlighted: { bg: 'gray.100' } }
}
})
const { withProvider, withContext } = createSlotRecipeContext(menu)
export const Menu = {
Root: withProvider(ArkMenu.Root, 'root'),
Trigger: withContext(ArkMenu.Trigger, 'trigger'),
Content: withContext(ArkMenu.Content, 'content'),
Item: withContext(ArkMenu.Item, 'item')
}Usage sites keep their <Menu.Root> / <Menu.Trigger> markup unchanged. See
Wrap headless libraries for the full pattern.
Run both during the migration
Panda's CSS ships inside cascade layers, and Emotion's injected styles are unlayered, so Chakra's styles win over converted components by default. See Migrating alongside unlayered CSS for the two ways to control which side wins.
After migrating
- Lint against your theme. The lint rules flag raw values where a token exists and deprecated tokens as you retire Chakra-era names.
- Drop the styling runtime. Source transforms rewrite static calls to plain class strings, so no styling code ships at all, the full distance from Emotion's runtime.
See also
- Wrap headless libraries for Chakra-like interactive components without Chakra's runtime.
- Recipes for the full recipe model this migration leans on.