Recipes
Writing multi-variant styles with recipes in Chakra.
Overview
Chakra provides a way to write CSS-in-JS with better performance, developer experience, and composability. One of its key features is the ability to create multi-variant styles with a type-safe runtime API.
A recipe consists of these properties:
className: The className to attach to the componentbase: The base styles for the componentvariants: The different visual styles for the componentcompoundVariants: The different combinations of variants for the componentdefaultVariants: The default variant values for the component
Defining the recipe
Use the defineRecipe identity function to create a recipe.
button.recipe.ts
import { defineRecipe } from "@chakra-ui/react"
export const buttonRecipe = defineRecipe({
base: {
display: "flex",
},
variants: {
visual: {
solid: { bg: "red.200", color: "white" },
outline: { borderWidth: "1px", borderColor: "red.200" },
},
size: {
sm: { padding: "4", fontSize: "12px" },
lg: { padding: "8", fontSize: "24px" },
},
},
})Using the recipe
There are two ways to use the recipe in a component:
- Directly in the component with
useRecipe - Creating a component (recommended) with the
chakrafactory
"use client" directive is required since it relies on
react hooks like useContext and useInsertionEffect under the hood.Directly in component
Use the useRecipe hook to get the recipe for a component. Then, call the
recipe with its variant props to get the styles.
button.tsx
"use client"
import { chakra, useRecipe } from "@chakra-ui/react"
import { buttonRecipe } from "./button.recipe"
export const Button = (props) => {
const { visual, size, ...restProps } = props
const recipe = useRecipe({ recipe: buttonRecipe })
const styles = recipe({ visual, size })
return <chakra.button css={styles} {...restProps} />
}splitVariantProps
Notice how the visual and size props were destructured from the props to be
passed to the recipe. A smarter approach would be to automatically split the
recipe props from the component props.
To do that, use the recipe.splitVariantProps function to split the recipe
props from the component props.
button.tsx
"use client"
import { chakra, useRecipe } from "@chakra-ui/react"
import { buttonRecipe } from "./button.recipe"
export const Button = (props) => {
const recipe = useRecipe({ recipe: buttonRecipe })
const [recipeProps, restProps] = recipe.splitVariantProps(props)
const styles = recipe(recipeProps)
// ...
}TypeScript
To infer the recipe variant prop types, use the RecipeVariantProps type
helper.
button.tsx
import type { RecipeVariantProps } from "@chakra-ui/react"
import { buttonRecipe } from "./button.recipe"
type ButtonVariantProps = RecipeVariantProps<typeof buttonRecipe>
export interface ButtonProps
extends React.PropsWithChildren<ButtonVariantProps> {}Creating a component
Use the chakra function to create a component from a recipe.
Note: The recipe can also be inlined into the chakra function.
button.tsx
"use client"
import { chakra } from "@chakra-ui/react"
import { buttonRecipe } from "./button.recipe"
export const Button = chakra("button", buttonRecipe)Next, use the component and pass recipe properties to it.
app.tsx
import { Button } from "./button"
const App = () => {
return (
<Button visual="solid" size="lg">
Click Me
</Button>
)
}Default Variants
The defaultVariants property is used to set the default variant values for the
recipe. This is useful when you want to apply a variant by default.
button.tsx
"use client"
import { chakra } from "@chakra-ui/react"
const Button = chakra("button", {
base: {
display: "flex",
},
variants: {
visual: {
solid: { bg: "red.200", color: "white" },
outline: { borderWidth: "1px", borderColor: "red.200" },
},
size: {
sm: { padding: "4", fontSize: "12px" },
lg: { padding: "8", fontSize: "24px" },
},
},
defaultVariants: {
visual: "solid",
size: "lg",
},
})Compound Variants
Use the compoundVariants property to define a set of variants that are applied
based on a combination of other variants.
button.tsx
"use client"
import { chakra } from "@chakra-ui/react"
const button = cva({
base: {
display: "flex",
},
variants: {
visual: {
solid: { bg: "red.200", color: "white" },
outline: { borderWidth: "1px", borderColor: "red.200" },
},
size: {
sm: { padding: "4", fontSize: "12px" },
lg: { padding: "8", fontSize: "24px" },
},
},
compoundVariants: [
{
size: "small",
visual: "outline",
css: {
borderWidth: "2px",
},
},
],
})When you use the size="small" and visual="outline" variants together, the
compoundVariants will apply the css property to the component.
app.tsx
<Button size="small" visual="outline">
Click Me
</Button>Theme Usage
To use the recipe in a reusable manner, move it to the system theme and add it
to theme.recipes property.
No need to add the "use client" directive when using the recipe in the
theme.
theme.ts
import { createSystem, defineConfig } from "@chakra-ui/react"
import { buttonRecipe } from "./button.recipe"
const config = defineConfig({
theme: {
recipes: {
button: buttonRecipe,
},
},
})
export default createSystem(config)TypeScript
Use the CLI to generate the types for the recipe.
npx @chakra-ui/cli@next typegen ./theme.tsThen, import the generated types in your component.
button.tsx
import type { RecipeVariantProps } from "@chakra-ui/react"
import { buttonRecipe } from "./button.recipe"
type ButtonVariantProps = RecipeVariantProps<typeof buttonRecipe>
export interface ButtonProps
extends React.PropsWithChildren<ButtonVariantProps> {}Update code
If you use the recipe directly in your component, update the useRecipe to use
the key property to get the recipe from the theme.
button.tsx
const Button = () => {
- const recipe = useRecipe({ recipe: buttonRecipe })
+ const recipe = useRecipe({ key: "button" })
// ...
}