migration
panda

Migrating from Panda CSS

Migrate your project from Panda CSS to Bamboo CSS.

Bamboo CSS started as a fork of Panda CSS (opens in a new tab), so unlike the other guides in this section this one is not a rewrite. The styling API is the same: your style objects, recipes, patterns, conditions and tokens carry over unchanged. What changes is the name of the package they come from.

For most projects this is a find-and-replace of panda with bamboo, plus a re-run of codegen.

What stays the same

  • css, cva, sva, cx and the pattern functions – same names and authoring signatures. Under Vite they are compile-time APIs: recipe selections share the same global declaration atoms as css() (below).
  • Recipes, variants, compound variants and default variants. Slot recipes are the exception – their output compiles to one atom string per selected slot; see below.
  • Tokens, semantic tokens, conditions, breakpoints, themes, staticCss, cascade layers.
  • The config shape, minus five keys. Every key you have in panda.config.ts is valid in bamboo.config.ts except studio, jsxFramework, jsxFactory, jsxStyleProps and syntax, none of which have an equivalent – delete them.
  • The output directory. It still defaults to styled-system, so imports like styled-system/css do not move.

Because the generated output directory keeps its name and shape, the only imports you have to touch are the ones that name the tool itself – the config file, and any direct @pandacss/* imports. Replace @pandacss/postcss with @bamboocss/vite.

Rename the packages

Every package moves from the @pandacss scope to @bamboocss. The ones a project normally depends on:

Panda CSSBamboo CSS
@pandacss/dev@bamboocss/dev
@pandacss/types@bamboocss/types
@pandacss/preset-base@bamboocss/preset-base
@pandacss/preset-panda@bamboocss/preset-bamboo
@pandacss/postcss@bamboocss/vite

The internal packages (@pandacss/core, /node, /parser, /generator, /token-dictionary and the rest) follow the same rule if you import them directly.

The default preset is the one rename that is not a straight scope swap: @pandacss/preset-panda becomes @bamboocss/preset-bamboo. If you never listed presets explicitly, the defaults are applied for you and there is nothing to change.

Step by step

Swap the dependencies

pnpm remove @pandacss/dev
pnpm add -D @bamboocss/dev

Rename the config file

panda.config.ts becomes bamboo.config.ts. Bamboo looks for bamboo.config.{ts,js,mts,mjs} and will not pick up a file under the old name.

Update the import inside it, and the preset names if you listed them:

bamboo.config.ts

import { defineConfig } from '@bamboocss/dev'
 
export default defineConfig({
  presets: ['@bamboocss/preset-base', '@bamboocss/preset-bamboo'],
  include: ['./src/**/*.{js,jsx,ts,tsx}'],
  outdir: 'styled-system',
})

Use the Vite plugin

Delete the @pandacss/postcss entry. Add bamboocss() from @bamboocss/vite and import virtual:bamboo.css. See Using Vite.

Update your scripts

The CLI is bamboo (bamboocss also works). Every command keeps its name, so panda codegen becomes bamboo codegen, panda cssgen becomes bamboo cssgen, and so on.

{
  "scripts": {
    "prepare": "bamboo codegen"
  }
}

Regenerate the output directory

Delete the existing styled-system directory and generate it again, so nothing from the previous install is left behind:

rm -rf styled-system
pnpm bamboo codegen

cx joins, in both tools

At runtime, both helpers join arbitrary class strings. Bamboo's Vite compiler additionally recognizes analyzable Bamboo style arguments: it composes their declarations in argument order before allocating atoms, so overridden declarations do not ship. A cx() containing an unknown external class remains the tiny joining helper. See Merging styles.

Vite compiles slot selections directly

Panda's slot recipe output carries recipe- and variant-specific classes. Bamboo's Vite compiler resolves the selection first, then returns one shared atom string per slot:

const classes = checkbox({ size })
return (
  <div className={classes.root}>
    <span className={classes.label} />
  </div>
)

A static selection becomes a literal object. A finite runtime selection becomes a reduced lookup whose leaves are complete slot objects. Recipe names, slot names, scopeRoots, and DOM shape do not enter the emitted selectors, so portals require no CSS scoping accommodation. Use a stable data-slot attribute if application CSS or tests need a semantic slot selector; generated atom names are intentionally opaque.

Vite compiles recipes to shared atoms

With @bamboocss/vite, cva() and sva() are compile-time declarations. A selected recipe is resolved to authored styles and shares declaration atoms with every other recipe and css() call:

cva({ base: { padding: '4' }, variants: { size: { sm: { fontSize: 'sm' } } } })
// panda:  'p_4 fs_sm'
// bamboo: compact global atoms for padding:4 and fontSize:sm

The recipe name, source file, and className do not enter declaration identity. For analyzable cx(recipe(), css()), Bamboo composes the StyleSets in argument order before allocating classes, so overrides do not depend on stylesheet order.

Finite runtime axes become a reduced lookup whose leaves already include matching compound variants. The recipe config and recipe-specific selectors do not ship. Bamboo has no extraction-only runtime recipe mode.

Things that are easy to miss

  • PANDA_DEBUG is now BAMBOO_DEBUG. See Debugging.

  • There is no JSX factory. styled.div, panda.div, styled('button', recipe), createStyleContext and splitCssProps have no equivalent, and styled-system/jsx is not generated at all. Style props become a css() call, and a recipe component becomes an ordinary component that calls the recipe:

    // panda
    const Button = styled('button', buttonRecipe)
    <Button size="sm" />
     
    // bamboo
    const Button = (props: ButtonProps) => {
      const [variantProps, rest] = buttonRecipe.splitVariantProps(props)
      return <button {...rest} className={cx(buttonRecipe(variantProps), props.className)} />
    }

    jsxFramework, jsxFactory and jsxStyleProps are gone from the config with it — delete them.

    createStyleContext wrapped each slot in a component so it could see the root's variant. A slot recipe returns a record of class strings, so a compound component carries that record in an ordinary context and needs no wrapper per part — see styling compound components.

  • There are no JSX pattern components. <Stack>, <Box>, <HStack> and the rest are not generated, and styled-system/jsx does not exist to import them from. The pattern functions are unchanged and are what those components wrapped, so the rewrite is mechanical:

    // panda
    import { Stack } from '../styled-system/jsx'
    ;<Stack gap="4" align="center">
      {children}
    </Stack>
     
    // bamboo
    import { flex } from '../styled-system/patterns'
    ;<div className={flex({ direction: 'column', gap: '4', align: 'center' })}>{children}</div>
  • There is no template literal syntax. css`color: red` , and the template form of cva, sva and the patterns, are not supported. Write the object form:

    // panda
    const heading = css`
      font-size: 24px;
      font-weight: bold;
    `
     
    // bamboo
    const heading = css({ fontSize: '24px', fontWeight: 'bold' })

    The syntax config key is gone with it — both values — so delete it if you set it either way.

  • The ESLint plugin is @bamboocss/eslint-plugin.

  • Editor and CI caches. The TypeScript server holds on to the old styled-system types; restart it after regenerating.

  • .gitignore. If you generate styled-system in CI rather than committing it, the entry does not change, since the directory name is the same.

Worth a look once you have migrated

These are documented in full elsewhere, and none of them are required to get a migrated project building:

  • Source compilation@bamboocss/vite resolves every style API into shared atoms or a finite recipe table, and rejects open runtime styling.
  • prune.tokens – drops token CSS variables nothing can reach, which is usually the largest single saving in render-blocking CSS. On by default.
  • preflight.prune – drops the parts of the reset that style elements your source never renders, which is most of what is left once the tokens have gone. Off by default, and the one pruning option that cannot be proven from the build.
  • Spec files – generate a machine-readable description of your design system.
  • MCP server and llms.txt – expose your design system to AI tooling.

Getting help

If something does not carry over, check the open issues (opens in a new tab) first. If you cannot find it, please open a new issue (opens in a new tab) with a minimal reproduction.