Material-UIは、Reactベースのコンポーネントライブラリで、Googleのマテリアルデザインに基づいた美しいUIを簡単に構築できるツールです。2025年3月にリリースされたv7が最新バージョンとなっており、モダンなWebアプリケーション開発に不可欠なライブラリとして広く利用されています。

1. Material-UIの基本情報

1.1 Material-UIとは

Material-UIは、Reactアプリケーション向けの包括的なUIコンポーネントライブラリです。Googleのマテリアルデザインの概念を実装し、美しく統一されたユーザーインターフェースを提供します。

  • 正式名称: MUI(旧Material-UI)
  • 最新バージョン: v7.0.2(2025年5月現在)
  • 公式サイト: mui.com
  • ライセンス: MIT(商用利用可)

1.2 なぜMaterial-UIを使うのか?

  • デザインの一貫性: マテリアルデザインガイドラインに基づいた統一された美しいUIを実現
  • 開発効率の向上: 再利用可能なコンポーネントによりUIの実装時間を大幅に短縮
  • レスポンシブ対応: モバイルからデスクトップまで、様々な画面サイズに対応するコンポーネント
  • カスタマイズ性: テーマやスタイルを柔軟にカスタマイズ可能
  • アクセシビリティ: 標準でアクセシビリティに配慮された実装

2. インストールと基本セットアップ

2.1 インストール方法

Reactプロジェクトにインストールする基本的な方法は以下の通りです。

# npm の場合
npm install @mui/material @emotion/react @emotion/styled

# yarn の場合
yarn add @mui/material @emotion/react @emotion/styled

2.2 フォントと依存関係の追加

Material-UIはデフォルトでRobotoフォントを使用します。フォントの追加方法は以下の通りです。

# npm の場合
npm install @fontsource/roboto

# yarn の場合
yarn add @fontsource/roboto

エントリーポイント(index.jsなど)で以下をインポートします。

import '@fontsource/roboto/300.css';
import '@fontsource/roboto/400.css';
import '@fontsource/roboto/500.css';
import '@fontsource/roboto/700.css';

アイコンを使用する場合は、以下のパッケージもインストールします。

# npm の場合
npm install @mui/icons-material

# yarn の場合
yarn add @mui/icons-material

3. 基本的なコンポーネントの使い方

3.1 Buttonコンポーネント

Buttonはユーザーのアクションをトリガーする基本的なUIコンポーネントです。

import Button from '@mui/material/Button';

function MyComponent() {
  return (
    <div>
      <Button>テキストボタン</Button>
      <Button variant="contained">塗りつぶしボタン</Button>
      <Button variant="outlined">アウトラインボタン</Button>
      <Button variant="contained" color="primary">プライマリーカラー</Button>
      <Button variant="contained" color="secondary">セカンダリーカラー</Button>
      <Button variant="contained" disabled>無効化ボタン</Button>
    </div>
  );
}

3.2 テキストフィールド

import TextField from '@mui/material/TextField';

function MyForm() {
  return (
    <div>
      <TextField label="標準" />
      <TextField label="必須" required />
      <TextField label="無効" disabled />
      <TextField label="パスワード" type="password" />
      <TextField
        label="マルチライン"
        multiline
        rows={4}
      />
    </div>
  );
}

3.3 カードコンポーネント

カードは情報を整理して表示するためのコンポーネントです。

import Card from '@mui/material/Card';
import CardActions from '@mui/material/CardActions';
import CardContent from '@mui/material/CardContent';
import CardMedia from '@mui/material/CardMedia';
import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography';

function MediaCard() {
  return (
    <Card sx={{ maxWidth: 345 }}>
      <CardMedia
        component="img"
        height="140"
        image="/static/images/sample.jpg"
        alt="サンプル画像"
      />
      <CardContent>
        <Typography gutterBottom variant="h5" component="div">
          カードタイトル
        </Typography>
        <Typography variant="body2" color="text.secondary">
          カードの内容をここに記述します。情報を簡潔に伝えるためのテキストを配置します。
        </Typography>
      </CardContent>
      <CardActions>
        <Button size="small">詳細</Button>
        <Button size="small">共有</Button>
      </CardActions>
    </Card>
  );
}

4. レスポンシブデザインの実装

Material-UIはレスポンシブなUIを簡単に実現するための機能を提供しています。

4.1 ブレイクポイント

Material-UIのデフォルトブレイクポイントは以下の通りです。

  • xs: 0px以上
  • sm: 600px以上
  • md: 900px以上
  • lg: 1200px以上
  • xl: 1536px以上

4.2 Gridコンポーネント

Gridコンポーネントは、レスポンシブレイアウトを実現するための柔軟なグリッドシステムを提供します。

import Grid from '@mui/material/Grid';
import Paper from '@mui/material/Paper';

function ResponsiveGrid() {
  return (
    <Grid container spacing={2}>
      <Grid item xs={12} sm={6} md={4}>
        <Paper>xs=12 sm=6 md=4</Paper>
      </Grid>
      <Grid item xs={12} sm={6} md={4}>
        <Paper>xs=12 sm=6 md=4</Paper>
      </Grid>
      <Grid item xs={12} sm={6} md={4}>
        <Paper>xs=12 sm=6 md=4</Paper>
      </Grid>
    </Grid>
  );
}

この例では、画面幅に応じてグリッドのサイズが変わります:

  • モバイル(xs): 1列表示(各アイテムが画面幅いっぱい)
  • タブレット(sm): 2列表示
  • デスクトップ(md以上): 3列表示

4.3 スタイルでのレスポンシブ設定

sx propを使用して、ブレイクポイントごとに異なるスタイルを適用できます。

import Box from '@mui/material/Box';

function ResponsiveBox() {
  return (
    <Box
      sx={{
        padding: { xs: 1, sm: 2, md: 3 },
        fontSize: { xs: '0.8rem', sm: '1rem', md: '1.2rem' },
        backgroundColor: { xs: 'primary.light', md: 'primary.main' },
      }}
    >
      レスポンシブなボックス
    </Box>
  );
}

4.4 useMediaQueryフック

コンポーネントの条件付きレンダリングにはuseMediaQueryフックが便利です。

import useMediaQuery from '@mui/material/useMediaQuery';
import { useTheme } from '@mui/material/styles';

function ResponsiveComponent() {
  const theme = useTheme();
  const isMobile = useMediaQuery(theme.breakpoints.down('sm'));

  return (
    <div>
      {isMobile ? (
        <p>モバイル向け表示</p>
      ) : (
        <p>デスクトップ向け表示</p>
      )}
    </div>
  );
}

5. スタイリングとテーマのカスタマイズ

5.1 sx propを使ったスタイリング

Material-UIではsxプロパティを使って直接コンポーネントにスタイルを適用できます。

import Box from '@mui/material/Box';

function StyledBox() {
  return (
    <Box
      sx={{
        bgcolor: 'primary.main',
        color: 'white',
        p: 2,
        m: 1,
        borderRadius: 2,
        fontSize: '1.2rem',
        fontWeight: 'bold',
        '&:hover': {
          backgroundColor: 'primary.dark',
        },
      }}
    >
      スタイリングされたボックス
    </Box>
  );
}

5.2 テーマのカスタマイズ

ThemeProviderを使用してアプリ全体のテーマをカスタマイズできます。

import { createTheme, ThemeProvider } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import Button from '@mui/material/Button';

// カスタムテーマの作成
const theme = createTheme({
  palette: {
    primary: {
      main: '#556cd6',
    },
    secondary: {
      main: '#19857b',
    },
    error: {
      main: '#ff1744',
    },
    background: {
      default: '#f5f5f5',
    },
  },
  typography: {
    fontFamily: [
      '"Helvetica Neue"',
      'Arial',
      'sans-serif',
    ].join(','),
    h1: {
      fontSize: '2.2rem',
      fontWeight: 500,
    },
  },
  components: {
    MuiButton: {
      styleOverrides: {
        root: {
          borderRadius: 8,
        },
      },
    },
  },
});

function ThemedApp() {
  return (
    <ThemeProvider theme={theme}>
      <CssBaseline />
      <Button variant="contained">カスタムテーマボタン</Button>
    </ThemeProvider>
  );
}

5.3 スタイルのカスタマイズにEmotionを活用

styled API(@emotion/styled)を使用して、カスタムスタイリングされたコンポーネントを作成できます。

import { styled } from '@mui/material/styles';
import Button from '@mui/material/Button';

// カスタムボタンコンポーネントの作成
const CustomButton = styled(Button)(({ theme }) => ({
  color: theme.palette.getContrastText('#ff4081'),
  backgroundColor: '#ff4081',
  '&:hover': {
    backgroundColor: '#f01b64',
  },
  padding: '10px 24px',
  borderRadius: 25,
}));

function CustomizedButtons() {
  return (
    <div>
      <CustomButton>カスタムボタン</CustomButton>
    </div>
  );
}

6. 実践的なUI例

6.1 ログインフォーム

import React, { useState } from 'react';
import { 
  Box, 
  Button, 
  TextField, 
  Typography, 
  Container, 
  Paper,
  Link,
  Alert
} from '@mui/material';

function LoginForm() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [error, setError] = useState('');

  const handleSubmit = (event) => {
    event.preventDefault();
    // バリデーションの例
    if (!email || !password) {
      setError('メールアドレスとパスワードを入力してください');
      return;
    }
    // 実際のログイン処理をここに記述
    console.log('ログイン情報:', { email, password });
  };

  return (
    <Container maxWidth="xs">
      <Paper elevation={3} sx={{ p: 4, mt: 8 }}>
        <Box
          sx={{
            display: 'flex',
            flexDirection: 'column',
            alignItems: 'center',
          }}
        >
          <Typography component="h1" variant="h5">
            ログイン
          </Typography>

          {error && <Alert severity="error" sx={{ mt: 2, width: '100%' }}>{error}</Alert>}

          <Box component="form" onSubmit={handleSubmit} sx={{ mt: 1, width: '100%' }}>
            <TextField
              margin="normal"
              required
              fullWidth
              id="email"
              label="メールアドレス"
              name="email"
              autoComplete="email"
              autoFocus
              value={email}
              onChange={(e) => setEmail(e.target.value)}
            />
            <TextField
              margin="normal"
              required
              fullWidth
              name="password"
              label="パスワード"
              type="password"
              id="password"
              autoComplete="current-password"
              value={password}
              onChange={(e) => setPassword(e.target.value)}
            />
            <Button
              type="submit"
              fullWidth
              variant="contained"
              sx={{ mt: 3, mb: 2 }}
            >
              ログイン
            </Button>
            <Box sx={{ textAlign: 'center' }}>
              <Link href="#" variant="body2">
                パスワードをお忘れですか?
              </Link>
              <Box mt={1}>
                <Link href="#" variant="body2">
                  アカウントをお持ちでない方はこちら
                </Link>
              </Box>
            </Box>
          </Box>
        </Box>
      </Paper>
    </Container>
  );
}

6.2 レスポンシブなナビゲーションバー

import React, { useState } from 'react';
import { 
  AppBar, 
  Box, 
  Toolbar, 
  IconButton, 
  Typography, 
  Menu, 
  Container, 
  Button, 
  MenuItem,
  useMediaQuery,
  useTheme
} from '@mui/material';
import MenuIcon from '@mui/icons-material/Menu';

const pages = ['ホーム', '製品', 'サービス', '会社情報', 'お問い合わせ'];

function ResponsiveAppBar() {
  const theme = useTheme();
  const isMobile = useMediaQuery(theme.breakpoints.down('md'));
  const [anchorElNav, setAnchorElNav] = useState(null);

  const handleOpenNavMenu = (event) => {
    setAnchorElNav(event.currentTarget);
  };

  const handleCloseNavMenu = () => {
    setAnchorElNav(null);
  };

  return (
    <AppBar position="static">
      <Container maxWidth="xl">
        <Toolbar disableGutters>
          {/* ロゴ - 大画面用 */}
          <Typography
            variant="h6"
            noWrap
            component="div"
            sx={{ mr: 2, display: { xs: 'none', md: 'flex' } }}
          >
            LOGO
          </Typography>

          {/* モバイルメニュー */}
          {isMobile ? (
            <>
              <Box sx={{ flexGrow: 1, display: { xs: 'flex', md: 'none' } }}>
                <IconButton
                  size="large"
                  aria-controls="menu-appbar"
                  aria-haspopup="true"
                  onClick={handleOpenNavMenu}
                  color="inherit"
                >
                  <MenuIcon />
                </IconButton>
                <Menu
                  id="menu-appbar"
                  anchorEl={anchorElNav}
                  anchorOrigin={{
                    vertical: 'bottom',
                    horizontal: 'left',
                  }}
                  keepMounted
                  transformOrigin={{
                    vertical: 'top',
                    horizontal: 'left',
                  }}
                  open={Boolean(anchorElNav)}
                  onClose={handleCloseNavMenu}
                  sx={{
                    display: { xs: 'block', md: 'none' },
                  }}
                >
                  {pages.map((page) => (
                    <MenuItem key={page} onClick={handleCloseNavMenu}>
                      <Typography textAlign="center">{page}</Typography>
                    </MenuItem>
                  ))}
                </Menu>
              </Box>
              <Typography
                variant="h6"
                noWrap
                component="div"
                sx={{ flexGrow: 1, display: { xs: 'flex', md: 'none' } }}
              >
                LOGO
              </Typography>
            </>
          ) : (
            // デスクトップメニュー
            <Box sx={{ flexGrow: 1, display: { xs: 'none', md: 'flex' } }}>
              {pages.map((page) => (
                <Button
                  key={page}
                  onClick={handleCloseNavMenu}
                  sx={{ my: 2, color: 'white', display: 'block' }}
                >
                  {page}
                </Button>
              ))}
            </Box>
          )}
        </Toolbar>
      </Container>
    </AppBar>
  );
}

6.3 製品カードグリッド

import React from 'react';
import {
  Box,
  Card,
  CardActions,
  CardContent,
  CardMedia,
  Button,
  Typography,
  Grid,
  Container,
  Rating
} from '@mui/material';

// サンプルデータ
const products = [
  {
    id: 1,
    name: '製品A',
    description: '高品質な製品Aの詳細説明。多機能でコストパフォーマンスに優れています。',
    price: '¥9,800',
    image: 'https://example.com/productA.jpg',
    rating: 4.5
  },
  {
    id: 2,
    name: '製品B',
    description: '軽量でポータブルな製品B。外出先での使用に最適です。',
    price: '¥12,800',
    image: 'https://example.com/productB.jpg',
    rating: 4.2
  },
  {
    id: 3,
    name: '製品C',
    description: '最新テクノロジーを搭載した製品C。次世代の体験をお届けします。',
    price: '¥15,800',
    image: 'https://example.com/productC.jpg',
    rating: 4.8
  },
  {
    id: 4,
    name: '製品D',
    description: 'シンプルで使いやすい製品D。初心者にもおすすめです。',
    price: '¥7,800',
    image: 'https://example.com/productD.jpg',
    rating: 4.0
  },
];

function ProductGrid() {
  return (
    <Container sx={{ py: 4 }}>
      <Typography variant="h4" component="h1" gutterBottom align="center">
        製品一覧
      </Typography>
      <Grid container spacing={4}>
        {products.map((product) => (
          <Grid item key={product.id} xs={12} sm={6} md={4}>
            <Card 
              sx={{ 
                height: '100%', 
                display: 'flex', 
                flexDirection: 'column',
                transition: '0.3s',
                '&:hover': {
                  transform: 'translateY(-10px)',
                  boxShadow: '0 12px 20px rgba(0,0,0,0.1)',
                }
              }}
            >
              <CardMedia
                component="img"
                height="200"
                image={product.image}
                alt={product.name}
                sx={{ objectFit: 'cover' }}
              />
              <CardContent sx={{ flexGrow: 1 }}>
                <Typography gutterBottom variant="h5" component="div">
                  {product.name}
                </Typography>
                <Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
                  <Rating value={product.rating} precision={0.1} readOnly />
                  <Typography variant="body2" color="text.secondary" sx={{ ml: 1 }}>
                    {product.rating}
                  </Typography>
                </Box>
                <Typography variant="body2" color="text.secondary">
                  {product.description}
                </Typography>
                <Typography variant="h6" color="primary" sx={{ mt: 2 }}>
                  {product.price}
                </Typography>
              </CardContent>
              <CardActions>
                <Button size="small">詳細を見る</Button>
                <Button size="small" variant="contained" color="primary" sx={{ ml: 'auto' }}>
                  カートに追加
                </Button>
              </CardActions>
            </Card>
          </Grid>
        ))}
      </Grid>
    </Container>
  );
}

7. 実践的なヒントとベストプラクティス

7.1 パフォーマンス最適化

Material-UIを使用する際のパフォーマンス最適化のヒントです。

  1. コンポーネントの遅延読み込み
   import React, { Suspense, lazy } from 'react';

   // 重いコンポーネントを遅延読み込み
   const HeavyComponent = lazy(() => import('./HeavyComponent'));

   function App() {
     return (
       <Suspense fallback={<div>Loading...</div>}>
         <HeavyComponent />
       </Suspense>
     );
   }
  1. 不必要な再レンダリングの防止
   import React, { memo } from 'react';

   const MyComponent = memo(function MyComponent(props) {
     return <div>{props.name}</div>;
   });
  1. バンドルサイズの最適化
   // 個別のコンポーネントをインポート
   import Button from '@mui/material/Button';
   // 以下のようにまとめてインポートしない
   // import { Button } from '@mui/material';

7.2 テーマの一貫性を保つ

  1. テーマの定義を一箇所に集約
  • プロジェクトのルートでテーマを定義し、アプリ全体で一貫して使用する
  1. デザイントークンを活用
   // theme.js
   const theme = createTheme({
     palette: {
       primary: {
         main: '#1976d2',
         light: '#4791db',
         dark: '#115293',
       },
     },
     spacing: 8, // 8pxを基準とするスペーシング
     typography: {
       fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
       fontSize: 14,
       h1: {
         fontSize: '2.5rem',
       },
     },
   });
  1. 共通のスタイリングコンポーネントの作成
   // commonStyles.js
   import { styled } from '@mui/material/styles';
   import Box from '@mui/material/Box';

   export const PageContainer = styled(Box)(({ theme }) => ({
     padding: theme.spacing(3),
     [theme.breakpoints.down('sm')]: {
       padding: theme.spacing(1),
     },
   }));

7.3 エラー処理とローディング状態

ユーザーエクスペリエンスを向上させるためのエラー処理とローディング状態の管理。

import React, { useState, useEffect } from 'react';
import { 
  CircularProgress, 
  Alert, 
  Box, 
  Button 
} from '@mui/material';

function DataFetchComponent() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const fetchData = async () => {
    setLoading(true);
    setError(null);

    try {
      // APIからデータを取得
      const response = await fetch('https://api.example.com/data');
      if (!response.ok) {
        throw new Error('Network response was not ok');
      }
      const result = await response.json();
      setData(result);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    fetchData();
  }, []);

  return (
    <Box sx={{ p: 2 }}>
      {loading && (
        <Box sx={{ display: 'flex', justifyContent: 'center', my: 4 }}>
          <CircularProgress />
        </Box>
      )}

      {error && (
        <Box sx={{ my: 2 }}>
          <Alert severity="error">{error}</Alert>
          <Button 
            variant="outlined" 
            onClick={fetchData} 
            sx={{ mt: 2 }}
          >
            再試行
          </Button>
        </Box>
      )}

      {!loading && !error && data && (
        <div>
          {/* データの表示 */}
        </div>
      )}
    </Box>
  );
}

8. まとめ

Material-UIは、Reactアプリケーションでプロフェッショナルなユーザーインターフェースを素早く構築するための強力なツールです。このガイドでは、基本的なセットアップから高度なカスタマイズ、実践的なUIパターンまで幅広く解説しました。

Material-UIを活用することで、以下のメリットが得られます:

  • 開発の高速化: 再利用可能な高品質コンポーネントを使用して開発時間を短縮
  • デザイン一貫性: マテリアルデザインガイドラインに基づいた統一されたUI
  • レスポンシブ対応: モバイルからデスクトップまで、あらゆる画面サイズに対応
  • カスタマイズ性: 企業のブランドに合わせたテーマのカスタマイズが容易
  • アクセシビリティ: 標準でアクセシビリティに配慮された実装

最新のMaterial-UI(v7)では、パフォーマンスが向上し、最新のReact機能との互換性が強化されています。実践的なプロジェクトでは、公式ドキュメントやサンプルコードを参考にしながら、デザインシステムを構築していくことをお勧めします。

参考リソース

Material-UIを活用して、魅力的で機能的なWebアプリケーションの開発に取り組んでください。

カテゴリー: WEB