本文へスキップ
ウェブエンジニア問題集
第6章

MUIのカスタマイズ4段階 — sx・styled・theme.componentsの使い分け

約8分
この章の目次開く

MUIの見た目を変える方法は1つではありません。公式ドキュメントは、影響範囲の狭いものから広いものへという順番で4つの方法を示しています。

段階方法影響範囲
1sx propその1か所だけ
2styled()その自作コンポーネントを使った場所すべて
3theme.componentsアプリ全体の、そのコンポーネントすべて
4GlobalStylesアプリ全体のHTML要素

迷ったら、いちばん狭い範囲から始めて、繰り返しが増えてきたら一段広げるのが原則です。最初から全体に効く方法を選ぶと、あとで例外を作りたくなったときに苦しみます。

学習者学習者

sx を覚えたばかりなんだけど、もう別の方法があるの…?全部覚えないとダメ?

全部を同時に使う必要はありません。sx で書いていて「また同じことを書いている」と感じたときが、次の段階に上がる合図です。


段階1 — sx で一点物を作る

その場限りの調整には sx を使います。

<Button variant="contained" sx={{ mt: 2 }}>
  保存
</Button>
tsx

この段階の利点は、影響範囲が明確なことです。ほかの画面を壊す心配がありません。

欠点は、繰り返すと同じ記述が散らばることです。3回以上同じ sx を書いていたら、次の段階を検討します。


段階2 — styled() で再利用する

styled() は、スタイルを内蔵した新しいコンポーネントを作る関数です。

import { styled } from '@mui/material/styles';
import Button from '@mui/material/Button';
 
const DangerButton = styled(Button)(({ theme }) => ({
  backgroundColor: theme.palette.error.main,
  color: theme.palette.error.contrastText,
  '&:hover': {
    backgroundColor: theme.palette.error.dark,
  },
}));
 
// 使う側
<DangerButton>削除する</DangerButton>;
tsx

引数の関数は theme を受け取れるので、色を直書きせずテーマから引けます。

sxとの書き方の違い

先生先生

styled() は毎回スタイルを計算し直さないので、同じ見た目を大量に並べる場面では sx より有利です。テーブルの行やリストの項目のように、数十回描画されるものに向いています。

段階的に広げていくイメージ

段階3 — theme.components で全体を変える

「このアプリのボタンは全部、角丸なしで大文字変換もしない」のような方針は、テーマに書きます。

const theme = createTheme({
  components: {
    MuiButton: {
      defaultProps: {
        disableElevation: true,
      },
      styleOverrides: {
        root: {
          borderRadius: 0,
          textTransform: 'none',
        },
      },
    },
  },
});
tsx

キーは Mui + コンポーネント名です。Button なら MuiButton、TextField なら MuiTextField になります。

各キーには3つの区画があります。

区画役割
defaultPropspropのデフォルト値を変える。毎回 variant="contained" と書く手間が省ける
styleOverrides見た目のCSSを上書きする
variants条件に応じたバリエーションを追加する

defaultPropsが効く場面

MuiTextField: {
  defaultProps: { size: 'small', variant: 'outlined' },
}
tsx

これを書いておくと、以降 <TextField /> と書くだけで小さめのアウトライン表示になります。「毎回同じpropを渡している」と気づいたら defaultProps の出番です。

styleOverridesのslotを指定する

root は「コンポーネントの一番外側」を指します。内部の特定部分だけを狙うこともできます。

MuiButton: {
  styleOverrides: {
    root: { textTransform: 'none' },
    startIcon: { marginRight: 4 },
  },
}
tsx

どんな名前が使えるかは、公式の各コンポーネントAPIページに一覧があります。内部構造そのものを差し替えたい場合は、次章の slots を使います。


段階4 — GlobalStyles でHTML要素に当てる

MUIのコンポーネントではなく、素のHTML要素に効かせたいときに使います。

import GlobalStyles from '@mui/material/GlobalStyles';
 
<GlobalStyles
  styles={{
    body: { backgroundColor: '#fafafa' },
    'a:focus-visible': { outline: '2px solid currentColor' },
  }}
/>;
tsx

ただし、ページ全体のリセットが目的なら CssBaseline のほうが適しています。GlobalStyles は、それでも足りない追加分に絞って使います。


判断フロー

学習者学習者

実際に書いてるときは、どれを選べばいいか迷いそう…。

次の順で自問すると、ほぼ決まります。

  1. この画面だけの調整か? → sx
  2. 同じ見た目を複数箇所で使うか? → styled()
  3. アプリ全体の方針か? → theme.components
  4. MUIではないHTML要素が対象か? → GlobalStyles

1つ注意があります。theme.components は強力な反面、変更したときの影響範囲が見えにくいという弱点があります。


よくあるハマりどころ

styled() を使ったらServer Componentでエラーになる

styled() で作ったコンポーネントはクライアント側の機能を含みます。定義したファイルに 'use client' を付けてください。

styled() の中で mt: 2 が効かない

前述のとおり、ショートハンドは sx だけの機能です。marginTop: theme.spacing(2) と書きます。

styleOverrides を書いたのに一部だけ効かない

狙った slot が違う可能性があります。root に書いても、内部要素が自前のスタイルを持っていると上書きされません。ブラウザの開発者ツールでクラス名(MuiButton-startIcon など)を確認すると、どのslotを指定すべきか分かります。

sx と theme.components が競合する

sx のほうが後に適用されるため、sx が勝ちます。全体の方針を個別に例外化したいときは、意図して sx で上書きする形になります。


ちゃんと使うためのポイント

  • 影響範囲の狭い順に sx → styled() → theme.components → GlobalStyles
  • 同じ sx を3回書いたら styled() を検討する
  • 毎回同じpropを渡しているなら defaultProps に移す
  • styled() の中ではショートハンドが使えず、theme.spacing() を明示的に呼ぶ
  • テーマに書くのは言葉で説明できる方針だけにする

次の章では、styleOverrides では届かない領域であるslotsとslotPropsを扱います。


参考リンク