🐛

MUI v5 Theme ~基本の使い方からカスタマイズまで~

2022/04/11に公開

Vite + React + TypeScript 環境で MUI v5 を使用しています。
今回は MUI v5 の Theme についてTips的にまとめてみました。

  • テーマをアプリケーション全体に適用する
  • Light モードと Dark モードを切り替える
  • テーマの色を変更する
  • テーマを一部のコンポーネントに適用する
  • 適用されているテーマの一部を変更したテーマを作成し、一部のコンポーネントに適用する
  • テーマに欧文フォント・日本語フォントを追加する
  • テーマに独自のカラーキーワードを追加し、ボタンで使用する

Themeの基本

デフォルトテーマ

MUI には Light モード用のデフォルトテーマと Dark モード用のデフォルトテーマが用意されています。
それぞれのテーマに設定されている値などは 公式: Default Theme で確認できます。

テーマをアプリケーション全体に適用する global theme

デフォルトテーマは特に何も設定しなくても、アプリケーション全体に適用されています。
試しに、テーマを設定しない状態でもデフォルトテーマが適用されていることを確認してみます。

下記のサンプルは、ボタンにテーマカラーを設定しているだけのSamppleComponent.tsx を
App.tsx から表示しているだけの何でもないコードです。

CssBaseline は MUI が用意しているリセットCSSです。
リセットCSSとは・・・
ブラウザにはデフォルトCSSとよばれるがあります。このデフォルトスタイルはブラウザごとにバグや微妙な差があり、この差を調整してくれるのがリセットCSSです。
詳しくはコチラを参照:Vite+React+Emotionに、リセットCSSを導入する。
MUI が用意しているリセットCSSは App.tsx などのルートコンポーネントに配置しておきます。

App.tsx
import CssBaseline from '@mui/material/CssBaseline'
import { SampleComponent } from './SampleComponent'

export function App() {
  return (
    <>
      <CssBaseline />
      <SampleComponent />
    </>
  )
}
SamppleComponent.tsx
import { Stack, Button } from "@mui/material"

export function SampleComponent()  {
  return (
    <Stack direction="row" spacing={2} sx={{m:2, p:2}}>
      <Button variant="contained" color="primary">primary</Button>
      <Button variant="contained" color="secondary">secondary</Button>
      <Button variant="contained" color="warning">warning</Button>
      <Button variant="contained" color="info">info</Button>
      <Button variant="contained" color="success">success</Button>
    </Stack>
  )
}

ボタンにテーマカラーが設定されています。

明示的にアプリケーション全体にテーマを適用する

明示的にアプリケーション全体にテーマを適用するには、
まずルートコンポーネントで、createTheme でテーマを作成します。
次に ThemeProvider に作成したテーマを指定し、子コンポーネントをラップします。
CssBaseline も ThemeProvider でラップすることがポイントです。
(CssBaseline をラップしないと Body 要素にテーマが適用されません)

App.tsx
import CssBaseline from '@mui/material/CssBaseline'
import { ThemeProvider, createTheme } from '@mui/material/styles'
import { SampleComponent } from './SampleComponent'

export function App() {
+  const theme = createTheme()
  return (
+    <ThemeProvider theme={theme}>
+      <CssBaseline />
      <SampleComponent />
+    </ThemeProvider>
  )
}

Light モードと Dark モードを切り替える

明示的にアプリケーション全体に適用したテーマの動作確認も兼ねて、
アプリケーション全体で、Lightモードテーマ と Darkモードテーマを切り替えます。

作成するテーマのパレットにmode: 'dark'を指定します。
Liteテーマの場合はmode:lightを指定します。
Body 要素にテーマが適用されるよう CssBaseline を ThemeProvider でラップします。

App.tsx
import CssBaseline from '@mui/material/CssBaseline'
import { ThemeProvider, createTheme } from '@mui/material/styles'
import { SampleComponent } from './SampleComponent'


export function App() {
-  const theme = createTheme()
+  const theme = createTheme({
+    palette: {
+      mode: 'dark'
+    }
+  })
  
  return (
    <ThemeProvider theme={theme}>
      <CssBaseline />
      <SampleComponent />
    </ThemeProvider>
  )
}

Darkテーマ が 適用されました。

▶ OSの設定でテーマが切り替わるようにする

OSの設定でテーマが切り替わるようにするサンプルコードです。
useMediaQueryで OS の 表示モードの設定を取得します。

App.tsx
import CssBaseline from '@mui/material/CssBaseline'
import { ThemeProvider, createTheme } from '@mui/material/styles'
+ import useMediaQuery from '@mui/material/useMediaQuery'
import { SampleComponent } from './SampleComponent'

export function App() {
+  const isDarkMode = useMediaQuery('(prefers-color-scheme: dark)')

  const theme = createTheme({
    palette: {
+      mode: isDarkMode ? 'dark' : 'light'
    }
  })

  return (
    <ThemeProvider theme={theme}>
      <CssBaseline />
      <SampleComponent />
    </ThemeProvider>
  )
}

windows11 で Light/Dark モードを切り替えるには 「設定 > 個人設定 > 色 > モードを選ぶ」で変更します。

テーマの色を変更する

次に テーマ の primary の色を変更してみます。
最初に紹介したデフォルトテーマの定義内容を見れば、primary の色を変更する方法はわかると思います。
primary の main色、light色、dark色を上書きします。
下記のサイトを使うと main色から light色、dark色を自動で見つけてくれるので便利です。
https://material.io/resources/color/#!/?view.left=0&view.right=0&primary.color=3F51B5&secondary.color=F44336

デフォルトの primary に設定されている青系の色を、ピンク系の色に変更してみます。

Asp.tsx
import CssBaseline from '@mui/material/CssBaseline'
import { ThemeProvider, createTheme } from '@mui/material/styles'
import { SampleComponent } from './SampleComponent'

export function App() {
  
  const theme = createTheme({
    palette: {
      mode: 'light',
+      primary: {
+        main: '#d87274',
+        light: '#ffa2a3',
+        dark: '#a34449'
        }
      }  
  })

  return (
    <ThemeProvider theme={theme}>
      <CssBaseline />
      <SampleComponent />
    </ThemeProvider>
  )
}

テーマを一部のコンポーネントに適用する

作成したテーマをネストし、テーマを適用したいコンポーネントをラップすることで、一部のコンポーネントだけにテーマを適用できます。

App.tsx
import CssBaseline from '@mui/material/CssBaseline'
import { ThemeProvider, createTheme } from '@mui/material/styles'
import { SampleComponent } from './SampleComponent'

export function App() {
  
  const apptheme = createTheme({
    palette: {
      mode: 'light',
      primary: {
        main: '#d87274',
        light: '#ffa2a3',
        dark: '#a34449'
        }
      }  
  })

  const subtheme = createTheme({
    palette: {
      mode: 'light',
      secondary: {
        main: '#5F3E3A',
        light: '#8d6964',
        dark: '#341714'
        }
      }  
  })

  return (
    <ThemeProvider theme={apptheme}>
      <CssBaseline />
      <span>apptheme 適用</span>
      <SampleComponent />

      <ThemeProvider theme={subtheme}>
        <span>subtheme 適用</span>
        <SampleComponent />
      </ThemeProvider>
    
    </ThemeProvider>
  )
}

適用されているテーマの一部を変更したテーマを作成し、一部のコンポーネントに適用する

primaryカラーを変更したアプリケーションに適用したテーマを取得し、そのテーマの一部を変更した上で一部のコンポーネントに適用します。

先に実行結果を表示します。
アプリケーション全体に適用したテーマは primary カラーを変更しています。
このテーマを取得し secondary カラーを変更した上で、一部のコンポーネントに適用しています。

Appコンポーネントでは primaryカラーを変更したテーマを、アプリケーション全体に適用しています。

App.tsx
import CssBaseline from '@mui/material/CssBaseline'
import { ThemeProvider, createTheme } from '@mui/material/styles'
import { SampleComponent } from './SampleComponent'

export function App() {
  
  const apptheme = createTheme({
    palette: {
      mode: 'light',
      primary: {
        main: '#d87274',
        light: '#ffa2a3',
        dark: '#a34449'
        }
      }  
  })

  return (
    <ThemeProvider theme={apptheme}>
      <CssBaseline />
      <SampleComponent />
    </ThemeProvider>
  )
}

Sampleコンポーネントでは、useTheme()で適用されているテーマを取得し、createThemeで取得したテーマを指定しつつ secondaryカラーを変更しています。

SampleComponent.tsx
import { Box, Stack, Button } from "@mui/material"
import { ThemeProvider, createTheme, useTheme } from '@mui/material/styles'

export function SampleComponent()  {
+  const appTheme = useTheme()

  const localtheme = createTheme(
+    {...appTheme}, 
    {
      palette: {
        mode: 'light',
        secondary: {
          main: '#5F3E3A',
          light: '#8d6964',
          dark: '#341714'
        }
      }  
    }
  )

  return (
    <Box sx={{m:2}}>
      <Stack direction="row" spacing={2} sx={{m:2, p:2}}>  
        <Button variant="contained" color="primary">primary</Button>
        <Button variant="contained" color="secondary">secondary</Button>
        <Button variant="contained" color="warning">warning</Button>
        <Button variant="contained" color="info">info</Button>
        <Button variant="contained" color="success">success</Button>
      </Stack>
      
+      <ThemeProvider theme={localtheme}>
        <Stack direction="row" spacing={2} sx={{m:2, p:2}}>
          <Button variant="contained" color="primary">primary</Button>
          <Button variant="contained" color="secondary">secondary</Button>
          <Button variant="contained" color="warning">warning</Button>
          <Button variant="contained" color="info">info</Button>
          <Button variant="contained" color="success">success</Button>
        </Stack>
      </ThemeProvider>  
    </Box>
  )
}

テーマに欧文フォント・日本語フォントを追加する

デフォルトテーマの typography の設定です。
Robotoフォントが設定されていますが、Robotoフォントの読み込みは行われていません。
fontWeightはデフォルトで 300, 400, 500, 700 が使用されています。

typography: Object
    htmlFontSize: 16
    pxToRem: f ()
    fontFamily: ""Roboto", "Helvetica", "Arial", sans-serif"
    fontSize: 14
    fontWeightLight: 300
    fontWeightRegular: 400
    fontWeightMedium: 500
    fontWeightBold: 700

googleフォントから、欧文フォントにRobotoフォント、日本語フォントにNoto Sans JP を使用しようと思います。
googoleフォントからRobotoフォントを選択し、fontWeightは300, 400, 500, 700 の link要素をコピーします。

同じようにNoto Sans JP もコピーします。

index.htmlで Roboto フォントと Noto Sans JP フォントを読み込みます。

index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
    <!-- Robotoフォント読み込み -->
    <link
      rel="stylesheet"
      href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
    />
    <!-- Noto Sans JPフォント読み込み -->
    <link
      rel="stylesheet"
      href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@300;400;500;700&display=swap"
    />

    <link
      rel="stylesheet"
      href="https://fonts.googleapis.com/icon?family=Material+Icons"
    />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite App</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

Appコンポーネントで、アプリケーション全体に適用するテーマの typography に fontFamily を設定します。

App.tsx
import CssBaseline from '@mui/material/CssBaseline'
import { ThemeProvider, createTheme } from '@mui/material/styles'
import { Typography } from '@mui/material'

export function App() {
  
  const apptheme = createTheme({
    typography: {
      fontFamily: [
       'Roboto',
       '"Noto Sans JP"', 
       '"Helvetica"',
       'Arial',
       'sans-serif',
     ].join(','),
    }
  })

  return (
    <ThemeProvider theme={apptheme}>
      <CssBaseline />
      <Typography>Almost before we knew it, we had left the ground.</Typography>
      <Typography>人類社会のすべての構成員の固有の尊厳と平等で譲ることのできない権利とを承認することは</Typography>
    </ThemeProvider>
  )
}

テーマに独自のカラーキーワードを追加し、ボタンで使用する

デフォルトテーマの palette に primary, secondary, error, warning, info, successといったカラーキーワードが設定されています。
この palette に独自のキーワードを追加し、 ボタンの color プロップで使用できるようにします。

ファイル名はなんでもいいですが、拡張用のファイルを作成します。
themeOption.ts というファイル名で作成しました。

PaletteOptions を拡張して、追加するカラーキーワードを設定します。
またボタンの Colorプロップを拡張し、追加したカラーキーワードを使用できるようにします。
ボタン以外でも使用する場合は、そのコンポーネントの Colorプロップも拡張します。

themeOption.ts
import * as PaletteColorOptions from '@mui/material/styles/createPalette'

// PaletteOptions を拡張して、カラーキーワードを追加
declare module '@mui/material/styles/createPalette' {
    interface PaletteOptions {    
      mycolor1?: PaletteColorOptions;
      mycolor2?: PaletteColorOptions;
    }
}

// Button の color prop に追加
declare module '@mui/material/Button' {
  interface ButtonPropsColorOverrides {
    mycolor1: true;
    mycolor2: true;
  }
}

テーマの palette に追加したカラーキーワードの main, light, dark カラーを設定します。

App.tsx
import CssBaseline from '@mui/material/CssBaseline'
import { ThemeProvider, createTheme } from '@mui/material/styles'
import { Stack, Button } from '@mui/material'

export function App() {
  
  const apptheme = createTheme({
    palette: {
      mycolor1:{
        main: '#65b2c6',
        light: '#98e4f9',
        dark: '#308295'
      },
      mycolor2:{
        main: '#d57276',
        light: '#ffa2a5',
        dark: '#a0444a'
      }
    }
  })

  return (
    <ThemeProvider theme={apptheme}>
      <CssBaseline />
      <Stack direction="row" spacing={2} sx={{m:2}}>
        <Button variant="contained" color="mycolor1">mycolor1</Button>
        <Button variant="contained" color="mycolor2">mycolor2</Button>
      </Stack>
     
    </ThemeProvider>
  )
}

以上、MUI v5 の Theme について、開発初期に必要そうな部分をまとめてみました。

Discussion