Direct Answer
Use semantic tokens as the single source of truth for any value that must respond to color‑mode changes. Restrict component recipes to structural concerns (layout, spacing, typography) and only reference tokens — never hard‑code colors or other mode‑sensitive values — inside a recipe. When a recipe and a token target the same CSS property, the recipe’s value wins at merge time; if that value is a hard‑coded literal it will not update when the color mode toggles, creating an inconsistency.
How the Merge Order Works
- Default recipe — built‑in component styles shipped with Chakra UI.
- Theme recipe override — your
theme.recipes (or theme.components in v2) extensions. - Inline style props —
sx, style, or component props passed at render time.
At each layer, a plain value (hex, pixel number, etc.) replaces whatever came before. A token reference (e.g. {colors.brand.primary}) is preserved and resolved at runtime against the active color mode.
Why Hard‑Coded Recipe Values Break Color Modes
If theme.tokens.colors.brand.primary points to {_light: 'blue.500', _dark: 'blue.300'} but a button recipe sets background: '#3b82f6', the merge order makes the literal win. The button will stay #3b82f6 in both light and dark mode because the token is never consulted for that property.
Recommended Pattern
// theme/tokens/colors.ts
export const colors = {
brand: {
primary: { _light: 'blue.500', _dark: 'blue.300' },
},
};
// theme/recipes/button.ts
import { defineRecipe } from '@chakra-ui/react';
export const buttonRecipe = defineRecipe({
base: {
// ✅ token reference — reacts to color mode
background: 'colors.brand.primary',
color: 'white',
// ✅ structural only
borderRadius: 'md',
px: 4,
py: 2,
},
variants: {
ghost: {
background: 'transparent', // structural, not color‑mode dependent
},
},
});
Verification Steps
- Open the component in browser DevTools.
- Toggle the color mode (via
ColorModeScript or manual class swap). - Inspect the computed
background-color (or other property) — it should change if driven by a token, stay static if a hard‑coded recipe value won.
One Diagnostic Detail Needed
Are you extending recipes via theme.recipes (v3 Panda‑CSS style) or the legacy theme.components API? The merge mechanics differ slightly; confirming which path you use lets me confirm whether any additional precedence rules apply.