defineChakraConfig
The function that writes your Panda config, so one run produces your stylesheet, your styled-system and its types
defineChakraConfig() is the one part of theming here that is not Panda’s API. It replaces Panda’s
defineConfig — it returns the same object, so it is still a plain panda.config.ts default export:
import { defineChakraConfig } from "@chakra-ui-solid/panda-preset";
export default defineChakraConfig({
include: ["./node_modules/chakra-ui-solid/dist/panda.buildinfo.json", "./src/**/*.{ts,tsx}"],
outdir: "styled-system-app",
});It exists for two jobs, and both are about failures Panda gives you no other guard against.
Four keys decide what Panda extracts — not what it names, what it sees. Set differently, they
produce a stylesheet that is missing rules nobody asked Panda for: every <chakra.button> in your
source, every style prop, or Chakra’s palette merged with Panda’s. Nothing errors, and the page is
simply unstyled. Those four are a type error to pass.
And three keys have no merging seam. A config’s presets, theme and staticCss replace a
preset’s rather than adding to them, so this function does the merge for you — including the one
Panda’s own documentation gets wrong, which is staticCss below.
You never spread the return value, so there is no shallow-spread hazard on top of either.
The naming knobs — hash, prefix, separator — are yours, and freely. One panda codegen run
writes your stylesheet, the styled-system/chakra-system.ts you hand <ChakraProvider>, and the
declarations that type them, so a name can only ever disagree with itself.
What it sets
Four keys, and none of them are yours to choose:
| Key | Value | Why |
|---|---|---|
eject | true | Drops @pandacss/preset-panda, Panda’s default theme, whose palette disagrees with Chakra’s about colors.gray.* — leaving a theme that is neither, with nothing to say so. You keep every utility: the preset declares @pandacss/preset-base itself |
jsxFramework | "solid" | Extracts style props from any capitalized JSX component, which is why <Box p="4"> in your source produces a rule at all |
jsxFactory | "chakra" | A different knob, and the one with the silent failure: chakra.button is lowercase, so without it Panda’s isUpperCase fallback declines the tag and every <chakra.button bg="…"> you write emits nothing |
importMap | our packages, and your outdir | Registers the chakra factory. Panda takes a factory only from an import whose name is the jsxFactory and whose module is on this list, so unset, <chakra.div bg="…"> emits nothing either. Your own outdir stays on the map beside ours, so import { flex } from "./styled-system-app/patterns" in your source extracts as it would in any Panda project |
It also sets presets to [chakraSolidPreset] — Chakra’s tokens and all 75 recipes — but that one
is merged, not locked: yours are appended after it.
Writing any of the four is a type error whose message names the key:
export default defineChakraConfig({
// ✗ `jsxFactory` is set by defineChakraConfig, because it decides what Panda
// extracts rather than what it names
jsxFactory: "styled",
include: [...],
});Two keys are set as defaults, not locks, so whatever you pass wins: preflight: true, and
prefix: { cssVar: "chakra" } — the React version’s own cssVarsPrefix, which keeps our ~547 token
variables out of a collision with the identically-named ones your app declares. Panda merges nothing
inside prefix, so a prefix of yours replaces it whole: write cssVar too, or the variables lose
their namespace.
What you can pass
Three classes, and the type tells them apart for you.
| Keys | What happens | |
|---|---|---|
| Locked | the four above | A type error. If one reaches Panda anyway — an untyped panda.config.js, or a spread of the return value — your Panda run prints [chakra-ui-solid] restoring … and puts our value back |
| Merged | presets, plugins, theme, staticCss | Added to ours rather than replacing it. Your presets and plugins come after ours; theme accepts only extend; staticCss is merged for you |
| Yours | include, outdir, hash, prefix, separator, utilities, conditions, globalCss, preflight, cssVarRoot, hooks, the rest | Passed straight through |
utilities and conditions are worth naming there rather than leaving in “the rest”: a style
property or a condition you invent is a typed prop on our components, because your run generates both
the isCssProperty that folds it into a class and the declaration that types it.
Overview has both worked through.
What it generates
Three plugins are appended to your plugins array, and two of them write files into your outdir
every panda codegen:
| File | What it is |
|---|---|
chakra-system.ts | Five imports of data and no logic — the css, patterns and recipes namespaces, isCssProperty and token, passed to createSystem(). This is what you hand <ChakraProvider> |
chakra-system-types.d.ts | The declare module block telling TypeScript what this config decided: a row per recipe’s variants, a row per utilities entry, a row per conditions entry. Nothing imports it — it is picked up because it is in your program |
The third plugin is the gate behind components, and prints the line every run ends
with.
Neither generated file is yours to edit: every run rewrites both, so a change belongs in
panda.config.ts.
include is the one required key. Panda’s default (src/**/*.{js,jsx,ts,tsx}) misses
chakra-ui-solid/dist/panda.buildinfo.json, and that path is not optional: it is the channel
through which values our components name, and your source never writes, reach your extractor.
It is one file rather than a glob over our published sources, which also means our files can never
stand in for yours — if your own globs match nothing, the run says so instead of looking fine.
theme takes extend only
extend deep-merges; the key beside it replaces. A bare theme would drop Chakra’s whole token
table and all 75 recipes, so theme here accepts extend and nothing else:
export default defineChakraConfig({
theme: { extend: { tokens: { colors: { brand: { value: "#5b8" } } } } },
include: [...],
});theme: { tokens: … } is a type error naming the fix.
staticCss is merged for you
Write it the way Panda documents it — the merge is this function’s job:
export default defineChakraConfig({
staticCss: { css: [{ properties: { color: ["red.500"] } }] },
include: [...],
});That one needs the merge more than theme does, and neither spelling avoids the need. A config’s
staticCss.css replaces a preset’s whole array, and staticCss: { extend: { css } } replaces it
too. So the preset’s own seven entries are written into your config alongside yours, rather than left
behind in the preset for Panda to drop — and if you write an extend block, it is folded into that
same union rather than passed through.
extenddoes not extendstaticCss, whatever you have read. Panda’s Extend page listsstaticCssamong the parts the keyword extends. Measured on one merge against three of its list-mates:conditions,globalCssandutilitieseach keep the preset’s entry and yours;staticCsskeeps only yours. The three are objects keyed by name, so a clash is per name —staticCss.cssis an array, and the whole array loses. Nothing warns you, which is why this function does the union itself.
Those seven are not decoration: <Flex inline>, <Wrap> and every StackSeparator flip a value at
runtime, Panda’s usage scan cannot see a value that appears in no source file, and the pre-generated
rule is the only thing that makes the prop do anything. Lose them and those props go quietly inert.
Not
mergeConfigs. It is not exported from@pandacss/dev, so a consumer who installed only the peer dependency cannot reach it, and it returnsany— discarding theConfigtypes this function exists to supply.
An extra preset
Ours stays first, so yours is later and wins on a conflict:
export default defineChakraConfig({
presets: [myPreset],
include: [...],
});responsive
Recipe variants are pre-generated at one condition by default. Pass responsive to also generate
them at every breakpoint, which is what <Button size={{ base: "sm", md: "lg" }}> needs:
export default defineChakraConfig({
responsive: { button: ["size"] },
include: [...],
});Three grains, so nobody pays for conditions they do not use:
| Grain | Meaning |
|---|---|
{ button: ["size"] } | One variant key on one recipe |
["button", "heading"] | Every variant key on those recipes |
true | Every variant key on all 75 |
Each expands to a rule Panda already understands, appended to that recipe’s own staticCss list:
defineChakraConfig({ responsive: { button: ["size"] }, include: [] }).theme.extend.recipes.button
.staticCss;
// => ["*", { size: ["*"], responsive: true }]The recipe’s own list is where the rule has to land, and the "*" in front of it is worth knowing
about if you ever write staticCss by hand. Panda resolves staticCss.recipes[name] from the recipe
body whenever the body declares one, assigning it over whatever the config asked for that
recipe. None of ours declares one until an opt-in puts it there — and the config-level entry it then
replaces is the one deciding whether that recipe reaches your sheet at all, the one
components widens. So the list opens by asking for the whole recipe again, and
opting one variant key into breakpoints never costs you the rest of it.
Rules you write under staticCss.recipes are moved to the body for you, so both spellings work:
export default defineChakraConfig({
responsive: { button: ["size"] },
staticCss: { recipes: { button: [{ variant: ["*"] }] } },
include: [...],
});
// button's staticCss => ["*", { size: ["*"], responsive: true }, { variant: ["*"] }]Off by default because it multiplies a recipe’s variant values by six — base plus five
breakpoints. There are 490 of those values across the 75 recipes, and the true grain opts every
one of them in.
conditional
The sibling knob, for a variant that changes on a state rather than a breakpoint — which is what
<IconButton variant={{ base: "ghost", _selected: "outline" }}> needs:
export default defineChakraConfig({
conditional: { button: { variant: ["selected"] } },
include: [...],
});responsive does not cover this and cannot: Panda keeps breakpoints and conditions as two separate
keys on a static-css rule, so a responsive opt-in on button.variant generates
md:button--variant_outline and never selected:button--variant_outline. A class with no rule
renders nothing and reports nothing, so the selected item simply looks like the others.
Condition names are Panda’s own, written without the underscore a prop uses — _selected in JSX,
"selected" here. Two grains:
| Grain | Meaning |
|---|---|
{ button: { variant: ["selected"] } } | One variant key, under one condition |
{ button: ["selected"] } | Every variant key on that recipe, under it |
There is no true: breakpoints are a closed set, and conditions are not — your own conditions
block adds to them. Both knobs can name the same recipe, and the rules stack:
defineChakraConfig({
responsive: { button: ["size"] },
conditional: { button: { variant: ["selected"] } },
include: [],
}).theme.extend.recipes.button.staticCss;
// => ["*", { size: ["*"], responsive: true }, { variant: ["*"], conditions: ["selected"] }]components
Your stylesheet carries the recipes your source imports, and nothing else. Panda reads that off
the import specifiers in the files your include globs cover, so it survives an alias, a namespaced
part and a wrapper of your own alike — all three still name the component in the specifier. Every
Panda run prints what it found, counting the recipes each component composes — Button brings
spinner with it, Field brings fieldset and icon:
🐼 chakra-ui-solid 4 components → 7 recipes: Button, Dialog, Field, Tablecomponents names the ones no file it scanned could show it:
export default defineChakraConfig({
// Only for components reaching your app from somewhere Panda does not scan —
// a dependency built on chakra-ui-solid. For your own workspace package,
// add it to `include` instead.
components: ["Menu", "Toast"],
include: [...],
});It takes component names spelled the way your source spells them, and it can only widen: nothing here removes a recipe the scan found.
If a run finds no import from this library at all, it says so and generates everything, because a scan
that found nothing is a broken include rather than an app with no components:
🐼 chakra-ui-solid 0 components detected — generating all recipes.
Nothing in `include` imports from "chakra-ui-solid". Check your `include` globs.The recipe registry
Three more exports, read off the preset rather than typed out, so a Chakra release that adds a recipe is covered by the version bump alone:
import { recipeKeys, slotRecipeKeys, variantKeysFor } from "@chakra-ui-solid/panda-preset";
recipeKeys.length; // => 19 — button, input, heading, …
slotRecipeKeys.length; // => 56
variantKeysFor().find((entry) => entry.recipe === "button");
// => { recipe: "button", keys: ["size", "variant"] }variantKeysFor() is what expands the coarser grains of both opt-ins: responsive: ["button"] and
conditional: { button: ["selected"] } each mean every variant key on Button, and nothing else knows
which those are.