overview
faq

Frequently Asked Questions

Frequently asked questions and how to resolve common issues

How does Bamboo manage style conflicts ?

When a shorthand and a longhand set the same part of a style at equal specificity and condition, the longhand wins. For example, this gives padding-top: 20px and 10px on the other sides:

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

The utility rules are placed in ordered sublayers (shown here without minification):

@layer utilities {
  @layer s010-c0-p1000, s010-c0-p4000;
 
  @layer s010-c0-p1000 {
    .p_10px {
      padding: 10px;
    }
  }
  @layer s010-c0-p4000 {
    .pt_20px {
      padding-top: 20px;
    }
  }
}

See utility sublayers for conditions, specificity, and important styles. When you compose two style objects, a later value for the same property wins before class allocation.


Why didn't my className override the component's styles?

With Vite, cx() resolves conflicts only when the compiler can see every Bamboo argument. An arbitrary className prop remains a runtime string, so cx(componentStyles, props.className) is only a join. The browser applies the cascade and utility sublayers; the argument order of a string join does not set priority.

Use a declared recipe variant for finite component choices. When both style objects are statically available, css(base, override) merges their properties before class allocation. An arbitrary runtime style-object prop cannot be compiled, so changing className to an open css prop does not solve that case.

If both arguments are analyzable at the call site, cx(css(base), css(override)) is also composed semantically and losing declarations are never allocated. Recipes and utilities share the global atom layer.


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?

Bamboo's style calls must pass through @bamboocss/vite before a test executes them. ts-jest, Babel, and changing outExtension handle syntax or module-format concerns; they do not compile Bamboo styles.

For Jest, build your components with Vite first and have tests import the compiled output. The component-library build emits JavaScript that can run without Bamboo's compiler. Keep Jest's module-format and DOM setup appropriate for that output. Tests of ordinary helpers that never execute style calls can continue to run directly.

For tests that import application source, use a Vite-based test pipeline that runs bamboocss() on those modules and includes them in Bamboo's include. A missed style call throws an error containing was not compiled; changing the output extension cannot fix that error.


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 you import virtual:bamboo.css from a JavaScript or TypeScript module and that bamboocss() is listed in the Vite config.


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 inline recipe properties types by using RecipeVariantProps. Let's say you have an inline 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:

Button.tsx

import type { ComponentPropsWithoutRef } from 'react'
import { button, type ButtonVariantProps } from '../styled-system/recipes'
 
type ButtonProps = ButtonVariantProps & Omit<ComponentPropsWithoutRef<'button'>, keyof ButtonVariantProps | 'className'>
 
export function Button(props: ButtonProps) {
  const [variantProps, buttonProps] = button.splitVariantProps(props)
  return <button type="button" {...buttonProps} className={button(variantProps)} />
}

The same xxx.splitVariantProps method is available for both config recipes and inline 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. css, cva, sva, cx, the pattern functions, recipes, tokens, conditions and the config shape all carry over, so migrating is largely a rename — but not entirely. Three things differ: the API is smaller, a few things behave differently, and the output is leaner.

Every number below 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.

A smaller API

Bamboo deliberately has one way to write a style, rather than several.

  • There is no JSX factory. styled.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. jsxFramework, jsxFactory and jsxStyleProps are gone from the config with them. A compound component carries its slot classes in a context of your own — see styling compound components.
  • There are no JSX pattern components. <Stack>, <Box> and the rest are not generated. The pattern functions are what those components wrapped — see patterns for the set that survives.
  • There is no template literal syntax. css`color: red` is not supported; write the object form.

The result is that a class name in Bamboo always comes from a function call you can see, which is what makes the build-time compiler able to resolve the complete style graph.

Different behaviour

These compile unchanged and do something else, so they are worth checking on migration.

  • cx composes what the compiler can see. Fully analyzable Bamboo arguments are merged as StyleSets before atom allocation. With an arbitrary external class, cx remains a tiny string joiner and makes no semantic conflict guarantee. See cx joins external classes.
  • Vite compiles recipes to shared atoms. An inline or config recipe selection resolves into the same declaration pool as css(). Recipe identity does not enter class identity, and analyzable cx() composition is resolved before classes are allocated. See Vite compiles recipes to shared atoms.
  • Slot recipes return compiled slot atoms. Static selections become literal slot objects; finite runtime axes become reduced lookups of complete slot objects. No named slot selector or @scope is emitted by Vite.
  • Compound variants are resolved into complete leaves. They add no selector or runtime matcher; each selected leaf already contains the precedence-correct declarations.

Less CSS

  • prune.tokens drops token CSS variables nothing can reach. The token layer declares every token in your theme while an app uses a fraction of them, so this is usually the largest single saving.
  • prune.keyframes does the same for @keyframes rules a preset declares and your app never animates.

Both are on by default; set either to false to opt out.

  • preflight.prune drops the parts of the reset that style elements your source never renders. The reset is a fixed size, so it dominates a small stylesheet – a third of one sandbox's CSS here and four fifths of another's. Worth a further 13% to 38%, and unlike most CSS size work it holds up under compression, because it emits less rather than spelling the same thing differently.

This one is off by default and cannot be made safe by default: it has a textual scan of your own source and nothing else, so an element rendered by a dependency, by dangerouslySetInnerHTML, or by markdown is invisible to it.

Less JavaScript

  • The generated output now declares sideEffects, so a bundler can tree-shake the barrels. Previously import { box } from 'styled-system/patterns' retained every pattern module.
  • There is no JSX factory to import at all, so styled-system/jsx — previously the largest generated barrel — is not generated.

Compiled zero-runtime styling

@bamboocss/vite compiles style calls to globally shared declaration atoms in development and production:

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

A style that varies is expressed as a finite recipe axis. The build resolves compound variants and precedence for every reachable state and emits a reduced decision table:

const title = cva({
  base: { fontSize: 'lg' },
  variants: { weight: { bold: { fontWeight: 'bold' }, normal: { fontWeight: 'normal' } } },
})
 
title({ weight })

Recipe identity is discarded after selection, so the same declaration in this recipe, another recipe, or css() uses one atom. Recipe declarations are erased from JavaScript and the named recipe CSS layer is not emitted. Open runtime style values are compiler errors; there is no option that restores a runtime styling fallback.


How does Bamboo work?

The CLI resolves the config and presets, generates the typed styled-system authoring surface, and uses Rust/Oxc to extract styles from the configured source graph. bamboo and bamboo cssgen can write the extracted CSS.

During development and production builds, the required Vite plugin also compiles style calls to class strings or finite lookups, checks that their rules exist, and serves virtual:bamboo.css. Production builds can prune unreachable atoms and split exclusive utilities into lazy chunks. See Source compilation.


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

Generate the authoring surface with bamboo codegen, then check the import path, package exports, and outExtension. Resolving an import does not compile its style calls: components must still pass through Vite. See component-library builds.


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, when the set of styles is finite. Declare runtime choices as recipe variants; Bamboo compiles the possible states to a reduced decision table. Open values such as css({ color: userValue }) are rejected because they have no finite build-known CSS rule set. See Source compilation.


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

Yes. @bamboocss/vite compiles style calls to shared declaration atoms in development and production:

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

It covers css(), patterns, recipes, finite runtime recipe axes, tokens, static viewTransition() bags, and analyzable cx(). Open runtime style values fail compilation instead of retaining the style engine. See Source compilation.


Should I use atomic or config recipes ?

Under the Vite compiler both resolve to the same globally shared declaration atoms. The difference is where the recipe is declared and whether Bamboo can build a finite runtime decision table:

  • A config recipe is declared in theme.recipes under a key and can be shared through presets.
  • An inline recipe (cva) is colocated with the component.

Both support finite runtime axes under Vite. The choice is preset sharing versus colocation. See Should I use an inline or config recipe? for the full comparison table — kept there rather than duplicated here, since a copy of it in this page went stale once already.


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, tokens, utilities;
 
@layer ionic, reset, base, tokens, utilities;