defineChakraConfig
The function that writes your Panda config, so your stylesheet and our compiled runtime name the same classes
packages/panda-preset/src/config.tsdefineChakraConfig() 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/**/*.jsx", "./src/**/*.{ts,tsx}"],
outdir: "styled-system-app",
});It exists because of one failure mode that has no other guard. Panda’s usual model generates the
runtime and the stylesheet together from a single config; here the runtime was compiled into
@chakra-ui-solid/styled-system before you installed it, and only the stylesheet is yours. Several
config keys decide names on both sides of that split. Set differently on your side alone, they
produce a sheet whose rules our class names never match: every component renders unstyled, and
nothing anywhere raises an error.
The function owns the merge so that none of that is constructable. You never spread it, so there is no shallow-spread hazard; the keys that must agree are a type error to pass; and the keys that would otherwise replace ours are merged for you.
What it sets
Eight keys, and none of them are yours to choose:
| Key | Value | Why |
|---|---|---|
eject | true | Drops @pandacss/preset-panda, whose palette disagrees with Chakra’s about colors.gray.*. 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 | Tells your extractor the styled-system API lives in our published package rather than in a directory you generated |
hash | false | Our runtime computes p_4; a hashed sheet has no such rule |
separator | "_" | p_4 would become p=4 in your sheet only |
prefix | unset | className renames every rule; cssVar renames every variable, and the var(--sizes-*) strings inside SimpleGrid and Bleed were compiled before you installed them |
presets | [chakraSolidPreset] | Chakra’s tokens and all 75 recipes. Yours are appended after it |
Writing any of the eight is a type error whose message names the key:
export default defineChakraConfig({
// ✗ `hash` is set by defineChakraConfig, because our published runtime already
// committed to these names
hash: true,
include: [...],
});preflight is set to true as a default, not a lock — pass false or a scope and yours wins.
What you can pass
Three classes, and the type tells them apart for you.
| Keys | What happens | |
|---|---|---|
| Locked | the eight 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, globalCss, preflight, conditions, cssVarRoot, hooks, the rest | Passed straight through |
include is the one required key. Panda’s default (src/**/*.{js,jsx,ts,tsx}) misses the
dist glob, and that glob is not optional: it is the channel through which values our components
name, and your source never writes, reach your extractor.
theme takes extend only
extend deep-merges; the key beside it replaces. A bare theme would drop Chakra’s whole token
table and all 19 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 the staticCss block Panda already understands, so the opt-in asks nothing new of
Panda:
defineChakraConfig({ responsive: { button: ["size"] }, include: [] }).staticCss.recipes;
// => { button: [{ size: ["*"], responsive: true }] }If you also write staticCss.recipes yourself, the two are merged per recipe rather than one
replacing the other — so responsive and a custom staticCss can both be set.
Off by default because the stylesheet already carries 488 recipe-variant values, and turning
responsive on for all of them multiplies that by six — base plus five breakpoints.
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 two coarser responsive grains: ["button"] means every
variant key on Button, and nothing else knows which those are.