样式系统

配方(Recipes)

使用配方在 Chakra 中编写多变体样式。

概述

Chakra 提供了一种编写 CSS-in-JS 的方式,具备更佳的性能、开发者体验和可组合性。其关键 特性之一是通过类型安全的运行时 API 创建多变体样式。

一个配方包含以下属性:

  • className:附加到组件上的 className
  • base:组件的基础样式
  • variants:组件的不同样式变体
  • compoundVariants:多个变体的不同组合
  • defaultVariants:组件的默认变体取值

定义配方

使用 defineRecipe 标识函数创建配方。

tsx
import { defineRecipe } from "@chakra-ui/react"

export const buttonRecipe = defineRecipe({
  base: {
    display: "flex",
  },
  variants: {
    variant: {
      solid: { bg: "red.200", color: "white" },
      outline: { borderWidth: "1px", borderColor: "red.200" },
    },
    size: {
      sm: { padding: "4", fontSize: "12px" },
      lg: { padding: "8", fontSize: "24px" },
    },
  },
})

使用配方

在组件中使用配方的两种方式:

  • 直接在组件中使用 useRecipe
  • 使用 chakra 工厂创建组件(推荐)
RSC 提示
由于内部依赖 useContext useInsertionEffect 等 React Hooks,需要添加 "use client" 指令。

直接在组件中使用

使用 useRecipe Hook 获取组件的配方。然后,用它的变体属性调用该 配方即可得到样式。

tsx
"use client"

import { chakra, useRecipe } from "@chakra-ui/react"
import { buttonRecipe } from "./button.recipe"

export const Button = (props) => {
  const { variant, size, ...restProps } = props

  const recipe = useRecipe({ recipe: buttonRecipe })
  const styles = recipe({ variant, size })

  return <chakra.button css={styles} {...restProps} />
}

splitVariantProps

注意你是如何将 variantsize 属性从 props 中解构出来并传入配方的。更聪明的做法是用 recipe.splitVariantProps 函数自动将配方属性从组件属性中拆分开。

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

使用 RecipeVariantProps 类型辅助函数来推断配方的变体属性类型。

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> {}

创建组件

使用 chakra 函数从配方创建组件。

提示
配方也可以直接内联在 chakra 函数中。
tsx
"use client"

import { chakra } from "@chakra-ui/react"
import { buttonRecipe } from "./button.recipe"

export const Button = chakra("button", buttonRecipe)

接下来,使用该组件并向其传递配方属性:

tsx
import { Button } from "./button"

const App = () => {
  return (
    <Button variant="solid" size="lg">
      Click Me
    </Button>
  )
}

默认变体

defaultVariants 属性用于设置配方的默认变体取值。当你希望默认 应用某个变体时,这会很有用。

tsx
"use client"

import { chakra } from "@chakra-ui/react"

const Button = chakra("button", {
  base: {
    display: "flex",
  },
  variants: {
    variant: {
      solid: { bg: "red.200", color: "white" },
      outline: { borderWidth: "1px", borderColor: "red.200" },
    },
    size: {
      sm: { padding: "4", fontSize: "12px" },
      lg: { padding: "8", fontSize: "24px" },
    },
  },
  defaultVariants: {
    variant: "solid",
    size: "lg",
  },
})

复合变体

使用 compoundVariants 属性定义一组变体,它们会根据其他变体的组合 而被应用。

tsx
"use client"

import { chakra } from "@chakra-ui/react"

const button = cva({
  base: {
    display: "flex",
  },
  variants: {
    variant: {
      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",
      variant: "outline",
      css: {
        borderWidth: "2px",
      },
    },
  ],
})

当你同时使用 size="small" variant="outline" 时,compoundVariants 会将 css 属性应用到组件上。

tsx
<Button size="small" variant="outline">
  Click Me
</Button>

注意事项

由于设计约束,compoundVariants 无法与响应式值配合使用。

这意味着如下代码不会生效:

tsx
<Button size={{ base: "sm", md: "lg" }} variant="outline">
  Click Me
</Button>

对于这种情况,我们建议渲染多个不同断点下的组件版本,再根据需求进行显示/隐藏。

使用语义化令牌

主题中的语义化令牌可以直接用于配方中,通过名称引用即可。

tsx
import { defineRecipe } from "@chakra-ui/react"

export const buttonRecipe = defineRecipe({
  base: {
    bg: "bg.muted", // 语义化令牌
    color: "fg", // 语义化令牌
    borderRadius: "l2", // 语义化圆角
  },
  variants: {
    variant: {
      primary: {
        bg: "colorPalette.solid", // 虚拟颜色
        color: "colorPalette.contrast",
      },
    },
  },
})

常用令牌:bgfg bordercolorPalette.*

在主题中使用

要以可复用的方式使用配方,请将它移入系统主题,并添加到 theme.recipes 属性中。

tsx
import { createSystem, defaultConfig, defineConfig } from "@chakra-ui/react"
import { buttonRecipe } from "./button.recipe"

const config = defineConfig({
  theme: {
    recipes: {
      button: buttonRecipe,
    },
  },
})

export default createSystem(defaultConfig, config)

TypeScript

使用 CLI 为配方生成类型,然后在组件中导入它们。关于如何在 postinstall、CI 和 monorepo 中运行 typegen,参见 CLI 文档

bash
npx @chakra-ui/cli typegen ./theme.ts

然后,在组件中导入生成的类型:

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> {}

更新代码

如果你直接在组件中使用配方,请更新 useRecipe,改用 key 属性从主题中获取配方。

tsx
const Button = () => {
-  const recipe = useRecipe({ recipe: buttonRecipe })
+  const recipe = useRecipe({ key: "button" })
  // ...
}