Chakra UI : ダークモードの切り替えをアイコンを使って実装する

## 使用環境

MacOS BigSur（11.5.2） / VScode（1.59.1） / Node.js   (16.1.1） / "@chakra-ui/icons": "^1.0.15" / "@chakra-ui/react": "^1.6.6"

## はじめに

最近、Next.js（React）を使ったプロジェクトで**Chakra UI**を使用しています。
Chakra UIに関する記事がQiitaではあまり見られないので、学びになったことをちょこちょこ投稿していくことにしました。

今回は、タイトルの通りChakra UIを使ったダークモードの実装方法について書いていきます。
驚くほど簡単だったので、Reactの基礎が分かっている人なら10分以内で実装できてしまうと思います。

では早速、やっていきましょう。

## 実装

まず初めに基本の流れだけざっと説明しておくとこんな感じになります
1. 準備
　-  Chakra UIのインポート
　-  Providerのセットアップ
2. useColorModeのセットアップ
3. コンポーネントで使用する（以上）

+α ダークモード/適用時のスタイルをグローバルで指定する方法
+α 初期設定をする方法（CRA /Next.jsの場合）

---

### 1. 準備

**・Chakra UIのインストール**
導入していない方は、まずはここから始めましょう。既に終わっている方はスキップして2から始めてください。

```bash
#npmの場合
npm i @chakra-ui/react @emotion/react@^11 @emotion/styled@^11 framer-motion@^4
#yarnの場合
yarn add @chakra-ui/react @emotion/react@^11 @emotion/styled@^11 framer-motion@^4
```
        

**・Providerのセットアップ**

Create React Appの場合
Rootとなっているコンポーネントファイルで以下のように設定していきます。

```tsx
import * as React from "react"
// 1. import `ChakraProvider` component
import { ChakraProvider } from "@chakra-ui/react"
function App({ Component }) {
// 2. Use at the root of your app
return (
  <ChakraProvider>
    <Component />
  </ChakraProvider>
 )
}
```

Next.jsの場合
pages/_app.js か pages/_app.tsx（TypeScriptを導入している際はこちら）で以下のように設定していきます。

```tsx
import { ChakraProvider } from "@chakra-ui/react"
function MyApp({ Component, pageProps }) {
  return (
    <ChakraProvider>
      <Component {...pageProps} />
    </ChakraProvider>
  )
}
export default MyApp
```        

        

### 2. useColorModeのセットアップ

ダークモード実装を簡単にできる`useColorMode` というHooksと、アイコンに使用する `IconButton` 、`MoonIcon` 、`SunIcon` をインポートする。（お好みに応じてreact-iconsを使用するのもOK）

```tsx
import { IconButton, useColorMode } from '@chakra-ui/react'
import { MoonIcon, SunIcon } from '@chakra-ui/icons'
```

あとは、useColorModeをこの様に定義する。このuseColorModeというのは現在のカラーモードの状態をcolorModeで保持して、toggleColorModeで切り替えもしてくれる機能があります。

```tsx
const { colorMode, toggleColorMode } = useColorMode()
```

### 3. コンポーネントで使用する

あとはインポートしたIcconButtonというコンポーネントに記述していきます。ポイントはiconプロパティを使用していることと、その中でcolorModeが`'light'` かどうかで、`<MoonIcon />`か `<FaSun />` を出し分けしている点です。

ちなみにcolorModeは、デフォルトで`'light'`か`'dark'` というstringのみを保持する様になっています。（color-mode.utils.d.ts内での宣言: `export declare type ColorMode = "light" | "dark";`）

```tsx
<IconButton
// _focus={{_focus: "none"}} //周りの青いアウトラインが気になる場合に消す方法
mb={10}
aria-label="DarkMode Switch"
icon={colorMode === 'light' ? <MoonIcon /> : <FaSun />} //自分の好みでSunアイコンはreact-iconsを使用しています
onClick={toggleColorMode}
/>
```
![ezgif-2-889e090ad641.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/711874/507f60d8-423c-0f00-8ea0-55d0cf8eb1f4.gif)

この画像のように、Chakra UIで提供しているほとんどのコンポーネントはデフォルトでダークモードに対応してcolorを変化してくれます。
> By default, most of Chakra's components are dark mode compatible. In some scenario, you might need to make your component respond to color mode.

もし自分で切り替え時の色を設定したい場合は上記に併せて `useColorModeValue`が使えます。（以下は[公式サイト][2]から引っ張ってきたコードです。）

`useColorModeValue`は2つの引数を取ることができ、１つ目の引数が`'light'` に対応し、2つ目の引数が`'dark'` に対応します。

```tsx
import { useColorModeValue, useColorMode } from '@chakra-ui/react'

function StyleColorMode() {
  const { toggleColorMode } = useColorMode()

  const bg = useColorModeValue("red.500", "red.200")
  const color = useColorModeValue("white", "gray.800")

  return (
    <>
      <Box mb={4} bg={bg} color={color}>
        This box's style will change based on the color mode.
      </Box>
      <Button size="sm" onClick={toggleColorMode}>
        Toggle Mode
      </Button>
    </>
  )
}
    
```

こんなふうになります。
![ezgif-2-d6c7fe21b154.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/711874/795e34b3-e3ec-859d-f702-a1a034924b96.gif)



ちなみに自分で実装してみた場合はこんな感じです。

```tsx
import { useColorModeValue, useColorMode } from '@chakra-ui/react'

const { colorMode, toggleColorMode } = useColorMode()
const bgGradient = useColorModeValue("linear(to-l, blue.400, #7928CA,#FF0080)", "linear(to-r, blue.400, #7928CA,#FF0080)"

<Heading
 mb={8}
 bgClip="text"
 bgGradient={bgGradient}
 fontSize="6xl"
 fontWeight="extrabold"
>
 NEXT TODO
</Heading>     
    
```

どうでしょうか？密かにタイトルのグラデーション色が反転してますね。
（うん、わかりづらいですね。）
![ezgif-2-9fee03cffd46.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/711874/61db8df5-7236-e84e-97f2-b340c0e0d317.gif)


### +α グローバルでダークモード/ライトモード時のスタイルを指定する

グローバル適用するにはCustomize themeが使えるんでしたね。ここではCustomize themeについての詳しい説明は省きます。気になる方は[公式Doc][3]みてみてください。
ポイントはpropsとして別のコンポーネントで定義した`colorMode`を受け取って、colorを変化させている点です。

```tsx
import { extendTheme } from '@chakra-ui/react'

// NB: Chakra gives you access to `colorMode` and `theme` in `props`
export const theme = extendTheme({
styles: {
  global: (props) => ({
    'html, body': {
      fontSize: 'sm',
      color: props.colorMode === 'dark' ? 'white' : 'gray.600',
      lineHeight: 'tall',
      padding: 0,
      margin: 0,
    },
    a: {
      color: props.colorMode === 'dark' ? 'white' : 'gray.600',
      textDecoration: 'none',
    },
  }),
}
}
```

    
rootとなるコンポーネントで導入した `<ChakraProvider>`にthemeをインポートしてpropsとして渡すのをお忘れなく。


```tsx
import { ChakraProvider } from '@chakra-ui/react'
import { theme } from 'src/theme/theme'

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <ChakraProvider theme={theme}>
      <Component {...pageProps} />
    </ChakraProvider>
  )
}
export default MyApp
```

### +α 初期設定をする方法（CRA /Next.jsの場合）

先程のthemeにconfigで`initialColorMode`と`useSystemColorMode` を定義しextendThemeに渡します。

```tsx
// theme.js

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

const config = {
  initialColorMode: "light", // ColorModeの初期値を設定
  useSystemColorMode: false, // システムで導入しているカラーモードを適用するかどうか（falseは適用しない場合）
}

const theme = extendTheme({ config })

export default theme
```

あとは全体にこの設定を読み込ませていきます。

**・Create React Appの場合**

themeをインポートして`ColorModeScript` をindex.jsファイルに記述します。

```tsx
// index.js

import ReactDOM from "react-dom"
import App from "./App"
import theme from "./theme"

ReactDOM.render(
  <>
    <ColorModeScript initialColorMode={theme.config.initialColorMode} />
    <App />
  </>,
  document.getElementById("root"),
)
```

**・Next.jsの場合**

themeをインポートして`ColorModeScript` を_document.jsファイルに記述します。

```tsx
import { ColorModeScript } from "@chakra-ui/react"
import NextDocument, { Html, Head, Main, NextScript } from "next/document"
import bodyTheme from "src/theme/theme"

export default class Document extends NextDocument {

  render() {
    return (
      <Html lang="en">
        <Head />
        <body>
          <ColorModeScript initialColorMode={bodyTheme.config.initialColorMode} />
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
}
```

## 最後に

こんなに簡単にダークモードを実装できるなんて思ってもいなかったです。
画面を見る時間が増えている現代ではダークモード導入の必要性も高まっているので、今後も活用していきます。
また、実装していたらChakra UIがなくともReactのHooksで実装できそうだなと思ったので、時間がある時にアレンジを加えて試してみたいと思います。

p.s.
最後の初期設定のやり方自体は理解できたのですが、どうしても自分のプロジェクトに`initialColorMode`を導入することができませんでした。`systemColorMode` はtrueにした際に適用されたのでこちらはうまくできているみたいなので、ColorMode Scriptでの読み込みがうまくできてないのかなと疑って、Customize Themeでbodyにあてていた設定を消したり、色々試したりましたがダメみたいでした。もし理由がわかる方がいたら教えていただきたいです。:bow:

ソースコード: [https://github.com/hirooutdoor/next-todo2](https://github.com/hirooutdoor/next-todo2)
・src/theme/theme.ts
・pages/_document.js

---

## 参考資料

Chakra UI公式ドキュメント [useColorMode][1]
[1]:https://chakra-ui.com/docs/features/color-mode#usecolormode
[2]:https://chakra-ui.com/docs/features/color-mode#usecolormodevalue
[3]:https://chakra-ui.com/docs/theming/customize-theme