Chakra UIのV3へメジャーアップデートする際の変更点

# はじめに
こんにちは、H×Hのセンリツ大好きエンジニアです。（同担OKです😉）

今回は、2024年10月にメジャーアップデートされたChakra UIの変更点が思いの外多かったので、簡単にではありますがまとめました。
（足りない情報があればコメント等で教えていただけますと幸いです🙇）

# この記事で取り上げる変更点
- [使用するパッケージ](#%E4%BD%BF%E7%94%A8%E3%81%99%E3%82%8B%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8)
- [Theme、Recipe](#themerecipe)
- [Components](#components)


## 使用するパッケージ
V3では` @emotion/styled`と`framer-motion`パッケージが使用されません。
また、`@chakra-ui/next-js`も不要になりました。（`asChild`propsの登場により）

そのため、インストールするときは以下のコマンドでインストールします。
``` sh
npm i @chakra-ui/react @emotion/react
```
V3ではCLIパッケージも新たに追加され、今まで使用していたコンポーネント（`Accordion`や`Progress`など）を使用する際には以下のコマンドから事前に定義されたスニペットとして追加できます。
``` sh
npx @chakra-ui/cli snippet add
```

## Theme、Recipe
### Theme
Themeに関しても、Panda CSSを参考にして作られているため仕様が変わっています。
（Panda CSSを使っているような説明ですが、実際にはPanda CSSはChakra UIで使用されていません）

`theme.ts`では今まで`extendTheme`を使用することで独自のスタイリングや、コンポーネント毎のプロパティ調整など行っていましたが、V3からは`createSystem`と`defaultConfig`を使う必要があります。
``` theme.ts
import { createSystem, defaultConfig } from "@chakra-ui/react"

export const system = createSystem(defaultConfig, {
  theme: {
    tokens: {
      fonts: {
        heading: { value: `'Figtree', sans-serif` },
        body: { value: `'Figtree', sans-serif` },
      },
    },
  },
})
```

`theme.ts`の中で設定できるプロパティの例としては、以下のものがあります。
- breakpoints: ブレークポイントを定義する
- keyframes: CSSキーフレームアニメーションを定義する
- tokens: トークンを定義する
- semanticTokens: セマンティックトークンを定義する
- textStyles: タイポグラフィスタイルを定義する
- layerStyles: レイヤースタイルを定義する
- animationStyles: アニメーションスタイルを定義する
- recipes: コンポーネントレシピを定義する
- slotRecipes: コンポーネントスロットレシピを定義する

この中にある`tokens`や`semanticTokens`は独自のカラーコードやフォントを設定するためのものです。
また、Themeの中で最も変わった部分としては、`recipes`と`slotRecipes`になるかと思います。

### Recipe
Recipeは、ButtonやInputなどのコンポーネントのプロパティをグローバルに定義することが出来るものです。
以前はコンポーネント毎に`defineStyle`と`defineStyleConfig`を使用していましたが、Panda CSSライクになったことで廃止になりました。

使用方法としては、`defineRecipe`を使用して独自の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" },
    },
  },
})
```

これを`theme.ts`内で既存のコンポーネントに適用します。
``` theme.ts
import { buttonRecipe } from "./button.recipe"

export const system = createSystem(defaultConfig, {
  theme: {
    tokens: {
      fonts: {
        heading: { value: `'Figtree', sans-serif` },
        body: { value: `'Figtree', sans-serif` },
      },
    recipes: {
        button: buttonRecipe,
      },
    },
  },
})
```
ここで注意が必要なのですが、以前のように`variant`を独自で設定した場合**CLIでRecipeの型を生成**してあげる必要があります。
これをしないと、型エラーが発生してしまいます。
``` sh
npx @chakra-ui/cli typegen ./theme.ts
```

`Button`や`Input`などの単体で動作するコンポーネントに関しては`Recipe`で定義できますが、`Accordion`などの組み合わせることで初めて機能するコンポーネントに関しては`SlotRecipe`で定義する必要があります。
ちなみに、Accordionは以下のような形になっています。（Ark UIを基に作成されているため）
``` accoridon.ts
import {
  AccordionItem,
  AccordionItemContent,
  AccordionItemTrigger,
  AccordionRoot,
} from "@/components/ui/accordion"

const Demo = () => {
  return (
    <AccordionRoot collapsible defaultValue={["b"]}>
      {items.map((item, index) => (
        <AccordionItem key={index} value={item.value}>
          <AccordionItemTrigger>{item.title}</AccordionItemTrigger>
          <AccordionItemContent>{item.text}</AccordionItemContent>
        </AccordionItem>
      ))}
    </AccordionRoot>
  )
}

const items = [
  { value: "a", title: "First Item", text: "Some value 1..." },
  { value: "b", title: "Second Item", text: "Some value 2..." },
  { value: "c", title: "Third Item", text: "Some value 3..." },
]
```

`SlotRecipe`も基本的には`Recipe`と同様に定義することが可能ですが、複数のパーツに分かれているためどこのパーツにプロパティを適用させるか決める必要があります。

以下の画像は[Ark UIの公式リファレンス](https://ark-ui.com/react/docs/components/accordion)内で確認できます。
![スクリーンショット 2024-11-30 17.15.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2653241/809edafb-7d55-d2a6-be86-b23e000d3e63.png)

現段階ではChakra UIのリファレンスに上記の画像が掲載されていないため、**コンポーネント関連に関してはArk UIのリファレンスを見た方が良い**です。

そのため、Accordionを例に挙げる場合はSlotRecipeを以下のようにパーツ毎で記述します。
``` accordion.recipe.ts
import { defineRecipe } from "@chakra-ui/react"

export const accordionSlotRecipe = defineSlotRecipe({
  slots: ["root", "item", "itemTrigger", "itemBody", "itemContent"],
  base: {
    root: {
      width: "full",
      "--accordion-radius": "radii.l2",
    },
    item: {
      overflowAnchor: "none",
    },
    itemTrigger: {
      display: "flex",
      alignItems: "center",
      width: "full",
      outline: "0",
      gap: "3",
      fontWeight: "medium",
      borderRadius: "var(--accordion-radius)",
      _focusVisible: {
        outline: "2px solid",
        outlineColor: "colorPalette.focusRing",
      },
      _disabled: {
        layerStyle: "disabled",
      },
    },
    itemBody: {
      pt: "var(--accordion-padding-y)",
      pb: "calc(var(--accordion-padding-y) * 2)",
    },
    itemContent: {
      overflow: "hidden",
      borderRadius: "var(--accordion-radius)",
      _open: {
        animationName: "expand-height, fade-in",
        animationDuration: "moderate",
      },
      _closed: {
        animationName: "collapse-height, fade-out",
        animationDuration: "moderate",
      },
    },

    variants: {
      variant: {
        outline: {
          item: {
            borderBottomWidth: "1px",
          },
        },
      },
    },
})
```
上記のように、Chakra UIが事前に定義している`outline`のようなvariantにも上書きでプロパティを変更できたりもします。

実際に適用する場合は、AccordionコンポーネントのRootにvariantを設定してあげます。
``` accordion.ts
import {
  AccordionItem,
  AccordionItemContent,
  AccordionItemTrigger,
  AccordionRoot,
} from "@/components/ui/accordion"

const Demo = () => {
  return (
    <AccordionRoot collapsible defaultValue={["b"]} variant="outline">
      {items.map((item, index) => (
        <AccordionItem key={index} value={item.value}>
          <AccordionItemTrigger>{item.title}</AccordionItemTrigger>
          <AccordionItemContent>{item.text}</AccordionItemContent>
        </AccordionItem>
      ))}
    </AccordionRoot>
  )
}

const items = [
  { value: "a", title: "First Item", text: "Some value 1..." },
  { value: "b", title: "Second Item", text: "Some value 2..." },
  { value: "c", title: "Third Item", text: "Some value 3..." },
]
```

## Components
先ほどの章でも少し取り上げましたが、コンポーネントに関してはArk UIを基に作成したためガラリと変わっています。

基本的にはArk UIのリファレンスを参考にしつつ実装するのが良いと思います。
また、コンポーネントを使用する際は以下のCLIコマンドでスニペットを追加してあげる必要があります。
``` sh
npx @chakra-ui/cli snippet add
```

V3では`Pagination`などの新しいコンポーネントが追加された反面、シンプルになった影響で細かい調整などがしにくくなっています。

例えば、V3では`Hooks`が一部を除き無くなっています。(`useBreakpointValue`等は現在も使用可能)

そのため、`Radio`などで今まで使っていた`useRadio`や`useRadioGroup`を使用されている場合は代替手段として`react-use`や`usehooks-ts`を使用する必要があります。
 
# おわりに
ここまでご覧いただきありがとうございます🙇
見た感じ、Panda CSSとArk UIに触れたことがある方にとってはより親しみやすくなったんじゃないでしょうか。

Chakra UIの今回のメジャーアップデートはかなり破壊的変更が満載ですので、プロダクトに規模によってかなり慎重に検討する必要があるかと思います。
今回は特に変わったところをご紹介したので、他にも気になることがあれば教えていただければと思います🫡