Panda Integration Hooks
Leveraging hooks in Panda to create custom functionality.
Panda hooks let you add new functionality or change existing behavior at specific points in the compiler lifecycle.
Hooks are callbacks you attach to named plugins in the plugins array. There is no root-level hooks object — every
hook lives on a plugin with a name and a hooks map.
import { defineConfig } from '@pandacss/dev'
export default defineConfig({
plugins: [
{
name: 'my-plugin',
hooks: {
'cssgen:done': ({ content, path }) => {
// ...
},
},
},
],
})
Here are some things you can do with hooks:
- Modify the resolved config (
config:resolved), like stripping out patterns, tokens, or keyframes. - Modify a preset after it's resolved (
preset:resolved), like removing tokens or theme properties from a preset. - Transform a source file into
tsx-friendly syntax before it's parsed (parser:before), so Panda can extract its style usage — this also lets you support templating languages Panda doesn't parse natively. - Adjust the generated JS and DTS artifacts before they're written (
codegen:prepare), or react after they're written (codegen:done). - Observe the final CSS after it's produced (
cssgen:done), for reporting or downstream tooling.
Examples
Modifying the config
Use the utils helpers on the config:resolved hook to change the resolved config. This example removes the stack
pattern.
import { defineConfig } from '@pandacss/dev'
export default defineConfig({
plugins: [
{
name: 'remove-stack-pattern',
hooks: {
'config:resolved': ({ config, utils }) => {
return utils.omit(config, ['patterns.stack'])
},
},
},
],
})
Modifying presets
Use the preset:resolved hook to change a preset after it's resolved. This is useful for filtering out parts of a
preset.
import { defineConfig } from '@pandacss/dev'
export default defineConfig({
plugins: [
{
name: 'trim-preset-colors',
hooks: {
'preset:resolved': ({ utils, preset, name }) => {
if (name === '@pandacss/preset-panda') {
return utils.omit(preset, ['theme.tokens.colors', 'theme.semanticTokens.colors'])
}
return preset
},
},
},
],
})
Transforming a source file before parsing
Use parser:before to rewrite a file's content before Panda parses it. The hook receives { filePath, content } and
returns the transformed string. Return nothing to leave the content unchanged.
This is how you support source that isn't standard tsx — pre-process it into syntax Panda's parser understands, then
return it.
import { defineConfig } from '@pandacss/dev'
export default defineConfig({
plugins: [
{
name: 'strip-directives',
hooks: {
'parser:before': ({ filePath, content }) => {
if (!filePath.endsWith('.astro')) return
return content.replace(/^---[\s\S]*?---/, '')
},
},
},
],
})
To scope a hook to specific files, pass an object with a filter and a handler instead of a bare function:
import { defineConfig } from '@pandacss/dev'
export default defineConfig({
plugins: [
{
name: 'scoped-parser',
hooks: {
'parser:before': {
filter: { id: '**/*.{jsx,tsx}' },
handler: ({ content }) => content,
},
},
},
],
})
Observing the final CSS
cssgen:done runs after the final CSS is produced, for the CLI, Vite, and PostCSS. It's observe-only — the hook
receives { artifact, content, path?, … } and its return value is ignored, so you can't rewrite the CSS here.
import { defineConfig } from '@pandacss/dev'
export default defineConfig({
plugins: [
{
name: 'css-report',
hooks: {
'cssgen:done': ({ artifact, content, path }) => {
if (artifact === 'styles.css') {
console.log(`Generated ${content.length} bytes at ${path}`)
}
},
},
},
],
})
To strip unused tokens or keyframes from the final CSS, use the top-level optimize config instead of a hook:
import { defineConfig } from '@pandacss/dev'
export default defineConfig({
optimize: {
removeUnusedTokens: true,
removeUnusedKeyframes: true,
},
})
For any other CSS transform, run PostCSS after Panda.
Note: With removeUnusedTokens, you can't rely on the JS function
token.var (or token(xxx)
where xxx is a semanticToken path) from styled-system/tokens, because the
CSS variables are removed based on the usage found in the generated CSS.
Sharing hooks
Hooks are shared as plugins. A plugin is a plain object with a name and a hooks object.
Plugins differ from presets in that they can't be extended, but they run in sequence in the order they appear in the
plugins array, with the user's own config called last.
import { defineConfig } from '@pandacss/dev'
const myPlugin = {
name: 'strip-stack',
hooks: {
'config:resolved': ({ config, utils }) => {
return utils.omit(config, ['patterns.stack'])
},
},
}
export default defineConfig({
plugins: [myPlugin],
})
Reference
export interface PandaHooks {
/**
* Called after authored presets are merged, before defaults and serialization.
*/
'config:resolved': (args: ConfigResolvedHookArgs) => MaybeAsyncReturn<void | Config>
/**
* Called when an authored preset is resolved, before all configs are merged.
*/
'preset:resolved': (args: PresetResolvedHookArgs) => MaybeAsyncReturn<void | Config>
/**
* Called after reading file content but before parsing it.
* Use this to transform non-standard source into TSX-friendly syntax.
*/
'parser:before': (args: ParserResultBeforeHookArgs) => MaybeAsyncReturn<string | void>
/**
* Called before generated files are written by a JS host.
*/
'codegen:prepare': (args: CodegenPrepareHookArgs) => void | CodegenPrepareArtifact[]
/**
* Called after generated files are written by a JS host.
*/
'codegen:done': (args: CodegenDoneHookArgs) => void
/**
* Called after final CSS is produced by a JS host (observe-only; no rewrite).
* Fires for CLI, Vite, and PostCSS string sinks. Use `optimize` or PostCSS to mutate CSS.
*/
'cssgen:done': (args: CssgenDoneHookArgs) => void
}
Each hook can be a plain function or an object with a filter and a handler, which is useful for scoping
parser:before to specific files:
export type PandaHook<Handler> = Handler | { filter?: HookFilter; handler: Handler }
The argument types are:
export interface ConfigResolvedHookArgs {
config: Config
path: string
dependencies: string[]
utils: ConfigResolvedHookUtils
}
export interface PresetResolvedHookArgs {
preset: Config
name: string
utils: ConfigResolvedHookUtils
}
export interface ConfigResolvedHookUtils {
omit<T extends object>(obj: T, paths: string[]): T
pick<T extends object>(obj: T, paths: string[]): Partial<T>
traverse(obj: unknown, callback: (item: TraverseItem) => void, options?: TraverseOptions): void
}
export interface ParserResultBeforeHookArgs {
filePath: string
content: string
original?: string
}
export interface CodegenPrepareHookArgs {
artifacts: CodegenPrepareArtifact[]
outdir: string
cwd?: string
}
export interface CodegenDoneHookArgs {
files: string[]
outdir: string
cwd?: string
}
export interface CssgenDoneHookArgs {
artifact: 'styles.css' | 'styles.layer' | 'styles.split'
content: string
/** Absolute path when written to disk; omitted for string sinks (Vite/PostCSS). */
path?: string
outfile?: string
outdir?: string
cwd?: string
manifest?: CssgenDoneManifest
layerRanges?: CssgenDoneLayerRanges
}