레시피

레시피 (Recipes)

Chakra에서는 더 나은 성능, 개발 경험, 그리고 합성(composability)을 갖춘 CSS-in-JS 작성을 지원해요. 핵심 기능 중 하나는 타입 안전한 런타임 API로 다중 변형(multi-variant) 스타일을 만들 수 있다는 점이에요.

출처: 문서

본문

레시피는 다음 속성들로 구성돼요.

  • className: 컴포넌트에 붙일 className
  • base: 컴포넌트의 기본 스타일
  • variants: 컴포넌트의 다양한 스타일 변형
  • compoundVariants: 변형들의 다양한 조합
  • defaultVariants: 컴포넌트의 기본 변형 값

레시피 정의하기

defineRecipe 항등 함수로 레시피를 만들어요.

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 훅에 의존하므로 "use client" 지시어를 추가해야 해요.

컴포넌트에서 직접 사용하기

useRecipe 훅으로 컴포넌트의 레시피를 가져와요. 그런 다음 레시피에 변형 props를 호출해 스타일을 얻어요.

"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

variant와 size props를 props에서 구조 분해해 레시피에 전달한 걸 볼 수 있어요. 더 똑똑한 방법은 레시피 props를 컴포넌트 props에서 자동으로 분리하는 거예요.

recipe.splitVariantProps 함수로 레시피 props를 컴포넌트 props에서 분리할 수 있어요.

"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

레시피 변형 prop 타입을 추론하려면 RecipeVariantProps 타입 헬퍼를 사용해요.

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 함수로 레시피에서 컴포넌트를 만들어요.

Note: 레시피를 chakra 함수에 인라인할 수도 있어요.

"use client"

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

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

이제 컴포넌트를 사용하면서 레시피 속성을 전달하면 돼요.

import { Button } from "./button"

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

기본 변형 (Default Variants)

defaultVariants 속성은 레시피의 기본 변형 값을 설정할 때 사용해요. 변형을 기본으로 적용하고 싶을 때 유용해요.

"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",
  },
})

복합 변형 (Compound Variants)

compoundVariants 속성으로 다른 변형들의 조합에 기반해 적용되는 변형 집합을 정의할 수 있어요.

"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 속성을 컴포넌트에 적용해요.

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

주의사항

디자인 제약 때문에 compoundVariants와 반응형 값을 함께 사용할 수는 없어요.

즉, 다음과 같은 코드는 동작하지 않아요.

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

이런 경우에는 브레이크포인트별로 여러 버전의 컴포넌트를 렌더링한 뒤, 필요에 따라 보이기/숨기기를 하는 걸 권장해요.

시맨틱 토큰 사용하기

테마의 시맨틱 토큰을 레시피에서 사용할 수 있어요. 이름으로 참조하면 돼요.

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

export const buttonRecipe = defineRecipe({
  base: {
    bg: "bg.muted", // semantic token
    color: "fg", // semantic token
    borderRadius: "l2", // semantic radius
  },
  variants: {
    variant: {
      primary: {
        bg: "colorPalette.solid", // virtual color
        color: "colorPalette.contrast",
      },
    },
  },
})

흔히 쓰는 토큰: bg, fg, border, colorPalette.*

테마에서 사용하기

레시피를 재사용 가능한 형태로 쓰려면 시스템 테마로 옮기고 theme.recipes 속성에 추가해요.

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를 사용해 레시피용 타입을 생성한 뒤 컴포넌트에서 import해요. postinstall, CI, 모노레포에서 typegen을 실행하는 방법은 CLI 문서를 참고하세요.

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

그런 다음 생성된 타입을 컴포넌트에서 import해요.

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 속성을 사용해 테마에서 레시피를 가져오도록 업데이트해요.

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

더 알아보기 (Learn more)

  • 슬롯 레시피 문서에서 여러 부분으로 구성된 컴포넌트 스타일링을 알아보세요.
  • 테마 개요에서 Chakra UI 테마 시스템 전반을 알아보세요.