references
config

Configuring Bamboo

Customize how Bamboo works via the `bamboo.config.ts` file in your project.

Customize how Bamboo works via the bamboo.config.ts file in your project.

import { defineConfig } from '@bamboocss/dev'
 
export default defineConfig({
  // your configuration options here...
})

Output css options

presets

Type: string[]

Default: ['@bamboocss/preset-base', '@bamboocss/preset-bamboo']

The set of reusable and shareable configuration presets.

By default, any preset you add will be smartly merged with the default configuration, with your own configuration acting as a set of overrides and extensions.

{
  "presets": ["@bamboocss/preset-base", "@bamboocss/preset-bamboo"]
}

eject

Type: boolean

Default: false

Whether to opt-out of the defaults config presets: [@bamboocss/preset-base, @bamboocss/preset-bamboo]

{
  "eject": true
}

preflight

Type: boolean | { scope: string; }

Default: false

Whether to enable css reset styles. See also Global styles for how reset interacts with global variables and layering.

Enable preflight:

{
  "preflight": true
}

You can also scope the preflight; Especially useful for being able to scope the CSS reset to only a part of the app for some reason.

Enable preflight and customize the scope:

{
  "preflight": { "scope": ".extension" }
}

The resulting reset css would look like this:

.extension button,
.extension select {
  text-transform: none;
}
 
.extension table {
  text-indent: 0;
  border-color: inherit;
  border-collapse: collapse;
}

You can also set the level to element (defaults to parent) to only reset the elements that have the scope class assigned.

{
  "preflight": { "scope": ".extension", "level": "element" }
}

The resulting reset css would look like this:

button.extension,
select.extension {
  text-transform: none;
}
 
table.extension {
  text-indent: 0;
  border-color: inherit;
  border-collapse: collapse;
}

emitTokensOnly

Type: boolean

Default: false

Whether to only emit the tokens directory

{
  "emitTokensOnly": false
}

prefix

Type: string

The namespace prefix for the generated css classes and css variables.

Ex: when using a prefix of bamboo-

{
  "prefix": "bamboo"
}
import { css } from '../styled-system/css'
 
const App = () => {
  return <div className={css({ color: 'blue.500' })} />
}

would result in:

.bamboo-text_blue\.500 {
  color: var(--bamboo-colors-blue-500);
}

layers

Type: Partial<Layer>

Cascade layers used in generated css.

Ex: when customizing the utilities layer

{
  "layers": {
    "utilities": "bamboo_utilities"
  }
}
import { css } from '../styled-system/css'
 
const App = () => {
  return <div className={css({ color: 'blue.500' })} />
}

would result in:

@layer bamboo_utilities {
  .text_blue\.500 {
    color: var(--colors-blue-500);
  }
}

You should update the layer in your root css also.

separator

Type: '_' | '=' | '-'

Default: '_'

The separator for the generated css classes.

{
  "separator": "_"
}

Using a = with:

import { css } from '../styled-system/css'
 
const App = () => {
  return <div className={css({ color: 'blue.500' })} />
}

would result in:

.text\=blue\.500 {
  color: var(--colors-blue-500);
}

minify

Type: boolean

Default: false

Whether to minify the generated css. This can be set to true to reduce the size of the generated css.

{
  "minify": false
}

hash

Type: boolean | { cssVar: boolean; className: boolean }

Default: false

Whether to hash the generated class names / css variables. This is useful if want to shorten the class names or css variables.

Hash the class names and css variables:

{
  "hash": true
}

This

import { css } from '../styled-system/css'
 
const App = () => {
  return <div className={css({ color: 'blue.500' })} />
}

would result in something that looks like:

.dOFUTE {
  color: var(--cgpxvS);
}

You can also hash them individually.

E.g. only hash the css variables:

{
  "hash": { "cssVar": true, "className": false }
}

Then the result looks like this:

.text_blue\.500 {
  color: var(--cgpxvS);
}

Now only hash the class names:

{
  "hash": { "cssVar": false, "className": true }
}

Then the result looks like this:

.dOFUTE {
  color: var(--colors-blue-500);
}

cssMode

Type: 'atomic' | 'grouped'

Default: 'atomic'

How utility classes are generated. atomic emits one class per property; grouped emits one class per css() call with every property inside it.

{
  "cssMode": "grouped"
}

This

css({ color: 'red', fontSize: '14px', padding: '8px' })

produces three classes under atomic and one under grouped:

.cmmQjj {
  padding: 8px;
  color: red;
  font-size: 14px;
}

The trade is CSS size for HTML size. Atomic dedupes a shared declaration into one class no matter how many call sites use it; grouped repeats it in every group, but the markup carries one class per element instead of a dozen and the browser has fewer declarations to walk during style recalculation. That favours SSR and SSG, where the HTML is rarely cached and arrives on every initial render. hash shortens class names rather than reducing their count, so the two address different costs.

Cascade layers, cva, recipes and patterns are unaffected. Note that @bamboocss/vite declines element splitting under grouped, since that optimization builds a class from a prefix and a value.

File system options

gitignore

Type: boolean

Default: true

Whether to update the .gitignore file.

{
  "gitignore": true
}

Will add the following to your .gitignore file:

# Bamboo
styled-system
styled-system-static

cwd

Type: string

Default: process.cwd()

The current working directory.

{
  "cwd": "src"
}

clean

Type: boolean

Default: false

Whether to clean the output directory before generating the css.

{
  "clean": false
}

outdir

Type: string

Default: styled-system

The output directory for the generated css.

{
  "outdir": "styled-system"
}

importMap

Type: string | Partial<OutdirImportMap>

Default: { "css": "styled-system/css", "recipes": "styled-system/recipes", "patterns": "styled-system/patterns", "jsx": "styled-system/jsx" }

Allows you to customize the import paths for the generated outdir.

{
  "importMap": {
    "css": "@acme/styled-system",
    "recipes": "@acme/styled-system",
    "patterns": "@acme/styled-system",
    "jsx": "@acme/styled-system"
  }
}

You can also use a string to customize the base import path and keep the default entrypoints:

{
  "importMap": "@scope/styled-system"
}

is the equivalent of:

{
  "importMap": {
    "css": "@scope/styled-system/css",
    "recipes": "@scope/styled-system/recipes",
    "patterns": "@scope/styled-system/patterns",
    "jsx": "@scope/styled-system/jsx"
  }
}

Check out the Component Library guide for more information on how to use the importMap option.

include

Type: string[]

Default: []

List of files glob to watch for changes.

{
  "include": ["./src/**/*.{js,jsx,ts,tsx}", "./pages/**/*.{js,jsx,ts,tsx}"]
}

exclude

Type: string[]

Default: []

List of files glob to ignore.

{
  "exclude": []
}

dependencies

Type: string[]

Default: []

Explicit list of config related files that should trigger a context reload on change.

💡

We automatically track the config file and (transitive) files imported by the config file as much as possible, but sometimes we might miss some. You can use this option as a workaround for those edge cases.

{
  "dependencies": ["path/to/files/**.ts"]
}

watch

Type: boolean

Default: false

Whether to watch for changes and regenerate the css.

{
  "watch": false
}

poll

Type: boolean

Default: false

Whether to use polling instead of filesystem events when watching.

{
  "poll": false
}

outExtension

Type: 'mjs' | 'js'

Default: mjs

File extension for generated javascript files.

{
  "outExtension": "mjs"
}

forceConsistentTypeExtension

Type: boolean

Default: false

Whether to force consistent type extensions for generated typescript .d.ts files.

If set to true and outExtension is set to mjs, the generated typescript .d.ts files will have the extension .d.mts.

{
  "forceConsistentTypeExtension": true
}

syntax

Type: 'object-literal' | 'template-literal'

Default: object-literal

Decides which syntax to use when writing CSS. For existing projects, you might need to run the bamboo codegen --clean.

{
  "syntax": "template-literal"
}

Ex object-literal:

const styles = css({
  backgroundColor: 'gainsboro',
  padding: '10px 15px',
})

Ex template-literal:

const Container = styled.div`
  background-color: gainsboro;
  padding: 10px 15px;
`

lightningcss

Type: boolean

Default: false

Whether to use lightningcss instead of postcss for css optimization.

{
  "lightningcss": true
}

browserslist

Type: string[]

Default: []

Browserslist query to target specific browsers. Only used when lightningcss is set to true.

{
  "browserslist": ["last 2 versions", "not dead", "not < 2%"]
}

polyfill

Type: boolean

Default: false

Polyfill CSS @layers at-rules for older browsers.

{
  "polyfill": true
}

Design token options

shorthands

Type: boolean

Default: true

Whether to allow shorthand properties

{
  "shorthands": true
}

Ex true:

const styles = css({
  bgColor: 'gainsboro',
  p: '10px 15px',
})

Ex false:

const styles = css({
  backgroundColor: 'gainsboro',
  padding: '10px 15px',
})

pruneUnusedTokens

Type: boolean

Default: false

Whether to drop token css variables that nothing in the generated css can reach.

The token layer declares every token in your theme, and an app typically uses a small fraction of them, so this is usually the largest single saving in render-blocking css. It scales with the size of your design system rather than the size of your app.

{
  "pruneUnusedTokens": true
}

A variable is kept when the generated css references it, when a kept variable's own value references it, or when it is named by token(), token.var(), or a literal var(--x) anywhere under include. Anything a theme refers to is kept as well, since themes ship as separate artifacts injected at runtime.

It is opt-in because reachability cannot be proven for every reference. Three things stay invisible to it:

  • A token named by a path your source does not spell out as a string literal, such as token.var(key).
  • A token referenced only from a stylesheet outside include.
  • A token consumed by a separate package that treats your output as design tokens.

Use staticCss to keep those.

One cost is worth knowing about. Tokens that javascript receives as a var() rather than a literal are always kept, so that token() answers correctly for any path at runtime — that covers virtual tokens, any token carrying a condition, and negative tokens. A negative token resolves to calc(var(--spacing-4) * -1), so every token with a negative counterpart pins its own declaration. Spacing scales generate one negative per entry, which keeps the whole scale whether or not your app uses it. On the default preset that is roughly a third of what survives pruning, and there is no opt-out.

pruneUnusedKeyframes

Type: boolean

Default: false

Whether to drop @keyframes rules nothing in the generated css can reach.

A preset declares every animation it offers and an app uses a handful; the rest sit in the same render-blocking stylesheet. Like pruneUnusedTokens, the saving scales with the size of your design system rather than the size of your app. The two are independent — either can be switched on without the other.

{
  "pruneUnusedKeyframes": true
}

Only keyframes your theme declares are ever removed, so one emitted by globalCss is left alone. A name is kept when an animation property in the generated css names it, when a theme names it, when it is named by a custom property that is itself reachable, or when it appears anywhere under include.

That last clause is a textual scan, and it is deliberately over-inclusive: it covers an animation name assembled at runtime or handed to an inline style rather than to Bamboo, neither of which the css can show. A name matching a word in unrelated prose keeps its keyframe alive, which is the cheap failure.

Names are recovered by testing each token in a value against the set your theme declares, rather than by parsing the animation shorthand — whose parts come in any order. A keyframe named after a keyword such as none or ease therefore always looks referenced. The bias is intentional throughout: keeping an unused keyframe costs bytes, dropping a used one silently stops an animation.

cssVarRoot

Type: string

Default: :where(:host, :root)

The root selector for the css variables.

{
  "cssVarRoot": ":where(:host, :root)"
}

conditions

Type: Extendable<Conditions>

Default: {}

The css selectors or media queries shortcuts.

{
  "conditions": { "hover": "&:hover" }
}

globalCss

Type: Extendable<GlobalStyleObject>

Default: {}

The global styles for your project.

{
  "globalCss": {
    "html, body": {
      "margin": 0,
      "padding": 0
    }
  }
}

theme

Type: Extendable<Theme>

Default: {}

The theme configuration for your project.

{
  "theme": {
    "tokens": {
      "colors": {
        "red": { "value": "#EE0F0F" },
        "green": { "value": "#0FEE0F" }
      }
    },
    "semanticTokens": {
      "colors": {
        "danger": { "value": "{colors.red}" },
        "success": { "value": "{colors.green}" }
      }
    }
  }
}

themes

Type: Extendable<ThemeVariantsMap>

Default: {}

The theme variants configuration for your project.

{
  "themes": {
    "primary": {
      "tokens": {
        "colors": {
          "text": { "value": "red" }
        }
      },
      "semanticTokens": {
        "colors": {
          "muted": { "value": "{colors.red.200}" },
          "body": {
            "value": {
              "base": "{colors.red.600}",
              "_osDark": "{colors.red.400}"
            }
          }
        }
      }
    },
    "secondary": {
      "tokens": {
        "colors": {
          "text": { "value": "blue" }
        }
      },
      "semanticTokens": {
        "colors": {
          "muted": { "value": "{colors.blue.200}" },
          "body": {
            "value": {
              "base": "{colors.blue.600}",
              "_osDark": "{colors.blue.400}"
            }
          }
        }
      }
    }
  }
}

utilities

Type: Extendable<UtilityConfig>

Default: {}

The css utility definitions.

{
  "utilities": {
    extend: {
      borderX: {
        values: ['1px', '2px', '4px'],
        shorthand: 'bx', // `bx` or `borderX` can be used
        transform(value, token) {
          return {
            borderInlineWidth: value,
            borderColor: token('colors.red.200'), // read the css variable for red.200
          }
        },
      },
    },
  }
}

patterns

Type: Extendable<Record<string, AnyPatternConfig>>

Default: {}

Common styling or layout patterns for your project.

{
  "patterns": {
    extend: {
      // Extend the default `flex` pattern
      flex: {
        properties: {
          // only allow row and column
          direction: { type: 'enum', value: ['row', 'column'] },
        },
      },
    },
  },
}

staticCss

Type: StaticCssOptions

Default: {}

Used to generate css utility classes for your project.

{
  "staticCss": {
    css: [
      {
        properties: {
          margin: ['*'],
          padding: ['*', '50px', '80px'],
        },
        responsive: true,
      },
      {
        properties: {
          color: ['*'],
          backgroundColor: ['green.200', 'red.400'],
        },
        conditions: ['light', 'dark'],
      },
    ],
  },
}

strictTokens

Type: boolean

Default: false

Only allow token values and prevent custom or raw CSS values. Will only affect properties that have config tokens, such as color, bg, borderColor, etc. Learn more.

{
  "strictTokens": false
}

strictPropertyValues

Type: boolean

Default: false

Only use valid CSS values for properties that do have a predefined list of values. Will throw for properties that do not have config tokens, such as display, content, willChange, etc. Learn more.

{
  "strictPropertyValues": false
}

globalFontface

Type: GlobalFontfaceDefinition

Default: {}

Global font face definitions.

{
  "globalFontface": {
    "Inter": {
      "src": "url(/fonts/inter.woff2) format('woff2')",
      "fontWeight": "400",
      "fontStyle": "normal"
    },
    "Roboto": {
      "src": "url(/fonts/roboto.woff2) format('woff2')",
      "fontWeight": "400",
      "fontStyle": "normal"
    }
  }
}

Check out the Custom Fonts guide for more information on how to use the globalFontface option.

JSX options

jsxFramework

Type: 'react' | 'solid' | 'preact' | 'vue' | 'qwik' | (string & {})

JS Framework for generated JSX elements.

{
  "jsxFramework": "react"
}

jsxFactory

Type: string

The factory name of the element

{
  "jsxFactory": "bamboo"
}

Ex:

<bamboo.button marginTop="40px">Click me</bamboo.button>

jsxStyleProps

Type: all | minimal | none

Default: all

The style props allowed on generated JSX components

  • When set to 'all', all style props are allowed.
  • When set to 'minimal', only the css prop is allowed.
  • When set to 'none', no style props are allowed and therefore the jsxFactory will not be usable as a component:
    • <styled.div /> and styled("div") aren't valid
    • but the recipe usage is still valid styled("div", { base: { color: "red.300" }, variants: { ...} })

Ex with 'all':

<styled.button marginTop="40px">Click me</styled.button>

Ex with 'minimal':

<styled.button css={{ marginTop: '40px' }}>Click me</styled.button>

Ex with 'none':

<button className={css({ marginTop: '40px' })}>Click me</button>

Documentation options

studio

Type: Partial<Studio>

Default: { title: 'Bamboo', logo: '🎋' }

Used to customize the design system studio

{
  "studio": {
    "logo": "🎋",
    "title": "Bamboo"
  }
}

log level

Type: 'debug' | 'info' | 'warn' | 'error' | 'silent'

Default: info

The log level for the built-in logger.

{
  "logLevel": "info"
}

validation

Type: 'none' | 'warn' | 'error'

Default: warn

The validation strictness to use when validating the config.

  • When set to 'none', no validation will be performed.
  • When set to 'warn', warnings will be logged when validation fails.
  • When set to 'error', errors will be thrown when validation fails.
{
  "validation": "error"
}

Other options

Hooks

Type: BambooHooks

Bamboo provides a set of callbacks that you can hook into for more advanced use cases. Check the Hooks docs for more information.

Plugins

Type: BambooPlugin[]

Plugins are currently simple objects that contain a name and a hooks object with the same structure as the hooks object in the config.

They will be called in sequence in the order they are defined in the plugins array, with the user's config called last.

import { defineConfig } from '@bamboocss/dev'
 
export default defineConfig({
  // ...
  plugins: [
    {
      name: 'token-format',
      hooks: {
        'tokens:created': ({ configure }) => {
          configure({
            formatTokenName: (path) => '$' + path.join('-'),
          })
        },
      },
    },
  ],
})