はじめに
正直なところ、ReactのUIライブラリ選びって結構悩むんですよ。
Tailwind CSSで自作するか、Ant Designにするか、Chakra UIにするか...選択肢が多すぎて決められない。そんな中で、10年以上の実績と圧倒的なユーザー数を誇るのが**Material UI(MUI)**です。
GitHubで97,400スター以上、月間ダウンロード数2,500万回以上。これだけの数字を見れば、多くの開発者に信頼されていることがわかりますね。
GoogleのMaterial Designをベースにしながらも、独自の進化を遂げてきたこのライブラリ。2025年12月現在、バージョン7.3.6がリリースされており、3,000人以上のコントリビューターによって活発に開発が続いています。
Material UIとは
Material UIは、GoogleのMaterial Designシステムを独自に実装した、堅牢でカスタマイズ可能なReactコンポーネントライブラリです。
最大の特徴は実績と安定性。11年以上の開発期間を経て、数千人のオープンソースコントリビューターによって磨き上げられてきました。
「枯れた技術」という話。これって実はすごく重要で、本番環境で動かすときに「このライブラリ、来年もメンテされてるかな...」という不安がない。これがどれだけ心強いか、30代になるとよくわかります。
公式サイト: https://mui.com/ GitHub: https://github.com/mui/material-ui
特徴・メリット
1. 圧倒的なコンポーネント数
Material UIには、日常的に使うUIコンポーネントがほぼ全て揃っています。
- ボタン、テキストフィールド、チェックボックスなどの基本要素
- ダイアログ、ドロワー、スナックバーなどのフィードバック系
- テーブル、カード、リストなどのデータ表示系
- オートコンプリート、日付ピッカー、スライダーなどの入力系
これ、意外と他のライブラリにはない強みなんですよ。「あれがない」「これがない」で別のライブラリを追加する必要がほとんどない。依存関係が増えないのは運用面で非常に助かります。
2. 強力なテーマシステム
import { createTheme, ThemeProvider } from '@mui/material/styles';
const theme = createTheme({
palette: {
primary: {
main: '#1976d2',
},
secondary: {
main: '#dc004e',
},
mode: 'light', // または 'dark'
},
typography: {
fontFamily: '"Noto Sans JP", "Roboto", sans-serif',
h1: {
fontSize: '2.5rem',
fontWeight: 700,
},
},
shape: {
borderRadius: 8,
},
});
function App() {
return (
<ThemeProvider theme={theme}>
{/* 全てのコンポーネントにテーマが適用される */}
</ThemeProvider>
);
}
一箇所でテーマを定義すれば、全てのコンポーネントに反映される。ダークモード対応もテーマのmodeを切り替えるだけ。時短になりますね。
3. TypeScript完全対応
Material UIは最初からTypeScriptで書かれています。型推論がバッチリ効くので、IDEの補完を活用しながら開発できます。
import Button, { ButtonProps } from '@mui/material/Button';
// 独自のプロップスを追加したカスタムボタン
interface CustomButtonProps extends ButtonProps {
loading?: boolean;
}
function CustomButton({ loading, children, disabled, ...props }: CustomButtonProps) {
return (
<Button disabled={disabled || loading} {...props}>
{loading ? 'Loading...' : children}
</Button>
);
}
型安全なのに冗長な型定義が不要。このバランス感覚が素晴らしい。
4. アクセシビリティへの配慮
WCAG(Web Content Accessibility Guidelines)に準拠したコンポーネント設計がされています。
- キーボードナビゲーション対応
- スクリーンリーダー対応
- 適切なARIA属性の自動付与
- フォーカス管理
個人的には、アクセシビリティを一から自分で実装するのは大変なので、これがデフォルトで備わっているのは非常にありがたい。
5. MUI Xによる高度なコンポーネント
基本のMaterial UIに加えて、MUI Xという拡張ライブラリがあります。
- Data Grid(高機能なテーブル)
- Date/Time Pickers(日時選択)
- Charts(グラフ)
- Tree View(ツリー表示)
業務アプリでよく使う高度なコンポーネントが揃っています。有料プランもありますが、基本機能は無料で使えます。
インストール方法
基本パッケージ
# npm
npm install @mui/material @emotion/react @emotion/styled
# yarn
yarn add @mui/material @emotion/react @emotion/styled
# pnpm
pnpm add @mui/material @emotion/react @emotion/styled
アイコンパッケージ(任意)
npm install @mui/icons-material
フォントの読み込み
<!-- public/index.html または _document.tsx -->
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap"
/>
または、npmでインストール:
npm install @fontsource/roboto
// index.tsx または App.tsx
import '@fontsource/roboto/300.css';
import '@fontsource/roboto/400.css';
import '@fontsource/roboto/500.css';
import '@fontsource/roboto/700.css';
基本的な使い方
シンプルなボタン
import Button from '@mui/material/Button';
function App() {
return (
<div>
<Button variant="text">Text</Button>
<Button variant="contained">Contained</Button>
<Button variant="outlined">Outlined</Button>
</div>
);
}
シンプルですね。propsを変えるだけでスタイルが切り替わります。
テキストフィールド
import TextField from '@mui/material/TextField';
function Form() {
return (
<div>
<TextField label="名前" variant="outlined" />
<TextField label="メール" type="email" variant="outlined" />
<TextField
label="メッセージ"
multiline
rows={4}
variant="outlined"
/>
</div>
);
}
カードコンポーネント
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import CardActions from '@mui/material/CardActions';
import Typography from '@mui/material/Typography';
import Button from '@mui/material/Button';
function ProductCard() {
return (
<Card sx={{ maxWidth: 345 }}>
<CardContent>
<Typography gutterBottom variant="h5" component="div">
製品名
</Typography>
<Typography variant="body2" color="text.secondary">
製品の説明文がここに入ります。
</Typography>
</CardContent>
<CardActions>
<Button size="small">詳細</Button>
<Button size="small" variant="contained">購入</Button>
</CardActions>
</Card>
);
}
sxプロップによるスタイリング
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
function StyledComponent() {
return (
<Box
sx={{
display: 'flex',
gap: 2,
p: 2,
bgcolor: 'background.paper',
borderRadius: 1,
boxShadow: 1,
'&:hover': {
boxShadow: 3,
},
}}
>
<Button
sx={{
bgcolor: 'primary.main',
'&:hover': {
bgcolor: 'primary.dark',
},
}}
variant="contained"
>
ボタン
</Button>
</Box>
);
}
sxプロップを使えば、インラインでスタイルを書けます。テーマの値も参照できるので、一貫性を保ちながらカスタマイズできる。これがコスパ的にかなりデカい。
実践的なユースケース
1. ログインフォーム
import { useState } from 'react';
import Box from '@mui/material/Box';
import TextField from '@mui/material/TextField';
import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography';
import Paper from '@mui/material/Paper';
import InputAdornment from '@mui/material/InputAdornment';
import IconButton from '@mui/material/IconButton';
import Visibility from '@mui/icons-material/Visibility';
import VisibilityOff from '@mui/icons-material/VisibilityOff';
function LoginForm() {
const [showPassword, setShowPassword] = useState(false);
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
console.log({ email, password });
};
return (
<Paper
elevation={3}
sx={{
p: 4,
maxWidth: 400,
mx: 'auto',
mt: 8,
}}
>
<Typography variant="h5" component="h1" gutterBottom align="center">
ログイン
</Typography>
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 2 }}>
<TextField
fullWidth
label="メールアドレス"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
margin="normal"
required
/>
<TextField
fullWidth
label="パスワード"
type={showPassword ? 'text' : 'password'}
value={password}
onChange={(e) => setPassword(e.target.value)}
margin="normal"
required
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton
onClick={() => setShowPassword(!showPassword)}
edge="end"
>
{showPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
</InputAdornment>
),
}}
/>
<Button
type="submit"
fullWidth
variant="contained"
sx={{ mt: 3, mb: 2 }}
>
ログイン
</Button>
</Box>
</Paper>
);
}
2. レスポンシブなナビゲーション
import { useState } from 'react';
import AppBar from '@mui/material/AppBar';
import Toolbar from '@mui/material/Toolbar';
import Typography from '@mui/material/Typography';
import IconButton from '@mui/material/IconButton';
import MenuIcon from '@mui/icons-material/Menu';
import Drawer from '@mui/material/Drawer';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemText from '@mui/material/ListItemText';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import useMediaQuery from '@mui/material/useMediaQuery';
import { useTheme } from '@mui/material/styles';
const menuItems = ['ホーム', 'サービス', '料金', 'お問い合わせ'];
function ResponsiveNavigation() {
const [drawerOpen, setDrawerOpen] = useState(false);
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
return (
<>
<AppBar position="static">
<Toolbar>
<Typography variant="h6" sx={{ flexGrow: 1 }}>
My App
</Typography>
{isMobile ? (
<IconButton
color="inherit"
onClick={() => setDrawerOpen(true)}
>
<MenuIcon />
</IconButton>
) : (
<Box sx={{ display: 'flex', gap: 2 }}>
{menuItems.map((item) => (
<Button key={item} color="inherit">
{item}
</Button>
))}
</Box>
)}
</Toolbar>
</AppBar>
<Drawer
anchor="right"
open={drawerOpen}
onClose={() => setDrawerOpen(false)}
>
<Box sx={{ width: 250 }}>
<List>
{menuItems.map((item) => (
<ListItem button key={item} onClick={() => setDrawerOpen(false)}>
<ListItemText primary={item} />
</ListItem>
))}
</List>
</Box>
</Drawer>
</>
);
}
useMediaQueryフックでブレークポイントを検出して、PCとモバイルで表示を切り替える。レスポンシブ対応がこれだけ簡単にできます。
3. データテーブル
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import Paper from '@mui/material/Paper';
import Chip from '@mui/material/Chip';
interface User {
id: number;
name: string;
email: string;
status: 'active' | 'inactive';
}
const users: User[] = [
{ id: 1, name: '田中太郎', email: 'tanaka@example.com', status: 'active' },
{ id: 2, name: '鈴木花子', email: 'suzuki@example.com', status: 'inactive' },
{ id: 3, name: '佐藤次郎', email: 'sato@example.com', status: 'active' },
];
function UserTable() {
return (
<TableContainer component={Paper}>
<Table>
<TableHead>
<TableRow>
<TableCell>ID</TableCell>
<TableCell>名前</TableCell>
<TableCell>メール</TableCell>
<TableCell>ステータス</TableCell>
</TableRow>
</TableHead>
<TableBody>
{users.map((user) => (
<TableRow key={user.id} hover>
<TableCell>{user.id}</TableCell>
<TableCell>{user.name}</TableCell>
<TableCell>{user.email}</TableCell>
<TableCell>
<Chip
label={user.status === 'active' ? '有効' : '無効'}
color={user.status === 'active' ? 'success' : 'default'}
size="small"
/>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
}
4. 通知(Snackbar)
import { useState } from 'react';
import Button from '@mui/material/Button';
import Snackbar from '@mui/material/Snackbar';
import Alert from '@mui/material/Alert';
function NotificationExample() {
const [open, setOpen] = useState(false);
const handleClick = () => {
setOpen(true);
};
const handleClose = (event?: React.SyntheticEvent | Event, reason?: string) => {
if (reason === 'clickaway') {
return;
}
setOpen(false);
};
return (
<>
<Button variant="contained" onClick={handleClick}>
通知を表示
</Button>
<Snackbar open={open} autoHideDuration={6000} onClose={handleClose}>
<Alert onClose={handleClose} severity="success" sx={{ width: '100%' }}>
保存が完了しました
</Alert>
</Snackbar>
</>
);
}
注意点
いいことばかり書いてきましたが、注意点もあります。
1. バンドルサイズ
全てのコンポーネントをインポートするとバンドルサイズが大きくなります。必要なコンポーネントだけを個別にインポートすることが推奨されています。
// 良い例(Tree Shaking対応)
import Button from '@mui/material/Button';
import TextField from '@mui/material/TextField';
// 避けるべき例
import { Button, TextField } from '@mui/material';
2. Emotionへの依存
Material UI v5からは、スタイリングエンジンとしてEmotionがデフォルトになっています。styled-componentsを使いたい場合は追加設定が必要です。
3. 学習コスト
コンポーネント数が多い分、全てを把握するのに時間がかかります。ただ、公式ドキュメントが非常によくできているので、困ったときはすぐに調べられます。
4. Material Design風のデザイン
Material Designがベースなので、全く異なるデザインシステムを構築したい場合は、テーマのカスタマイズに工数がかかります。完全にオリジナルなUIが必要なら、HeadlessなUIライブラリの方が向いているかもしれません。
まとめ
正直なところ、業務アプリを素早く作りたいなら、Material UIは一択ですね。
以下の条件に当てはまるなら、試してみる価値は間違いなくあります:
- Reactでプロダクションレベルのアプリを作りたい
- 一貫性のあるUIを効率よく構築したい
- アクセシビリティを担保したい
- TypeScriptで型安全に開発したい
- 長期的にメンテナンスされるライブラリを使いたい
97,000スター以上、月間2,500万ダウンロードという数字が、その実力を証明しています。
個人的には、管理画面や業務アプリならMaterial UI、LPやポートフォリオならTailwind CSS、という使い分けをしています。用途に応じて選ぶのが大事ですね。
まずはシンプルなフォームから始めてみてください。コンポーネントの豊富さと使いやすさに驚くはずです。QOL上がること間違いなしです。
公式サイト: https://mui.com/ GitHub: https://github.com/mui/material-ui ドキュメント: https://mui.com/material-ui/getting-started/