Consume with Panda
Point an app at a design system with one config field.
To point an app at a design system with one config field: install the package, then set designSystem. Panda loads its
theme, reuses the styles the package already extracted, and recognizes imports from both @acme/ds and your own local
styled-system.
panda.config.ts
export default defineConfig({
designSystem: '@acme/ds',
include: ['src/**/*.{ts,tsx}']
})Do not add the preset, importMap, or buildinfo.json by hand. designSystem already resolves all three from the
package. Do not list the package in include either. That errors with design_system_in_include, since include is
for your own app source, not the design system that generated it.
Add the dependency
Install the design system like any other package, workspace or npm:
package.json
{
"dependencies": {
"@acme/ds": "workspace:*"
},
"devDependencies": {
"@pandacss/dev": "^2.0.0",
"@pandacss/vite": "^2.0.0"
}
}Use the published version when the package is not in this repo:
{
"dependencies": {
"@acme/ds": "^1.0.0"
}
}Extend the theme
Add to the design system's theme the same way you'd extend any preset:
panda.config.ts
import { defineConfig } from '@pandacss/dev'
export default defineConfig({
designSystem: '@acme/ds',
include: ['src/**/*.{ts,tsx}'],
outdir: 'styled-system',
theme: {
extend: {
tokens: {
spacing: {
6: { value: '1.5rem' }
}
}
}
}
})If both your app and @acme/ds define spacing.6, your value wins. Panda still reports design_system_token_conflict
(info) so the collision doesn't pass silently.
Wire up the bundler
The bundler needs the Panda plugin to generate CSS for both your app and the design system's components:
vite.config.ts
import pandacss from '@pandacss/vite'
import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [pandacss(), react()]
})Using PostCSS instead of the Vite plugin? Only the plugin changes. designSystem and the imports stay the same.
postcss.config.mjs
import pandacss from '@pandacss/postcss'
export default {
plugins: [pandacss()]
}The plugin regenerates styled-system on every build, but the folder has to exist the first time the bundler resolves
your imports:
package.json
{
"scripts": {
"predev": "panda codegen",
"prebuild": "panda codegen"
}
}Add the entry CSS
Panda still needs one file declaring the cascade layer order, the same as any Panda app:
src/index.css
@layer reset, base, tokens, recipes, utilities;src/main.tsx
import { createRoot } from 'react-dom/client'
import { App } from './app'
import './index.css'
createRoot(document.getElementById('root')!).render(<App />)The bundler plugin injects the generated CSS. This file only sets layer order. See Cascade layers.
Import components and css
Use the package's components and your own css() side by side:
src/app.tsx
import { Button } from '@acme/ds'
import { css } from '../styled-system/css'
export function App() {
return (
<main className={css({ color: 'brand', p: '6' })}>
<Button>Welcome</Button>
</main>
)
}Import css from the local styled-system whenever the app extends tokens, utilities, conditions, or breakpoints. That
keeps css({ p: '6' }) typed against the merged theme, yours plus the design system's.
Added nothing of your own yet? Import css from @acme/ds/css instead. Both paths extract the same way.
What the app generates
Without this, the app and the design system would each ship their own copy of css(), cva(), and the JSX factory,
doubling that runtime in the bundle for no reason. So your local styled-system only regenerates what you actually
added, and re-exports everything else from @acme/ds:
| You author in the app | css(), cva(), cx | Recipes and patterns the design system already owns |
|---|---|---|
| Nothing | Re-export from @acme/ds | Re-export |
| Tokens, utilities, conditions, or breakpoints | Generated locally | Still re-exported |
prefix, hash, separator, jsxFramework, jsxStyleProps, or syntax | Full local tree | Full local tree |
| A nested design system (the package itself extends another) | Full local tree | Full local tree |
Add your own recipes
The app can define its own recipes alongside the ones the design system already ships:
panda.config.ts
export default defineConfig({
designSystem: '@acme/ds',
theme: {
extend: {
recipes: {
panel: {
className: 'panel',
base: { display: 'flex', flexDirection: 'column', gap: '3', p: '3' }
}
}
}
}
})src/app.tsx
import { Button } from '@acme/ds'
import { button, panel } from '../styled-system/recipes'
export function App() {
return (
<div className={panel()}>
<button className={button()}>Welcome</button>
</div>
)
}Name a recipe the design system already ships and your version wins. Panda reports design_system_artifact_conflict
(warning), and the design system's copy quietly drops out of the re-export.
Isolate styles with a prefix
Say two independently-built bundles both load on the same page, two micro-frontends, or an app plus a widget it embeds.
If both compiled from the same design system, they emit identical class names by default (.button, --colors-brand).
The cascade only keeps one. Set prefix so they stop colliding:
panda.config.ts
export default defineConfig({
designSystem: '@acme/ds',
prefix: 'app' // .button → .app-button
})A prefix that differs from the design system's own regenerates the whole runtime locally. That's the "full local tree"
row in the table above. Cross-version collisions, scoping tokens, and the limits of prefix:
Style isolation.
Nested design systems
A design system can extend another. You still write one field, the leaf package:
panda.config.ts
export default defineConfig({
designSystem: '@acme/marketing-ds'
})Panda walks the parent chain, merges presets root-first, and hydrates each layer. The app emits a full local
styled-system runtime instead of re-exporting one package's runtime.
With optimize.treeshakeDesignSystem, Panda also follows selected components through the chain. If a selected leaf
module depends on a parent component, Panda keeps the parent CSS it needs. Namespace and side-effect imports in a
selected design-system module keep all modules from that parent.
You cannot point designSystem at two unrelated packages. Put the second package in include instead if it only
consumes the first, not a design system in its own right.
Building the child package? See Extend another design system.
Troubleshooting
The codes you're most likely to actually hit wiring this up, and what to do about each:
| Code | What to do |
|---|---|
design_system_manifest_not_found / design_system_manifest_not_exported | The package has no ./panda/* export. The author runs panda lib and republishes. Then reinstall. |
design_system_export_missing | A styled-system subpath (./css, ./recipes) is missing. Same fix. |
design_system_peer_range_unsatisfied | This app's Panda major does not match the package. Upgrade them together. |
design_system_in_include | Move the package from include to designSystem. |
design_system_buildinfo_stale | Panda re-extracted fallback files. Styles stay correct. If it persists, the author republishes. |
design_system_token_conflict / design_system_artifact_conflict | Expected when you override on purpose. |
More cases: Troubleshooting.
Example
A Next.js app that installs a published system and sets designSystem:
npx degit chakra-ui/panda-examples/examples/panda-app my-apppanda-app (opens in a new tab) in panda-examples.