【Mantine】テーマをカスタマイズする

# はじめに
この記事では、React UI コンポーネントライブラリ Mantine のテーマのカスタマイズ方法を記載します。

https://mantine.dev/

# 開発環境
開発環境は以下の通りです。

- Windows 11
- Next.js 14.2.4
- React 18.3.1
- TypeScript 5.5.2
- Node.js 20.13.1
- npm
- @mantine/core 7.12.1
- @mantine/hooks 7.12.1

# テーマのカスタマイズ方法
テーマのカスタマイズ方法は、カスタマイズ対象によって、以下の2種類あります。

1. アプリ全体 : `MantineProvider` を利用する
1. 各コンポーネント単位 : テーマを上書きする

今回は 1. の `MantineProvider` を利用して、アプリ全体のテーマを一括してカスタマイズする方法を記載します。

# MantineProvider でテーマをカスタマイズ
`MantineProvider` の `props` に `theme` を渡してカスタマイズします。`theme` 内の値がアプリ全体のテーマとして設定されます。

例えば、画面上に `fz (font-size)` が `md` の `Text` コンポーネントがあるとします。

```page.tsx
import { Container, Text } from "@mantine/core";

export default function Home() {
  return (
    <Container m="md">
      <Text fz="md">Hello Mantine</Text>
    </Container>
  );
}
```

デフォルトでは、`fz (font-size)` が `md` の場合、フォントサイズは、`16px` となります。

```layout.tsx
import "@mantine/core/styles.css";
import { ColorSchemeScript, MantineProvider } from "@mantine/core";

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <head>
        <ColorSchemeScript />
      </head>
      <body>
        <MantineProvider>{children}</MantineProvider>
      </body>
    </html>
  );
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/e052d6d4-fe82-3201-6351-991e5ce4eb32.png)

テーマをカスタマイズして、`fontSizes` の `md` を `24px` にすると、フォントサイズが `24px` になります。

```page.tsx
import "@mantine/core/styles.css";
import { ColorSchemeScript, MantineProvider } from "@mantine/core";

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <head>
        <ColorSchemeScript />
      </head>
      <body>
        <MantineProvider
          theme={{
            fontSizes: { md: "24px" },
          }}
        >
          {children}
        </MantineProvider>
      </body>
    </html>
  );
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/0b538c39-e7a7-fccd-0695-83d0165737f1.png)

# テーマ設定ファイル
先ほどの例では、`MantineProvider` の `props` に直接カスタマイズする値を記載しましたが、別途テーマ設定用のファイルを作成して利用することもできます。

```theme.ts
import { createTheme } from "@mantine/core";

export const theme = createTheme({
  fontSizes: {
    md: "24px",
  },
});
```

```layout.tsx
import "@mantine/core/styles.css";
import { ColorSchemeScript, MantineProvider } from "@mantine/core";
import { theme } from "@/theme";

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <head>
        <ColorSchemeScript />
      </head>
      <body>
        <MantineProvider theme={theme}>{children}</MantineProvider>
      </body>
    </html>
  );
}
```

# カラーのカスタマイズ
Mantine では、Open Color という1つのカラーにつき、10種類の色合いを持つカラー定義を利用しています。

https://yeun.github.io/open-color/

そのため、カラーをカスタマイズする場合、1つのカラーにつき、10種類の色合いを指定する必要があります。

```theme.ts
import { createTheme } from "@mantine/core";

export const theme = createTheme({
  colors: {
    blue: [
      "#dff4ff",
      "#b2ddff",
      "#83c5fb",
      "#54aef7",
      "#2997f4",
      "#147edb",
      "#0862ab",
      "#00467b",
      "#002a4c",
      "#000f1e",
    ],
  },
});

```

```page.tsx
import { Container, Text } from "@mantine/core";

export default function Home() {
  return (
    <Container m="md">
      <Text c="blue.6">
        Hello Mantine
      </Text>
    </Container>
  );
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/3b27c099-88e2-151e-26c0-0b2f1699b0e5.png)

10種類の色合いを自身で作成して、指定するのは大変なので、以下のようなツールを使って生成することもできます。

- [Themera](https://themera.vercel.app/)
- [Material Design Colors](https://materialui.co/colors)
- [Omatsuri](https://omatsuri.app/color-shades-generator)
- [Smart Swatch](https://smart-swatch.netlify.app/)

# コンポーネントのカスタマイズ
コンポーネントもテーマとして、カスタマイズできます。

## props のデフォルト値のカスタマイズ
`Default props` を利用すると props のデフォルト値をカスタマイズできます。

https://mantine.dev/theming/default-props/

```theme.ts
"use client";

import { Button, createTheme } from "@mantine/core";

export const theme = createTheme({
  components: {
    Button: Button.extend({
      defaultProps: {
        color: "cyan",
        variant: "outline",
      },
    }),
  },
});
```

```page.tsx
import { Button, Container, Group } from "@mantine/core";

export default function Home() {
  return (
    <Container m="md">
      <Group>
        <Button>Default Button</Button>
        <Button color="red" variant="filled">
          Button with props
        </Button>
      </Group>
    </Container>
  );
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/50641017-917b-94fe-36ff-3c825cf70dab.png)

## `Styles API` のカスタマイズ
`Styles API` のカスタマイズもできます。

https://mantine.dev/styles/styles-api/

```theme.ts
"use client";

import { Button, createTheme } from "@mantine/core";

export const theme = createTheme({
  colors: {
    blue: [
      "#dff4ff",
      "#b2ddff",
      "#83c5fb",
      "#54aef7",
      "#2997f4",
      "#147edb",
      "#0862ab",
      "#00467b",
      "#002a4c",
      "#000f1e",
    ],
  },
  components: {
    Button: Button.extend({
      styles: {
        label: {
          color: "darkcyan",
        },
      },
    }),
  },
});
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/24a03ebd-dc60-d4b8-5035-0bcd330ee713.png)


## CSS 変数を利用したカスタマイズ
`props` で渡された値によって、カスタマイズ内容を変えることもできます。

```theme.ts
"use client";

import { Button, createTheme, rem } from "@mantine/core";

export const theme = createTheme({
  components: {
    Button: Button.extend({
      vars: (theme, props) => {
        if (props.size === "xxl") {
          return {
            root: {
              "--button-height": rem(60),
              "--button-padding-x": rem(30),
              "--button-fz": rem(24),
            },
          };
        }

        if (props.size === "xxs") {
          return {
            root: {
              "--button-height": rem(24),
              "--button-padding-x": rem(10),
              "--button-fz": rem(10),
            },
          };
        }

        return { root: {} };
      },
    }),
  },
});

```

```page.tsx
import { Button, Container, Group, Space } from "@mantine/core";

export default function Home() {
  return (
    <Container m="md">
      <Group>
        <Button size="xxl">XXL Button</Button>
        <Button size="xxs">XXS Button</Button>
      </Group>
    </Container>
  );
}

```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/1db42220-188a-a446-5aaa-ff8c2462ff74.png)

# 参考
- [Colors](https://mantine.dev/theming/colors/)
- [Default props](https://mantine.dev/theming/default-props/)
- [MantineProvider](https://mantine.dev/theming/mantine-provider/)
- [Open Color](https://yeun.github.io/open-color/)
- [Styles API](https://mantine.dev/styles/styles-api/)
- [Theme object](https://mantine.dev/theming/theme-object/)