Using Bamboo in a Component Library
How to set up Bamboo in a component library
When creating a component library that uses Bamboo which can be used in a variety of different projects, you have four options:
- Ship a Bamboo preset
- Ship a static CSS file
- Use Bamboo as external package, and ship the src files
- Use Bamboo as external package, and ship the build info file
In the examples below, we use tsdown as the build tool. You can use any other build tool.
Recommendations
- If your library code shouldn't be published on npm and App code uses Bamboo, use ship build info approach
- If your app code doesn't use Bamboo, use the static css file approach
- If your app code lives in a monorepo, use the include src files approach
- If your library code doesn't ship components but only ships tokens, patterns or recipes, use the ship preset approach
⚠️ If you use the include src files or ship build info approach, you might also need to ship a preset if your
library code has any custom tokens, patterns or recipes.
Ship a Bamboo Preset
This is the simplest approach. You can include the token, semantic tokens, text styles, etc. within a preset and consume them in your projects.
Library code
src/index.ts
import { definePreset } from '@bamboocss/dev'
export const acmePreset = definePreset({
theme: {
extend: {
tokens: {
colors: { primary: { value: 'blue.500' } },
},
},
},
})Build the preset code
pnpm tsdown src/index.ts
App code
bamboo.config.ts
import { acmePreset } from '@acme-org/bamboo-preset'
import { defineConfig } from '@bamboocss/dev'
export default defineConfig({
//...
presets: ['@bamboocss/dev/presets', acmePreset],
})Adding a preset will remove the default theme from Bamboo. To add it back, you need to include the
@bamboocss/dev/presets preset.
Ship a Static CSS File
This approach involves extracting the static css of your library at build time. Then you can import the CSS file in your app code.
Library code
src/index.tsx
import { css } from '../styled-system/css'
export function Button({ children }) {
return (
<button type="button" className={css({ bg: 'red.300', px: '2', py: '3' })}>
{children}
</button>
)
}Then you can build the library code and generate the static CSS file:
# build the library code
tsdown src/index.tsx
# generate the static CSS file
bamboo cssgen --outfile dist/styles.css
Finally, don't forget to include the cascade layers as well in your app code:
App code
src/App.tsx
import { Button } from '@acme-org/design-system'
import './main.css'
export function App() {
return <Button>Click me</Button>
}main.css
src/main.css
@layer reset, base, tokens, recipes, utilities;
@import url('@acme-org/design-system/dist/styles.css');
/* Your own styles here */This approach comes with a few downsides:
-
You can't customize the styles since the css is already generated
-
You might need add the prefix option to avoid className conflicts
bamboo.config.ts
import { defineConfig } from '@bamboocss/dev' export default defineConfig({ //... prefix: 'acme', }) -
You might have duplicate CSS classes when using multiple atomic css libraries
Use Bamboo as external package
Summary
- create a Bamboo preset so that you (and your users) can share the same design system tokens
- create a workspace package for your outdir (
@acme-org/styled-system) and use that package name as theimportMapin your app code - have your component library (
@acme-org/components) use the@acme-org/styled-systempackage as external
Let's make a dedicated workspace package for your outdir:
- Create a new directory
packages/styled-system(or any other name) - Install
@bamboocss/devas a dev dependency - Run the
bamboo initcommand in there to generate abamboo.config.tsfile, don't forget to set thejsxFrameworkif needed - [optional] you might want to install and import your preset in this
bamboo.config.tsfile as well - Run the
bamboo emit-pkgcommand to set the entrypoints inexports(opens in a new tab)
This should look similar to this:
{
"name": "@acme-org/styled-system",
"version": "1.0.0",
"exports": {
"./css": {
"types": "./css/index.d.ts",
"require": "./css/index.mjs",
"import": "./css/index.mjs"
},
"./tokens": {
"types": "./tokens/index.d.ts",
"require": "./tokens/index.mjs",
"import": "./tokens/index.mjs"
},
"./types": {
"types": "./types/index.d.ts",
"require": "./types/index.mjs",
"import": "./types/index.mjs"
},
"./patterns": {
"types": "./patterns/index.d.ts",
"require": "./patterns/index.mjs",
"import": "./patterns/index.mjs"
},
"./recipes": {
"types": "./recipes/index.d.ts",
"require": "./recipes/index.mjs",
"import": "./recipes/index.mjs"
},
"./jsx": {
"types": "./jsx/index.d.ts",
"require": "./jsx/index.mjs",
"import": "./jsx/index.mjs"
},
"./styles.css": "./styles.css"
},
"devDependencies": {
"@bamboocss/dev": "^1.4.2",
"@types/react": "19.2.2",
"react": "^19.2.0"
},
"peerDependencies": {
"react": ">=19"
}
}
Notice that we've included the react and its corresponding @types/react in the devDependencies and
peerDependencies. This is to ensure the types are correctly set up in the styled-system package.
Going forward, you'll now import the functions from the @acme-org/styled-system monorepo package.
import { css } from '@acme-org/styled-system/css'
export function Button({ children }) {
return (
<button type="button" className={css({ bg: 'red.300', px: '2', py: '3' })}>
{children}
</button>
)
}
App code
Install the newly created @acme-org/styled-system package in your app code.
pnpm add @acme-org/styled-system
Configure the importMap in your bamboo.config.ts to match the name field of your outdir package.json. This will
inform Bamboo which imports belong to the styled-system.
bamboo.config.ts
import { defineConfig } from '@bamboocss/dev'
export default defineConfig({
//...
importMap: '@acme-org/styled-system',
outdir: 'styled-system',
})Mark the @acme-org/styled-system as an external package in your library build tool. This ensures that the generated JS
runtime code is imported only once, avoiding duplication.
tsdown src/index.tsx --external @acme-org/styled-system
Include the src files
Include the src directory from the library code in the bamboo config.
bamboo.config.ts
import { defineConfig } from '@bamboocss/dev'
export default defineConfig({
//...
include: ['../@acme-org/design-system/src/**/*.tsx', './src/**/*.{ts,tsx}'],
importMap: '@acme-org/styled-system',
outdir: 'styled-system',
})Ship the build info file
This approach is similar to the previous one, but instead of shipping the source code, you ship the Bamboo build info
file. This will have the exact same end-result as adding the sources files in the include, but it will allow you
not to ship the source code.
The build info file is a JSON file that only contains the information about the static extraction result, you still need to ship your app build/dist by yourself. It can be used by Bamboo to generate CSS classes without the need for parsing the source code.
Generate the build info file:
bamboo ship --outfile dist/bamboo.buildinfo.json
App code
Install the newly created @acme-org/styled-system package in your app code.
pnpm add @acme-org/styled-system
Configure the importMap in your bamboo.config.ts to match the name field of your outdir package.json. This will
inform Bamboo which imports belong to the styled-system.
bamboo.config.ts
import { defineConfig } from '@bamboocss/dev'
export default defineConfig({
//...
importMap: '@acme-org/styled-system',
outdir: 'styled-system',
})Will allow imports like:
import { css } from '@acme-org/styled-system/css'
import { button } from '@acme-org/styled-system/recipes'
Next, you need to include the build info file from the library code in the bamboo config.
bamboo.config.ts
import { defineConfig } from '@bamboocss/dev'
export default defineConfig({
//...
include: ['./node_modules/@acme-org/design-system/dist/bamboo.buildinfo.json', './src/**/*.{ts,tsx}'],
importMap: '@acme-org/styled-system',
outdir: 'styled-system',
})FAQ
Why should my component library use an external package styled-system?
By de-coupling the component library from the styled-system, your users can share the same runtime code between your
library and their app code.
component-lib/src/button.tsx
import { css } from '@acme-org/styled-system/css'
export function Button({ children, css: cssProp }) {
return (
<button type="button" className={css({ bg: 'red.300', px: '2', py: '3' }, cssProp)}>
{children}
</button>
)
}app/src/App.tsx
import { Button } from '@acme-org/design-system'
import { css } from '@acme-org/styled-system/css'
export function App() {
return <Button css={{ color: 'white' }}>Click me</Button>
}Marking the styled-system as an external package in your build tool means that the generated JS runtime code (the
css function is the example above) is imported only once, avoiding duplication.
How do I use the @acme-org/styled-system package ?
You can use your monorepo workspace package @acme-org/styled-system just like any other dependency in your app or
component library code.
pnpm add @acme-org/styled-system
Set the importMap in your bamboo.config.ts to that same package name. This will inform Bamboo which imports belong
to the styled-system.
bamboo.config.ts
import { defineConfig } from '@bamboocss/dev'
export default defineConfig({
//...
importMap: '@acme-org/styled-system',
})Then you can import the functions from the @acme-org/styled-system monorepo package.
import { css } from '@acme-org/styled-system/css'
export function Button({ children }) {
return (
<button type="button" className={css({ bg: 'red.300', px: '2', py: '3' })}>
{children}
</button>
)
}
How to override tokens used by the @acme-org/styled-system package?
You can override the tokens used by the @acme-org/styled-system package by extending the theme in your
bamboo.config.ts file.
bamboo.config.ts
import { defineConfig } from '@bamboocss/dev'
export default defineConfig({
//...
presets: ['@acme-org/preset']
theme: {
extend: {
tokens: {
colors: { primary: { value: 'blue.500' } }
}
}
}
})Troubleshooting
-
When using
tsdownor any other build tool for your component library, if you run into a module resolution error that looks similar toERROR: Could not resolve "../styled-system/xxx". Consider setting theoutExtensionin the bamboo config tojs -
If you use Yarn PnP, you might need to set the
nodeLinker: node-modulesin the.yarnrc.ymlfile.