Build a design system
Ship tokens, recipes, and components as one package. Apps opt in with designSystem.
You own the design system and the apps, and both run Panda. Run panda lib in the package, then point each app at it
with designSystem: '@acme/ds'.
That replaces wiring presets, importMap, and include by hand. The app reuses the styles the design system already
extracted, instead of scanning its source again.
Sharing only tokens, or styling apps that don't use Panda? See when to use each.
Choose how to distribute
| You want to… | Use | Guide |
|---|---|---|
| Share a full design system between apps that all use Panda | panda lib + designSystem | This guide |
| Share only tokens, recipes, or patterns (no components) | A preset | Presets |
| Style apps that don't use Panda | A prebuilt static CSS file | Component library |
Set up the monorepo
Keep the design system and its apps as separate packages. This walkthrough uses pnpm workspaces.
pnpm-workspace.yaml
packages:
- 'packages/*'my-design-system/
├── packages/
│ ├── ds/ # the design system
│ └── app/ # an app that consumes it
├── package.json
└── pnpm-workspace.yaml
Configure the theme
Create packages/ds/panda.config.ts. Define tokens, recipes, and patterns. Set jsxFramework if you ship JSX
components.
packages/ds/panda.config.ts
import { defineConfig } from '@pandacss/dev'
export default defineConfig({
presets: ['@pandacss/preset-base'],
include: ['src/**/*.{ts,tsx}'],
outdir: 'styled-system',
jsxFramework: 'react',
theme: {
tokens: {
colors: {
brand: { value: '#facc15' }
},
radii: {
md: { value: '0.375rem' }
}
},
recipes: {
button: {
className: 'button',
base: {
display: 'inline-flex',
alignItems: 'center',
borderRadius: 'md',
paddingInline: '4',
color: 'black',
backgroundColor: 'brand'
}
}
}
}
})Author your components
Generate styled-system with panda codegen, then build components on top of it. Import from the local
styled-system. Consumers import the components from your package name.
packages/ds/src/button/button.tsx
import type { ComponentPropsWithoutRef } from 'react'
import { button } from '../../styled-system/recipes'
export function Button(props: ComponentPropsWithoutRef<'button'>) {
return <button className={button()} {...props} />
}packages/ds/src/index.ts
export { Button } from './button/button'Set up package.json
Write the package entry, the build scripts, and a peer dependency on @pandacss/dev. Don't list styled-system exports
yourself. panda lib adds them.
packages/ds/package.json
{
"name": "@acme/ds",
"version": "1.0.0",
"type": "module",
"exports": {
".": "./src/index.ts"
},
"scripts": {
"codegen": "panda codegen",
"lib": "panda codegen && panda lib",
"watch": "panda codegen && panda lib --watch"
},
"peerDependencies": {
"@pandacss/dev": "^2.0.0",
"react": ">=18"
}
}Publish the library artifacts
pnpm --filter @acme/ds lib
panda lib writes three files under dist/panda/:
lib.jsonis the manifest a consumer resolves as@acme/ds/panda/lib.json.preset.mjsis your theme (tokens, recipes, patterns).buildinfo.jsonis the extraction result, so apps don't scan your source again.
An app never imports preset.mjs or buildinfo.json by hand. designSystem loads both.
It also syncs package.json exports. Your "." entry stays. Everything else below is added for you, and only for
categories codegen actually emitted. This walkthrough has no patterns, so ./patterns is absent.
packages/ds/package.json
{
"name": "@acme/ds",
"exports": {
".": "./src/index.ts",
"./panda/*": "./dist/panda/*",
"./css": {
"types": "./styled-system/css/index.d.ts",
"default": "./styled-system/css/index.js"
},
"./css/*": {
"types": "./styled-system/css/*.d.ts",
"default": "./styled-system/css/*.js"
},
"./helpers": {
"types": "./styled-system/helpers.d.ts",
"default": "./styled-system/helpers.js"
},
"./recipes": {
"types": "./styled-system/recipes/index.d.ts",
"default": "./styled-system/recipes/index.js"
},
"./recipes/*": {
"types": "./styled-system/recipes/*.d.ts",
"default": "./styled-system/recipes/*.js"
},
"./jsx": {
"types": "./styled-system/jsx/index.d.ts",
"default": "./styled-system/jsx/index.js"
},
"./jsx/*": {
"types": "./styled-system/jsx/*.d.ts",
"default": "./styled-system/jsx/*.js"
},
"./tokens": {
"types": "./styled-system/tokens/index.d.ts",
"default": "./styled-system/tokens/index.js"
}
}
}Don't edit those subpaths by hand. The next panda lib rewrites them.
Ship fallback files
If a consumer can't use the shipped buildinfo.json (corrupt file, schema skew), Panda re-extracts the paths listed in
lib.json as files. No files means that recovery fails closed.
panda lib infers those paths from your include globs. If package.json "files" would not publish them (a
"files": ["dist"] package whose sources live in src/), Panda drops the inferred paths and warns with
design_system_files_not_publishable. For a built-only package, pass the files you actually publish:
panda lib --files './**/*.{js,mjs}'
Build dist/panda/ before you publish. A "prepublishOnly": "panda codegen && panda lib" script keeps it fresh without
committing generated output.
Point an app at it
The app writes one field. Bundler setup, imports, extensions, and prefixes live on Consume a design system.
packages/app/panda.config.ts
export default defineConfig({
designSystem: '@acme/ds'
})Develop with watch mode
pnpm --filter @acme/ds watch # panda codegen once, then panda lib --watch
pnpm --filter @acme/app dev
panda lib --watch rewrites dist/panda/ when tokens, recipes, or components change. It does not rerun
panda codegen. If a change updates generated styled-system files, run panda codegen again (or keep a second
codegen watcher).
Version and publish
pnpm --filter @acme/ds lib
pnpm --filter @acme/ds publish
The design system and its apps must run the same Panda major. panda lib stamps that range from the package's
@pandacss/dev peer, or from --panda. A mismatch fails the consumer build with
design_system_peer_range_unsatisfied.
The manifest version field is informational. Panda does not enforce it. Use normal semver when you rename or remove
tokens.
Troubleshooting
design_system_files_not_publishablemeans inferred fallback paths would not ship in the npm tarball. Pass--filesfor the sources you actually publish, or consumers can't recover from stale build info.design_system_export_overwrittenmeanspanda libreplaced apackage.jsonexport that didn't match what it would write. Let it own the./panda/*andstyled-systemsubpaths.
Wire the app next: Consume a design system.