overview
faq

Frequently Asked Questions

Frequently asked questions and how to resolve common issues

How does Bamboo manage style conflicts ?

When you combine shorthand and longhand properties, Bamboo will resolve the styles in a predictable way. The shorthand property will take precedence over the longhand property.

import { css } from '../styled-system/css'
 
const styles = css({
  paddingTop: '20px',
  padding: '10px',
})

The styles generated at build time will look like this:

@layer utilities {
  .p_10px {
    padding: 10px;
  }
 
  .pt_20px {
    padding-top: 20px;
  }
}

Imported Image is not working in Vite App

This is a known limitation of Bamboo due to our static extraction approach.

💡

Think of it this way: there's no way for the compiler to know what the final asset URL will be since Vite controls it.

We recommend moving the imported backgroundImage to the style attribute.

import myImageBackground from './my-image.png'
 
const Demo = () => {
  return (
    <p
      className={css({ bg: 'red.300', backgroundRepeat: 'repeat' })}
      style={{ backgroundImage: `url("${myImageBackground}")` }}
    >
      Hello World
    </p>
  )
}

How to get Bamboo to work with Jest?

If you run into error messages like SyntaxError: Unexpected token 'export' when running Jest tests. Here's what you can:

In your tsconfig, add

{
  "compilerOptions": {
    "allowJs": true
  }
}

In your Jest configuration, add the ts-jest transformer:

export default {
  // ...
  transform: {
    '^.+\\.tsx?$': 'ts-jest',
    '^.+\\.(ts|tsx|js|jsx)?$': 'ts-jest',
  },
}

In your Bamboo config, set the outExtension to js:

export default defineConfig({
  // ...
  outExtension: 'js',
})

HMR does not work when I use tsconfig paths?

Bamboo tries to automatically infer and read the custom paths defined in tsconfig.json file. However, there might be scenarios where the hot module replacement doesn't work.

To fix this add the importMap option to your bamboo.config.js file, setting it's value to the specified paths in your tsconfig.json file.

// tsconfig.json
 
{
  "compilerOptions": {
    "baseUrl": "./src",
    "paths": {
      "@my-path/*": ["./styled-system/*"]
    }
  }
}
// bamboo.config.js
 
module.exports = {
  importMap: '@my-path',
}

This will ensure that the paths are resolved correctly, and HMR works as expected.


HMR not triggered

If you are having issues with HMR not being triggered after a bamboo.config.ts change (or one of its dependencies), you can manually specify the files that should trigger a rebuild by adding the following to your bamboo.config.ts:

bamboo.config.ts

import { defineConfig } from '@bamboocss/dev'
 
export default defineConfig({
  // ...
  dependencies: ['path/to/files/**.ts'],
})

Why are my styles not applied?

Check that the @layer rules are set and the corresponding .css file is included. If you're not using postcss, ensure that styled-system/styles.css is imported and that the bamboo command has been run (or is running with --watch).


How can I debug the styles?

You can use the bamboo debug to debug design token extraction & css generated from files.

If the issue persists, you can try looking for it in the issues (opens in a new tab). If you can't find it, please create a minimal reproduction and submit a new github issue (opens in a new tab) so we can help you.


Why is my IDE not showing styled-system imports?

If you're not getting import autocomplete in your IDE, you may need to include the styled-system directory in your tsconfig.json file.


How do I get a type with each recipe properties?

You can get a config recipe properties types by using XXXVariantProps. Let's say you have a config recipe named button, you can import its type like this:

import { button, type ButtonVariantProps } from '../styled-system/recipes'

You can get an atomic recipe properties types by using RecipeVariantProps. Let's say you have a atomic recipe named button, you can get its type like this:

import { cva, type RecipeVariantProps } from '../styled-system/css'
 
export type ButtonVariantProps = RecipeVariantProps<typeof buttonStyle>

How do I split recipe props from the rest?

You can split recipe props by using xxx.splitVariantProps. Let's say you have a recipe named button, you can split its props like this:

import { css, cx } from '../styled-system/css'
import { ButtonVariantProps, button } from '../styled-system/recipes'
 
interface ButtonProps extends ButtonVariantProps {
  children: React.ReactNode
}
 
export function Button(props: ButtonProps) {
  const { children, ...rest } = props
  const [buttonProps, cssProps] = button.splitVariantProps(rest)
  return <button className={cx(button(buttonProps), css(cssProps))}>{children}</button>
}

The same xxx.splitVariantProps method is available for both config recipes and atomic recipes.


How do I reference a token value or css var?

You can reference a token value or it's associated css variable using the token function. This function allows you to access and use the values stored in your theme tokens at runtime.

import { token } from '../styled-system/tokens'
 
function App() {
  return (
    <div
      style={{
        background: token('colors.blue.200'),
      }}
    />
  )
}

Should I commit the styled-system folder?

Just like the node_modules folder, you most likely don't want to commit the styled-system folder. It contains code that is auto-generated and can be re-generated at any time.


How is Bamboo CSS different from Panda CSS?

Bamboo CSS is a fork of Panda CSS v1, so the styling API is the same and migrating is mostly a rename. What has changed since the fork is everything below: what gets shipped to the browser, what it costs at runtime, and how quickly the build turns around.

Every number here was measured against this repository's own sandboxes and fixtures. Your project will differ — the output-size wins in particular scale with the size of your design system rather than the size of your app.

Less CSS

  • pruneUnusedTokens drops token CSS variables nothing can reach. The token layer declares every token in your theme while an app uses a fraction of them. On the vite-ts sandbox this takes styles.css from 24,433 to 12,293 bytes — 6,398 to 3,504 gzipped.
  • pruneUnusedKeyframes does the same for @keyframes rules a preset declares and your app never animates.

Both are opt-in and change nothing until switched on.

Less JavaScript

  • The generated output now declares sideEffects, so a bundler can tree-shake the barrels. Previously import { Box } from 'styled-system/jsx' retained every pattern module.
  • The JSX property list is emitted once rather than twice. It carried the browser CSS properties and your project's properties as two constants that the runtime immediately concatenated, with 285 entries appearing in both; the module drops from 15,684 to 11,468 bytes.

Together a JSX barrel import goes from 41.2 KB to 30.1 KB minified, and 12.6 KB to 10.2 KB gzipped.

A faster runtime

  • css() and the pattern helpers memoize their class names on a structural hash confirmed by an exact comparison. Repeated css() calls get roughly 4-5x faster, multi-argument calls about 4x, and the pattern helpers — which were not memoized at all — about 1.3x. Class name output is unchanged.
  • That cache is bounded. It previously grew for the lifetime of the process, which leaked in long-lived SSR.
  • Copying styles now happens at the raw() boundary rather than on every merge, so the two places that need an independent object pay for it instead of every caller.
  • The JSX factory no longer memoizes its props. Both useMemo calls keyed off a rest-destructured object, so neither could ever hit. About 3% on an unfolded tree.
  • splitProps reads each key's descriptor on demand rather than building one for the whole object: 2.4-2.9x on plain and frozen props. Accessor props, which is what Solid compiles to, keep the descriptor path.

Optional zero-runtime output

@bamboocss/vite rewrites statically-resolvable calls into the class string they would have returned, so they cost nothing at runtime:

// you write
export const title = css({ fontSize: 'lg', fontWeight: 'bold' })
 
// the bundle gets
export const title = 'fs_lg fw_bold'

It also collapses styled.* elements to the intrinsic tag they render, folds config recipes, pattern elements and a static as prop, and follows named imports across files. CSS output is unchanged — only the JavaScript changes. It is off by default, build-only, and reports how much of your source it managed to fold.

Less HTML

cssMode: 'grouped' emits one class per css() call rather than one per property, so an element carrying a dozen atomic classes carries one instead.

Atomic CSS is a good default, but at scale it puts long class sequences into the markup and multiplies the declarations the browser walks during style recalculation — a cost hash does not address, since it shortens class names rather than reducing their count. Grouped trades some CSS duplication for the other side of that: a shared declaration is repeated in every group instead of deduping into one class. Which way that goes depends on whether your bottleneck is the CSS bundle or the HTML payload, so it is opt-in and atomic stays the default. It matters most under SSR and SSG, where the HTML is rarely cached and lands on every initial render. The upstream proposal (opens in a new tab) has the benchmarks behind that.

Faster builds

  • The dependency graph resolves imports without initializing the TypeScript type checker, which was costing hundreds of milliseconds on a cold build for what is only a filesystem question.
  • Watch mode builds the stylesheet once per edit rather than once per affected file. Editing a shared style file with 60 importers previously ran the full optimize pipeline and wrote to disk 61 times.
  • Editing a shared style file now re-parses the files importing it, so consumers stop emitting stale styles.

How does Bamboo work?

When running pnpm bamboo, here's what's happening under the hood:

  • Load Bamboo context:
    • Find and evaluate app config, merge result with presets.
    • Create bamboo context: prepare code generator from config, parse user's file as AST.
  • Generating artifacts:
    • Write lightweight JS runtime and types to output directory
  • Extracting used styles in app code:
    • Run parser on each user's file: identify and extract styles, compute CSS, write to styles.css.

I'm seeing a "Could not resolve xxx" error with esbuild/tsdown. What should I do?

In such a case, check the outExtension in your bamboo.config and set it to "js". This will ensure your modules are resolved correctly.


Why does importing styled not exist?

You should use config.jsxFramework when you need to import styled components. You can then use the jsxFactory option to set the name of the factory component.


Why is my preset overriding the base one, even after adding it to the array?

You might have forgotten to include the extend keyword in your config. Without extend, your preset will completely replace the base one, instead of merging with it.


Why is my base condition not working in this example?

css({ color: { _base: 'red.600', _dark: 'white' } })

You used _base instead of base, there is no underscore _.


What's the difference between using defineConfig() vs definePreset()

defineConfig is intended to be used in your app config, and will show you all the config keys that are available. definePreset will only show you the config keys that will be merged into an app's config, the rest will be ignored.


How can I completely override the default tokens?.

If you want to completely override all of the default presets theme tokens, you can omit the extends keyword from your theme config object.

If you want to keep some of the defaults, you can install the @bamboocss/preset-bamboo package, import it, then specifically pick what you need in there (or use the JS spread operator ... and override the other keys).


How do I make a design system / component library with Bamboo?

There is a detailed guide on how to do this here.


Can I use dynamic styles with Bamboo?

Yes, you can use dynamic styles with Bamboo. More on that here.


Can Bamboo resolve styles at build time instead of at runtime?

Yes, for the parts it can see. @bamboocss/vite rewrites statically-resolvable style calls into the class string they would have returned, so those calls cost nothing on render:

export const title = css({ fontSize: 'lg' })
export const title = 'fs_lg'

It covers css(), pattern and recipe calls, styled.* and pattern elements, and the static half of anything only partly static. The CSS is unchanged; only the JavaScript is. Off by default and build-only — see Source Transformation.


Should I use atomic or config recipes ?

Config recipes are generated just in time, meaning that only the recipes and variants you use will exist in the generated CSS, regardless of the number of recipes in the config.

This contrasts with Atomic recipes (cva), which generates all of the variants regardless of what was used in your code. The reason for this difference is that all config.recipes are known at the start of the bamboo process when we evaluate your config. In contrast, the CVA recipes are scattered throughout your code. To get all of them and find their usage across your code, we would need to scan your app code multiple times, which would not be ideal performance-wise.

When dealing with simple use cases, or if you need code colocation, or even avoiding dynamic styling, atomic recipes shine by providing all style variants. Config recipes are preferred for design system components, delivering leaner CSS with only the styles used. Choose according to your component needs.


Why does the bamboo codegen command fail ?

If you run into any error related to "Transforming const to the configured target environment ("es5") is not supported yet", update your tsconfig to use es6 or higher:

tsconfig.json

{
  "compilerOptions": {
    "target": "es6"
  }
}

How can I generate all possible CSS variants at build time?

While it's possible to generate all variants, even unused ones, by using config.staticCss (opens in a new tab), it's generally not recommended to use it for more than a few values. However, keep in mind this approach compromises one of Bamboo's strengths: lean, usage-based CSS generation.


Can I use one-off media query and other at rules?

Yes, you can! You can apply one-off media queries and other at rules (such as @container, @supports) in your CSS as shown below:

css({
  containerType: 'size',
  '@media (min-width: 10px)': {
    fontSize: 'xl',
    color: 'blue.300',
  },
  '@container (min-width: 10px)': {
    fontSize: '2xl',
    color: 'green.300',
  },
  '@supports (display: flex)': {
    fontSize: '3xl',
    color: 'red.300',
  },
})

How can I prevent other libraries from overriding my styles?

You can use Layer Imports (opens in a new tab) to prevent other libraries from overriding your styles.

First of all you cast the css from the other library(s) to a css layer:

@import url('bootstrap.css') layer(bootstrap);
 
@import url('ionic.css') layer(ionic);

Then update the default layer list to deprioritize the styles from the other library(s):

@layer bootstrap, reset, base, token, recipes, utilities;
 
@layer ionic, reset, base, token, recipes, utilities;