Skip to content

Want to skip the docs? Check out pandamastery.com - the best way to learn Panda CSS

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 appcss(), cva(), cxRecipes and patterns the design system already owns
NothingRe-export from @acme/dsRe-export
Tokens, utilities, conditions, or breakpointsGenerated locallyStill re-exported
prefix, hash, separator, jsxFramework, jsxStyleProps, or syntaxFull local treeFull local tree
A nested design system (the package itself extends another)Full local treeFull 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:

CodeWhat to do
design_system_manifest_not_found / design_system_manifest_not_exportedThe package has no ./panda/* export. The author runs panda lib and republishes. Then reinstall.
design_system_export_missingA styled-system subpath (./css, ./recipes) is missing. Same fix.
design_system_peer_range_unsatisfiedThis app's Panda major does not match the package. Upgrade them together.
design_system_in_includeMove the package from include to designSystem.
design_system_buildinfo_stalePanda re-extracted fallback files. Styles stay correct. If it persists, the author republishes.
design_system_token_conflict / design_system_artifact_conflictExpected 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-app

panda-app (opens in a new tab) in panda-examples.

See also

Edit this page on GitHubView as markdown
Last updated on