web-ui-mui について
web-ui-mui は、MUI を使う React 開発者向けのパターンガイドスキルです。テーマ、コンポーネントのスタイル、レイアウト、ダークモード、MUI X を扱い、カスタマイズ方法の選び方に加え、移行や性能、よくある問題も解説します。
主な機能
- 01テーマと ThemeProvider の設定
- 02sx、styled、テーマ上書きの使い分け
- 03slots と slotProps の利用
- 04カラースキームとダークモードの実装
- 05レイアウトと MUI X のパターン活用
活用シーン
- 01React アプリで一貫した MUI テーマを構築
- 02コンポーネントのカスタマイズ方法を選択
- 03サーバーサイドレンダリングを考慮したダークモードの実装
- 04DataGrid、日付ピッカー、チャートの画面開発
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ agents-inc/ skills --skill web-ui-muiインストール後、Claude Code で/web-ui-muiを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
metadata.yamlreference.mdexamples/core.mddata-grid.mdfeedback.mdform-inputs.mdlayout.mdnavigation.mdstyling.md詳細
MUI(Material UI)のパターン
クイックガイド: MUI は、テーマを基盤とするスタイル設定済みの React コンポーネントライブラリです。
createThemeとThemeProviderで各コンポーネントが参照するトークンを定義します。sxは個々のインスタンスにスタイルを設定し、styled()は再利用可能なコンポーネントを作成します。theme.componentsはすべてのインスタンスを変更し、slots/slotPropsから 内部要素にアクセスできます。現行:v7.x(2025 年 3 月) — CSS レイヤー、slots/slotPropsの全体での標準化、 Grid v2 のGridへの昇格、React 19 への対応。MUI X v8 は DataGrid、DatePicker、 Charts をカバーします。スタイリングエンジンには Emotion を使用します。Pigment CSS はまだアルファ版で、開発が保留されています。
詳細なリソース:
- examples/core.md — カラースキームを使ったテーマ、コンポーネントのオーバーライド、ダークモードメニュー、TypeScript の型拡張、CSS レイヤーを使った SSR および SPA のセットアップ
- examples/styling.md —
styled()、shouldForwardPropによるカスタムプロパティ、テーマ対応のsx、レスポンシブ形式とコールバック形式 - examples/form-inputs.md — TextField、Select、Autocomplete、slots、slotProps
- examples/layout.md — Grid、Stack、Box、Container、レスポンシブなカードグリッド
- examples/navigation.md — AppBar、Drawer、Tabs、レスポンシブなダッシュボードシェル
- examples/feedback.md — Dialog、Snackbar、Alert、Skeleton、CircularProgress
- examples/data-grid.md — DataGrid の列、ページネーション、セルのレンダリング
- reference.md — コンポーネント一覧、テーマの構造、
sxの省略形、ブレークポイント、パッケージ一覧、v6 から v7 への移行、アンチパターンのコード
<critical_requirements>
MUI コードを書く前に
アプリを createTheme() インスタンスで ThemeProvider でラップしてください。 そうしないと、コンポーネントは MUI のデフォルトテーマにフォールバックし、正しくレンダリングされるものの、意図とは異なる見た目になります。エラーは出ず、他の人のデザインが適用されるだけです。
バレルではなくパスからインポートしてください — { Button } from "@mui/material" ではなく、@mui/material/Button を使います。バレルを使うと、初回読み込み時に開発サーバーがパッケージ全体を解析します。また、@mui/icons-material だけでもモジュール数は 2000 を超えます。
内部要素にアクセスするには slots と slotProps を使ってください。 これらはすべてのコンポーネントで使える v7 の API です。components と componentsProps は非推奨で、削除が予定されています。
ダークモードの分岐は theme.applyStyles("dark", { … }) として記述してください。 これにより両方のルールセットが出力され、CSS が適切なものを選択するため、サーバーとクライアントで表示が一致します。theme.palette.mode === "dark" はレンダリング時に判定するため、ハイドレーション前に誤ったテーマが一瞬表示されます。
</critical_requirements>
自動検出: MUI、Material UI、@mui/material、@mui/system、@mui/icons-material、 @mui/x-data-grid、@mui/x-date-pickers、createTheme、ThemeProvider、CssBaseline、sx prop、styled、 useTheme、useColorScheme、colorSchemes、cssVariables、applyStyles、slots、slotProps、 shouldForwardProp、GridColDef、StyledEngineProvider、enableCssLayer
対象範囲:
- MUI のコンポーネントセットを基盤にし、フォークするのではなくテーマを通じてカスタマイズする
- 特定のオーバーライドに対して、
sx、styled()、theme.components、slots/slotPropsのどれを使うか選ぶ - CSS 変数を使ったサーバーレンダリングに耐える方式を含む、カラースキームとダークモード
- モジュール拡張を通じたカスタムパレットカラーとタイポグラフィバリアントの型付け
- MUI X — DataGrid、日付ピッカー、チャート
別の箇所で扱う内容:
- MUI 独自のテーマを超える CSS の方法論とデザイントークンのアーキテクチャ —
enableCssLayerは MUI のスタイルを名前付きレイヤーに配置し、外部スタイルシートが優先順位を設定できるようにします。そのスタイルシートの記述方法は別の箇所で扱います - スタイルなしのプリミティブ — このライブラリには完全にスタイルが適用されているため、すべてのピクセルを自分で管理したいプロジェクトには、ヘッドレスプリミティブが適しています
- フォームの状態とバリデーション —
TextFieldはerrorとhelperTextを受け取り、エラーを表示します。また、inputRefを使って、ref を登録するライブラリに基になる<input>を渡せます。これらの値を何で決定するかは、このスキルの対象外です - サーバー状態 — コンポーネントはデータを props として受け取ります
テーマがプロダクトです。 パレット、タイポグラフィ、間隔、シャドウ、ブレークポイント、z-index、トランジションはすべて一つのオブジェクトにまとまっており、すべてのコンポーネントがそこから値を読み取ります。呼び出し箇所で記述したオーバーライドは、繰り返し適用する必要がある判断です。同じ判断を theme.components に記述すれば、一度で済みます。
カスタマイズは階層化されており、どの階層を選ぶかが重要です。 このインスタンスだけなら sx、再利用可能なバリアントなら styled()、すべてのインスタンスなら theme.components、自分では所有していないコンポーネント内部の要素なら slots/slotProps を使います。状況に必要な範囲を超えて重い階層を選ぶことが、後から変更できないスタイルの主な原因です。
MUI コンポーネントはクライアントコンポーネントです。 コンテキストとエフェクトを使用するため、サーバーレンダリングを行うアプリでは、ThemeProvider の上にキャッシュプロバイダーを配置し、コンポーネントを使うページをクライアント境界で囲む必要があります。
<decision_framework>
スタイリング階層の選択
Used in one place → sx prop
Reused, same shape each time → styled()
Every instance in the app → theme.components.MuiX
An element inside the component → slots / slotProps
レイアウトコンポーネントの選択
One axis, even spacing → Stack
Twelve-column grid → Grid (size prop)
Centred page with a max width → Container
Anything else that needs sx → Box
カラースキーム
No dark mode → one palette in createTheme
Follow the system only → cssVariables: true
User can choose → colorSchemes + useColorScheme
Both, without a flash → cssVariables: { colorSchemeSelector: "data" } + useColorScheme
MUI X のデータ表示
Under ~100 rows, read-only → Table
Sorting, filtering, pagination → DataGrid
Very large datasets → DataGridPro (row virtualisation)
Date or time entry → DatePicker / DateTimePicker
Charts → MUI X Charts
Hierarchy → TreeView
</decision_framework>
基本パターン
パターン 1:テーマとプロバイダー
const theme = createTheme({
palette: { primary: { main: "#1976d2" } },
typography: {
fontFamily: '"Inter", sans-serif',
button: { textTransform: "none" },
},
shape: { borderRadius: 8 },
spacing: 8,
});
<ThemeProvider theme={theme}>
<CssBaseline />
{children}
</ThemeProvider>;
CssBaseline は MUI のノーマライズと、テーマの背景色およびテキスト色を <body> に適用します。これがないと、コンポーネントの周囲のページにはブラウザーのデフォルトスタイルが残ります。
コード全文:examples/core.md
### パターン 2:CSS 変数とダークモード
const theme = createTheme({
cssVariables: { colorSchemeSelector: "data" },
colorSchemes: {
light: { palette: { primary: { main: "#1976d2" } } },
dark: { palette: { primary: { main: "#90caf9" } } },
},
});
const { mode, setMode } = useColorScheme(); // "light" | "dark" | "system"
cssVariables を使うと、両方のパレットがカスタムプロパティとして出力され、data-* 属性によって切り替えられるため、React の実行前に配色が決まります。初回のレンダーでは mode は undefined です。配色に依存するものをレンダーする前に、値が設定されているか確認してください。
コード全体:examples/core.md
パターン 3:sx プロパティ
<Box
sx={{
p: 3, // theme.spacing(3)
bgcolor: "background.paper", // theme.palette.background.paper
borderRadius: 1, // theme.shape.borderRadius
boxShadow: 3, // theme.shadows[3]
width: { xs: "100%", md: "50%" },
"&:hover": { boxShadow: 6 },
}}
/>
数値はテーマのスケールを通して処理され、ドット区切りの文字列はパレットを参照するため、テーマの値をリテラルとして繰り返し記述する必要はありません。ブレークポイントオブジェクトを使えばメディアクエリーを置き換えられ、sx オブジェクトの配列は左から右にマージされます。これにより、オブジェクトを作り直さずに条件付きスタイルを追加できます。
コード全体:examples/styling.md
パターン 4:styled()
const StyledCard = styled(Card)(({ theme }) => ({
padding: theme.spacing(3),
"&:hover": { boxShadow: theme.shadows[8] },
...theme.applyStyles("dark", { backgroundColor: theme.palette.grey[900] }),
}));
同じスタイルを複数回使う場合に使用します。第 2 引数には shouldForwardProp を指定でき、これによってスタイル設定専用のプロパティが不明な属性として DOM に渡されるのを防ぎます。
コード全体:examples/styling.md
パターン 5:スロットと slotProps
<Autocomplete
slots={{ paper: CustomPaper }}
slotProps={{
paper: { elevation: 8, sx: { borderRadius: 2 } },
listbox: { sx: { maxHeight: 300 } },
input: ({ open }) => ({
sx: { borderColor: open ? "primary.main" : "divider" },
}),
}}
renderInput={(params) => <TextField {...params} label="Framework" />}
/>
slots は内部コンポーネントを置き換え、slotProps はそのコンポーネントを設定します。コールバックとして記述した slotProp には、そのスロット固有の状態が渡されます。スロットのコンポーネントはレンダーの外で定義してください。インラインのアロー関数は毎回新しいコンポーネント型になるため、レンダーのたびにスロットが再マウントされます。
コード全体:examples/form-inputs.md
パターン 6:レイアウト
<Grid container spacing={3}>
<Grid size={{ xs: 12, md: 4 }}><Sidebar /></Grid>
<Grid size={{ xs: 12, md: 8 }}><MainContent /></Grid>
</Grid>
<Stack direction={{ xs: "column", sm: "row" }} spacing={2}>
<Button variant="contained">Save</Button>
</Stack>
<Container maxWidth="lg" sx={{ py: 4 }}>{children}</Container>
v7 では Grid は旧 Grid2 であり、xs/sm/md の個別のプロパティではなく、1 つの size プロパティを受け取ります。以前のコンポーネントも GridLegacy として引き続き使用できます。
コード全体:examples/layout.md
パターン 7:TypeScript の型拡張
declare module "@mui/material/styles" {
interface Palette {
neutral: Palette["primary"];
}
interface PaletteOptions {
neutral?: PaletteOptions["primary"];
}
}
declare module "@mui/material/Button" {
interface ButtonPropsColorOverrides {
neutral: true;
}
}
型拡張は 2 つ必要で、どちらも欠かせません。1 つ目はテーマにトークンを追加し、2 つ目はコンポーネントのプロパティ値としてそのトークンを受け入れられるようにします。2 つ目を省くと、テーマにトークンが含まれていても、すべての呼び出し箇所で color="neutral" が型エラーになります。
コード全体:examples/core.md
パターン 8:サーバーレンダリングの設定
<AppRouterCacheProvider options={{ enableCssLayer: true }}>
<GlobalStyles styles="@layer theme, base, mui, components, utilities;" />
<ThemeProvider theme={theme}>
<CssBaseline />
{children}
</ThemeProvider>
</AppRouterCacheProvider>
Emotion はコンポーネントのレンダー時にスタイルを生成するため、サーバーレンダリングを行うアプリでは ThemeProvider の上位にキャッシュプロバイダーが必要です。キャッシュプロバイダーはスタイルを収集し、ストリーミング HTML とともに出力します。これがないと、スタイルのないマークアップが届き、ハイドレーション時にスタイルが再適用されます。MUI は、サポート対象の各サーバーフレームワーク用にアダプターパッケージを公開しています。クライアントのみのアプリでは、代わりに同じ位置で StyledEngineProvider を使用します。enableCssLayer は MUI の出力を名前付きレイヤーに配置し、@layer 宣言によって、そのレイヤーとページ上のほかのすべての要素との順序を固定します。
両方の構成のコード全体:examples/core.md
パフォーマンス
パス指定のインポート。 import Button from "@mui/material/Button" を使うと、バレルの解析を省略できます。バレル形式では、開発サーバーの起動に数倍の時間がかかります。一部のバンドラーやフレームワークはバレルインポートを書き換えるため、コストが発生すると決めつける前に確認してください。パターン ^@mui/[^/]+$ を制限する lint ルールを設けると、再び使われるのを防げます。
コンポーネントが同一性の判定に使う値には、安定した参照を使用してください。 DataGrid の columns、スロットコンポーネント、Dialog の TransitionProps は、いずれも参照で比較されます。モジュールスコープで定義するか、メモ化してください。インラインで定義すると、親がレンダーされるたびに再レンダーまたは再マウントが発生します。
createTheme はバンドルサイズに影響しません。これはコンポーネントではなくデータです。バンドルサイズは、どのコンポーネントをインポートするかで決まります。
<red_flags>
注意すべき問題
実行時に問題が発生します:
- スタイル関数内の
theme.palette.mode === "dark"— レンダリング時に解決されるため、サーバーとクライアントの最初の描画で不一致が起き、誤ったテーマが一瞬表示されます slots内のインラインアロー関数 — レンダリングごとに新しいコンポーネント型が作られるため、スロットが再マウントされ、状態とフォーカスが失われます- DataGrid のインライン
columns— レンダリングごとに新しい配列が作られ、グリッド全体が再レンダリングされます @mui/material/styles/createThemeのように2階層より深いパスからのインポート — v7 の ESM パッケージレイアウトでは、これらのパスは公開されていません- Alert、Skeleton、Autocomplete に対する
@mui/labからのインポート — v7 ではこれらは@mui/materialに移行しました - クライアント境界を設けずにサーバー上で MUI コンポーネントをレンダリングすること — コンテキストとエフェクトが必要なため、境界はルートレイアウトだけでなくページに設ける必要があります
意外な動作:
spacingはピクセル値ではなく倍率です。デフォルトの基準値は8なので、spacing(2)は16pxになりますsxの配列は左から右へマージされ、後の要素が優先されます。これにより条件付きスタイルが機能しますTextFieldは3つのコンポーネント(入力欄、ラベル、ヘルパーテキスト)が組み合わさっているため、実際の<input>を対象にするにはslotProps.inputを使いますzIndexを手動で設定すると、モーダル、ドロワー、スナックバー、ツールチップにすでに値が割り当てられている MUI 独自のスケールと競合しますuseMediaQueryはサーバーでのレンダリング中にfalseを返すため、デスクトップ優先の分岐ではハイドレーション前に誤った内容がレンダリングされますuseColorScheme().modeは最初のレンダリングではundefinedなので、これを使ってスキームに依存するアイコンをレンダリングすると、ハイドレーション不一致が発生します- カスタムのパレットカラーに対する TypeScript 拡張がない場合、型エラーはテーマではなく使用箇所で発生します
componentsとcomponentsPropsは v7 でも引き続き動作しますが、非推奨です