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

design systems
consuming a design system

Consume a design system

Point an app at a Panda design system with one config field.

Install the package. Add one field:

panda.config.ts

export default defineConfig({
  designSystem: '@acme/ds'
})

Panda loads the package's theme, reuses the styles it already extracted, and recognizes imports from both @acme/ds and your local styled-system. You don't add the preset, importMap, or the library's build info by hand.

If you're authoring the package, start at Build a design system.

Add the dependency

package.json

{
  "dependencies": {
    "@acme/ds": "workspace:*"
  },
  "devDependencies": {
    "@pandacss/dev": "^2.0.0",
    "@pandacss/vite": "^2.0.0"
  }
}

Use the published version on npm when the design system isn't in the same repo.

panda.config.ts

import { defineConfig } from '@pandacss/dev'
 
export default defineConfig({
  designSystem: '@acme/ds',
  include: ['src/**/*.tsx'],
  outdir: 'styled-system',
  theme: {
    extend: {
      tokens: {
        spacing: {
          6: { value: '1.5rem' }
        }
      }
    }
  }
})

theme.extend layers on top of the design system's theme. The app value wins when both sides define the same token path.

Wire up the bundler

With Vite, add the @pandacss/vite plugin. It runs codegen and injects CSS. transform: true is optional: it inlines static css() calls at build time. See the framework guides for other setups.

vite.config.ts

import pandacss from '@pandacss/vite'
import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite'
 
export default defineConfig({
  plugins: [pandacss(), react()]
})

If the app runs Panda through PostCSS instead, only the PostCSS config changes. designSystem, the entry CSS, 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. Run panda codegen (or panda build) in a predev/prebuild script so a fresh checkout builds — the example apps (opens in a new tab) are wired this way. To emit CSS without the folder at all, use panda cssgen.

Add the entry CSS

Panda emits into cascade layers. Declare their order once and import that file at the app root. The bundler plugin injects the generated CSS. This file only sets layer order.

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

Import components and css

Import components from the design system package. Import css from your local styled-system whenever the app extends tokens, utilities, conditions, or breakpoints. That keeps css({ p: '6' }) typed against the merged theme.

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

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. Authoring a field means you wrote it in this config (or a non-design-system preset), not that you inherited it.

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

Author them under theme.extend. Import both your recipes and the design system's from the local styled-system.

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

Override a design system value

Write your own definition of the token, recipe, or pattern. It merges over the original and wins.

  • A token path both sides define reports design_system_token_conflict (info).
  • A recipe or pattern both sides define reports design_system_artifact_conflict (warning). The design system's copy drops out of the re-export in favor of yours.

Isolate styles with a prefix

Two Panda builds emit the same class names by default, like .button and --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, --colors-brand → --app-colors-brand
})

Pick the prefix by the app's identity, not by design system version. A prefix that differs from the design system's regenerates the runtime locally.

Nested design systems

A design system can extend another. You still write one field, the leaf package:

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, or give it its own parent link if it is a real child design system.

Troubleshooting

  • design_system_manifest_not_found / design_system_manifest_not_exported means the installed package has no ./panda/* export. The author needs to run panda lib and republish. Then reinstall.
  • design_system_export_missing means a styled-system subpath the app's generated files import (./css, ./recipes, …) is missing from the package exports. Same fix: rebuild with panda lib.
  • design_system_peer_range_unsatisfied means this app's Panda major doesn't match the design system's. Upgrade them together.
  • design_system_buildinfo_stale means Panda re-extracted the design system's fallback files because buildinfo.json was unusable. Styles stay correct. If the warning persists after a package update, the author needs to republish. If the build errors instead, the manifest has no files and recovery is disabled.
  • design_system_token_conflict / design_system_artifact_conflict are expected when you override on purpose.

The example apps (opens in a new tab) show one design system shipped three ways: a monorepo that builds the system and a Next.js app together, an app that installs the published CSS and components with no Panda, and an app that runs Panda and extends the system's tokens. Grab any one with degit.