Architecture
The React version’s theming system is built around the API of
Panda CSS. This one is built on Panda CSS. The config object is therefore
close to the same object — it lives in panda.config.ts, and a build compiles it instead of an
engine serializing it while your app runs.
Almost nothing on this page is ours.
theme.extend, tokens, semantic tokens, recipes, conditions, utilities — all of it is Panda’s own API, and Panda’s docs are the reference for any key not covered here. This library contributes two things to it: the preset carrying Chakra’s design system, anddefineChakraConfig(), which stands in for Panda’sdefineConfig.
Three steps, and they are the React version’s three:
- Define the styling system configuration using
defineChakraConfig - Create the styling engine —
panda codegenwrites it for you, asstyled-system/chakra-system.ts - Pass the styling engine to the
ChakraProvidercomponent
import { defineChakraConfig } from "@chakra-ui-solid/panda-preset";
export default defineChakraConfig({
theme: {
extend: {
tokens: {
colors: { brand: { 500: { value: "tomato" } } },
},
},
},
include: ["./node_modules/chakra-ui-solid/dist/panda.buildinfo.json", "./src/**/*.{ts,tsx}"],
outdir: "styled-system-app",
});import { Box, ChakraProvider } from "chakra-ui-solid";
import { system } from "../styled-system-app/chakra-system";
export default function App() {
return (
<ChakraProvider value={system}>
<Box>Hello World</Box>
</ChakraProvider>
);
}The one difference with the React version’s theme.ts is where the two halves come from. There,
createSystem(defaultConfig, config) builds the engine while your app starts, and the engine writes
the CSS. Here, one panda codegen run over one config writes both the stylesheet and the system
module above — so the class names on the element and the rules behind them agree by construction
rather than by two sides being configured the same way.
defineChakraConfig()already puts the preset inpresets— do not import it again here. It is also the only call in the file: there is nodefineConfigto wrap it in and nothing to spread, so no key you write can replace one it set. Installation covers the setup this page assumes.
Config
The system is configured using the defineChakraConfig function. This function accepts a
configuration object that allows you to customize the styling system’s behavior.
After a config is defined, panda codegen reads it and writes the stylesheet and the system module.
cssVarRoot
cssVarRoot is the root element where the token CSS variables will be applied. Note the name: the
React version spells it cssVarsRoot.
export default defineChakraConfig({
cssVarRoot: ":where(:root, :host)",
include: [...],
});prefix
prefix names both halves at once: cssVar prefixes the token variables, className prefixes every
rule in the sheet.
export default defineChakraConfig({
prefix: { cssVar: "ck", className: "ck" },
include: [...],
});{ cssVar: "chakra" } is the default, which is what the React version’s cssVarsPrefix spells,
and it keeps our ~547 token variables — --chakra-spacing-4, --chakra-colors-red-500 — out of a
collision with the identically-named ones your app declares. Panda merges nothing inside this key, so
a prefix of yours replaces the default outright: write cssVar too, or the variables lose their
namespace.
className is what you need when two systems render on one page — see
Multiple systems.
globalCss
globalCss is used to apply global styles to the system.
export default defineChakraConfig({
globalCss: {
"html, body": { margin: 0, padding: 0 },
},
include: [...],
});preflight
preflight is used to apply css reset styles to the system. defineChakraConfig() turns it on;
pass false to drop it, or a scope to confine it to one element.
export default defineChakraConfig({
preflight: { scope: ".chakra-reset" },
include: [...],
});theme
Use the theme config property to define the system theme. Under extend, it accepts the same
properties the React version’s does:
breakpoints: for defining breakpointskeyframes: for defining css keyframes animationstokens: for defining tokenssemanticTokens: for defining semantic tokenstextStyles: for defining typography styleslayerStyles: for defining layer stylesrecipes: for defining component recipesslotRecipes: for defining component slot recipes
export default defineChakraConfig({
theme: {
extend: {
breakpoints: { sm: "320px", md: "768px", lg: "960px", xl: "1200px" },
tokens: {
colors: { red: { value: "#EE0F0F" } },
},
semanticTokens: {
colors: { danger: { value: "{colors.red}" } },
},
keyframes: {
spin: {
from: { transform: "rotate(0deg)" },
to: { transform: "rotate(360deg)" },
},
},
},
},
include: [...],
});Recipes are keyed by the names the preset already registers — button, input, heading and the
rest — so overriding one is adding to that key rather than declaring a new recipe:
theme: {
extend: {
recipes: {
button: { variants: { size: { xl: { px: "8", h: "14" } } } },
},
},
}A variant key the recipe did not have works the same way, and reaches the component as a prop of its own:
theme: {
extend: {
recipes: {
button: { variants: { tone: { brand: { bg: "brand.500" } } } },
},
},
}<Button tone="brand">Save</Button>The component reads its variant key list off the recipe your config produced, so tone is passed to
the recipe rather than left on the <button> as an attribute.
A bare
themewould drop Chakra’s whole token table and all 75 recipes —extendmerges, the key itself replaces. Sothemehere acceptsextendand nothing else:theme: { tokens: … }is a type error naming the fix.
utilities
Use the utilities config property to create a style property of your own, map an existing one to a
set of values, or give it a shorthand. Three keys define one:
shorthand: the alias version of the propertyvalues: what it accepts — a token category, an enum, a map, orstring/numbertransform: a function turning the value into a style object
Say you want a br property that applies a border radius:
export default defineChakraConfig({
utilities: {
extend: {
br: {
values: "radii",
transform(value) {
return { borderRadius: value };
},
},
},
},
include: [...],
});br is then a style prop on every component, and it type-checks:
import { Box } from "chakra-ui-solid";
<Box br="sm" />;Enum values and mapped values work the same way, and transform is handed token as its second
argument:
export default defineChakraConfig({
utilities: {
extend: {
borderX: {
values: ["1px", "2px", "4px"],
shorthand: "bx",
transform(value, { token }) {
return { borderInlineWidth: value, borderColor: token("colors.red.200") };
},
},
},
},
include: [...],
});The name is a style key, not a second prop bag, so it is legal everywhere one goes — as a prop,
inside css, inside a condition, and inside a condition inside a condition:
<Box br="sm" />
<Box css={{ br: "sm" }} />
<Box _hover={{ br: "lg", _dark: { br: "sm" } }} />conditions
Use the conditions config property to define custom selectors and media query conditions for use
in the system.
export default defineChakraConfig({
conditions: { cqSm: "@container(min-width: 320px)", child: "& > *" },
include: [...],
});Write it with the leading underscore a prop uses, and it works on our components and in your own
css() alike:
<Box mt="40px" _cqSm={{ mt: "0px" }}>
<Text>Hello World</Text>
</Box>It nests as far as a built-in condition does, because it is one — the same generated Conditions
interface carries both:
<Box _hover={{ _cqSm: { mt: "0px" } }} />strictTokens
strictTokens enforces the usage of only design tokens, raising a TS error on a raw value. Setting
it applies to the runtime your Panda run generates. It does not tighten the style props on our
components: those types come from our published declarations, which were generated without it.
TypeScript
There is no separate typegen step, and nothing to remember to run. panda codegen writes
styled-system/chakra-system-types.d.ts beside the system module, off the types it generated from the
same config:
pnpm panda codegenThat file is what makes a utilities entry a typed style prop, a conditions entry a typed
condition, and a recipe variant you added — value or key — a typed prop. Nothing imports it; it is
picked up because it sits in your tsconfig.json’s program.
The React version reaches the same place with @chakra-ui/cli typegen, one step later and one step
you have to remember. Add panda codegen to a prepare script and there is no step at all:
{ "scripts": { "prepare": "panda codegen" } }Keys the config sets for you
Four, and each decides what Panda extracts rather than what it names: eject, jsxFramework,
jsxFactory and importMap. Changing one produces a stylesheet missing rules nobody asked Panda
for, with nothing anywhere to say so, so all four are a type error to pass.
presets and staticCss are merged instead — your presets come after ours, and your staticCss
is unioned with the preset’s. defineChakraConfig is the full account.
Every naming knob is yours: hash, prefix and separator decide what the classes and variables
are called, and one Panda run names both the element and the sheet.
The system object
The React version’s createSystem returns a system with helpers hanging off it. Ours is the same
object with the members Panda already consumed at build time left off, and panda codegen assembles
it into styled-system/chakra-system.ts:
| The React version | Here |
|---|---|
createSystem(defaultConfig, config) | system from ./styled-system/chakra-system |
system.token("colors.red.500") | token("colors.red.500") from ./styled-system/tokens |
system.token.var("colors.red.500") | token.var("colors.red.500") |
system.css({ ... }) | css({ ... }) from ./styled-system/css |
system.cva({ ... }) | cva({ ... }) |
system.sva({ ... }) | sva({ ... }) |
system.isValidProperty(prop) | isCssProperty(prop) from ./styled-system/jsx/is-valid-prop |
system.splitCssProps(props) | splitCssProps(props) from the same module |
system.breakpoints.up("sm") | No counterpart |
system.tokens.flatMap | No counterpart |
system._global, system.layers | No counterpart — the provider renders no stylesheet |
import { css } from "../styled-system-app/css";
import { token } from "../styled-system-app/tokens";
token("colors.red.500");
// => "#ef4444"
token.var("colors.red.500");
// => "var(--chakra-colors-red-500)"
css({ color: "red.500", bg: "blue.200" });
// => "c_red.500 bg_blue.200"One difference worth reading twice: system.css returns a style object for a CSS-in-JS library to
insert, and css here returns a class name string naming rules your stylesheet already
contains. cva and sva likewise return functions that return class names.
Inside a component, the same system is on the context the provider carries:
import { useChakraContext } from "chakra-ui-solid";
function Swatch() {
const system = useChakraContext();
return <div class={system().css({ bg: "red.500" })} />;
}It is an accessor, because Solid’s contexts are not reactive: read inside the class getter, a
provider handed a new system restyles what is already on the page. That is the whole of
Multiple systems.