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.

Install the package. Add one field. Panda loads the theme, reuses the styles the package already extracted, and recognizes imports from both @acme/ds and your 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. Do not list the package in include; that errors with design_system_in_include.

Authoring the package: Build a design system. The app does not run Panda: Consume without Panda.

Add the dependency

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

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' }
        }
      }
    }
  }
})

The app value wins when both sides define the same token path. That reports design_system_token_conflict (info).

Wire up the bundler

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

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

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.

An app that adds nothing of its own can import css from @acme/ds/css instead. Both paths extract.

What the app generates

Your local styled-system re-exports what the design system already shipped, and only generates what you added.

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

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>
  )
}

A recipe or pattern both sides define reports design_system_artifact_conflict (warning). The app's copy wins. The design system's copy drops out of the re-export.

Isolate styles with a prefix

Two Panda builds emit the same class names by default (.button, --colors-brand). Set prefix when those styles load in more than one independently-built bundle.

panda.config.ts

export default defineConfig({
  designSystem: '@acme/ds',
  prefix: 'app' // .button → .app-button
})

A prefix that differs from the design system's regenerates the runtime locally. 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 instead of re-exporting. Output is correct, just not deduped.

You cannot point designSystem at two unrelated packages. Put the second package in include if it only consumes the first.

Troubleshooting

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.

See also

Edit this page on GitHubView as markdown
Last updated on