🐥

Material UI を使った TODO リストの UI 拡張チュートリアル

に公開

1. 概要

前回の記事で予告したとおり、今回は Material UI を使ってアプリの見た目と操作性を向上させていきます

適用前 適用後
適用前の画面 適用後の画面

Material UI は、Google が提唱する Material Design ガイドラインに基づいて設計されたコンポーネントライブラリです
多彩な、デザイン済みコンポーネントが提供されており、それらを組み合わせるだけで統一感のある美しいインターフェースを簡単に作成できます

目標

  • Material UI の導入と基本的な使い方を学ぶ
  • TODO リストの UI を Material UI で拡張する
  • Material UI の公式ドキュメントからコンポーネントの探し方を学ぶ

2. 前提条件

このチュートリアルを始める前に、以下の環境を準備してください

必要に応じて、Reactの基本チュートリアル(前回の記事)はこちらで環境構築の詳細を確認してください

このチュートリアルは、React の基本(コンポーネント、状態管理、ルーティング)を習得した方を対象としています

必要な前提条件

  • リポジトリ管理: GitHub 上のサンプルプロジェクト react-todo-tutorial を使用します
    • 作業開始ブランチ: lesson-materialui
    • 各チュートリアルごとに新しいブランチを作成して管理します
  • 動作環境:
    • Visual Studio Code
    • Devcontainer もしくはローカル環境(Node.js 20 以上)

手順: Material UI のインストール

  1. リポジトリのクローン
    ターミナルで以下のコマンドを実行して、リポジトリをクローンしてください

    git clone https://github.com/weworku/react-todo-tutorial.git
    cd react-todo-tutorial
    git checkout lesson-materialui
    
  2. Devcontainer を使用する場合

    • Visual Studio Code でプロジェクトフォルダを開きます
    • 左下の「><」アイコンから Reopen in Container を選択して開発コンテナを起動します
    • 開発コンテナが起動したら、ターミナルで以下のコマンドを実行して開発サーバーを開始します
    npm install
    npm run dev -- --host 0.0.0.0
    

    サーバーが起動すると、通常は http://localhost:5173 にアクセスできるようになります
    ブラウザでこのURLを開き、アプリケーションが正しく表示されることを確認してください

備考

  • 環境構築に不安がある場合は、前回の記事で Devcontainer を使用した詳細なセットアップ手順を確認してください

3. 環境構築

Material UI のインストール

プロジェクトのルートディレクトリで以下のコマンドを実行し、Material UI をインストールします

npm install @mui/material @emotion/react @emotion/styled
  • @mui/material: Material UI のコアコンポーネント
  • @emotion/react / @emotion/styled: スタイリングツール

詳しい手順については、公式のインストールガイドをご参照ください:
Material UI - Installation


4. Material UI を使った UI の改善

このセクションでは、main.tsxApp.tsxTodoForm.tsxTodoItem.tsxTodoDetails.tsxHome.tsx の UI を Material UI のコンポーネントで改善し、アプリ全体をスタイル適用済みのコンポーネントで整えていきます

4.1 main.tsx: アプリ全体へのテーマ適用

Material UI では、ThemeProvider を使用してアプリ全体にテーマを適用することができます
テーマは、色やフォントスタイルなどのデザイン要素を統一的に管理するための設定です
CssBaseline を利用すると、ブラウザのデフォルトスタイルがリセットされ、Material UI のスタイルを適用することができます

主なコンセプト

  • ThemeProvider:
    • 作成したテーマを全ての Material UI コンポーネントに適用するためのラッパー
    • アプリ全体のデザインを統一する役割を持つ
  • CssBaseline:
    • 各ブラウザが持つデフォルトスタイルをリセットし、Material UI のデザインを正しく反映するためのコンポーネント
    • 基本的なスタイル(ボックスモデルの初期化やフォントのリセットなど)も適用されます

main.tsx の修正

以下のように ThemeProviderCssBaseline を追加して、Material UI のテーマを適用します

import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { ThemeProvider, createTheme, CssBaseline } from '@mui/material' // Add: Material UI のテーマ関連をインポート
import App from './App.tsx'

// Add: カスタムテーマの作成
const theme = createTheme({
  palette: {
    primary: {
      main: '#1976d2', // Add: プライマリカラー(青系)
    },
    secondary: {
      main: '#dc004e', // Add: セカンダリカラー(赤系)
    },
  },
  typography: {
    fontFamily: 'Roboto, Arial, sans-serif', // Add: フォントファミリーの設定
  },
})

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <ThemeProvider theme={theme}> {/* Add: Material UI のテーマを適用 */}
      <CssBaseline /> {/* Add: デフォルトスタイルをリセット */}
      <App />
    </ThemeProvider>
  </StrictMode>
)

解説

  • テーマのカスタマイズ:
    createTheme を使用することで、色やフォントなどを自由にカスタマイズ可能です
    例えば、palette プロパティを使ってプライマリカラーやセカンダリカラーを指定できます
  • ブラウザスタイルのリセット:
    CssBaseline を使うことで、ブラウザごとの余白やデフォルトスタイルの違いを統一し、Material UI の見た目を正しく表示できます
  • ThemeProvider の使い方:
    アプリ全体を ThemeProvider でラップすることで、すべての Material UI コンポーネントが指定したテーマに従います

もしこのタイミングで動作確認する場合は、ブラウザの開発者ツールなどでフォントが変わっていることを確認してみてください

テーマ適用前 テーマ適用後
テーマ適用前のフォントを確認する画像 テーマ適用後のフォントを確認する画像

4.2 App.tsx: レイアウトとルーティングの統合

アプリ全体のレイアウトを改善するために、Material UI の Container を使用して中央寄せレイアウトにし、Typography を使ってタイトル部分をスタイリッシュに装飾します

既存の React Router によるルーティング機能はそのまま活用しつつ、UI のベース構造を Material UI に置き換えます

import { useState } from 'react'
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'
import { Container, Typography } from '@mui/material' // Add: Material UI のレイアウト用コンポーネントをインポート
import Home from './pages/Home'
import TodoDetails from './pages/TodoDetails'

function App() {
  const [todos, setTodos] = useState([
    "開発準備",
    "TODOアイテムのコンポーネントを作成する",
    "AppコンポーネントでTODOリストを表示する"
  ])

  const addTodo = (todo: string) => {
    setTodos([...todos, todo])
  }

  return (
    <Container maxWidth="sm"> {/* Add: アプリ全体を中央寄せレイアウトにする */}
      <Typography variant="h2" gutterBottom>TODO リスト</Typography> {/* Add: タイトル表示をスタイリッシュに */}
      <Router>
        <Routes>
          <Route path="/" element={<Home todos={todos} addTodo={addTodo} />} />
          <Route path="/todo/:id" element={<TodoDetails todos={todos} />} />
        </Routes>
      </Router>
    </Container>
  )
}

export default App

解説

  • TextField
    入力フィールドにラベルやスタイルが自動で付与される Material UI のコンポーネントです
    fullWidth を指定することで、横幅いっぱいに広がり、使いやすくなります

  • Button
    variant="contained" を指定することで、立体的で視認性の高いボタンになります
    色やサイズも簡単にカスタマイズできます

  • Box
    div の代わりに使える軽量なレイアウトコンポーネントで、Material UI の sx プロパティを使って、CSS をシンプルに適用できます

    ここでは display: 'flex'gap: 1 で、フォームの横並びと間隔を簡単に整えています

4.3 TodoForm.tsx: 入力フォームの改善

以前作成した TodoForm.tsx は、プレーンな HTML フォームとボタンを使用しており、少し味気ない UI になっていました

ここでは、Material UI の TextFieldButton を使用して、見た目と操作性の両面を改善します

加えて、Box を使うことでレイアウトも簡潔に整えます

import { useState } from 'react'
import { TextField, Button, Box } from '@mui/material' // Add: Material UI のコンポーネントをインポート

function TodoForm({ addTodo }: { addTodo: (todo: string) => void }) {
  const [inputValue, setInputValue] = useState('') // 入力状態を管理

  const handleSubmit = (e: React.FormEvent) => { // Add: フォーム送信イベントを処理
    e.preventDefault() // Add: ページリロードを防止
    if (inputValue.trim()) {
      addTodo(inputValue)
      setInputValue('') // Add: TODO追加後に入力欄をリセット
    }
  }

  return (
    <Box
      component="form" // Add: form要素としての振る舞いを持たせる
      onSubmit={handleSubmit} // Add: Submit時の処理を紐付け
      sx={{ display: 'flex', gap: 1 }} // Add: 横並びと間隔調整のスタイル
    >
      <TextField
        label="新しいTODO" // Add: フィールド上部のラベル
        variant="outlined" // Add: 枠付きデザインのスタイル
        fullWidth // Add: 横幅いっぱいに広げる
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value)} // 入力更新
      />
      <Button
        type="submit" // Add: フォーム送信ボタンとして動作
        variant="contained" // Add: 塗りつぶしタイプのデザイン
      >
        追加
      </Button>
    </Box>
  )
}

export default TodoForm

4.4 TodoItem.tsx: TODO リストアイテムの改善

これまでの TodoItem.tsx では、TODO を単なるテキストリンクとして表示していましたが、UI の視認性や操作性の観点からは少し味気ない印象です

このセクションでは、Material UI の Card コンポーネントを使って TODO アイテムをカード形式で表示し、ひと目で見やすく、クリックしやすい UI に改善します
さらに、Typography を活用して文字のスタイルも整え、全体のデザインに統一感をもたせていきます

// src/components/TodoItem.tsx
import React from 'react'
import { Link } from 'react-router-dom' // React Router の Link を使用
import { Card, CardContent, Typography } from '@mui/material' // Add: Material UI の表示用コンポーネントをインポート

function TodoItem({ index, content }: { index: number, content: string }) {
  return (
    <Card
      variant="outlined" // Add: 枠線付きのカードデザイン
      sx={{ marginBottom: 1 }} // Add: 下方向の余白を追加
    >
      <CardContent>
        {/* Add: テキストを Material UI の Typography で表示 */}
        <Typography variant="body1">
          <Link to={`/todo/${index}`} style={{ textDecoration: 'none', color: 'inherit' }}>
            {/* Add: クリック可能なリンク(装飾はTypographyに準拠) */}
            {content}
          </Link>
        </Typography>
      </CardContent>
    </Card>
  )
}

export default TodoItem

解説

  • Card / CardContent

    • TODO アイテム全体を Card で囲むことで、枠線や余白が自動で適用され、情報の区切りが明確になります
    • variant="outlined" により、フラットながら視認性の高い枠付きカードになります
    • sx={{ marginBottom: 1 }} によってリスト同士の間隔も簡単に調整できます
  • Typography

    • テキストのサイズや行間を整えて、読みやすいデザインに
    • 今後、フォントスタイルをテーマに合わせて変更したい場合も、Typography を使っておくと柔軟に対応できます
  • Link

    • React Router の Link を使って、各 TODO の詳細ページに遷移できるようにします
    • style={{ textDecoration: 'none', color: 'inherit' }} を指定することで、リンクっぽい青い下線を消しつつ、親要素のスタイル(Typography)を継承して自然な見た目に保ちます

4.5 Home.tsx: ホームページのレイアウト改善

TodoItemがCardコンポーネントを使用するようになったため、Home.tsxのレイアウトも調整する必要があります

従来の<ul>リストから、Material UIのBoxコンポーネントを使用したレイアウトに変更します

// src/pages/Home.tsx
import { Box } from '@mui/material' // add: Material UI の Box コンポーネントをインポート
import TodoItem from '../components/TodoItem'
import TodoForm from '../components/TodoForm'

function Home(props: { todos: string[]; addTodo: (todo: string) => void }) {
  return (
    <Box> {/* edit: <div> を Box に置き換えて Material UI のスタイル適用を可能に */}
      <TodoForm addTodo={props.addTodo} />
      <Box sx={{ mt: 2 }}> {/* add: TODO リスト部分にマージンを追加し、縦方向の余白を確保 */}
        {props.todos.map((todo, index) => (
          <TodoItem key={index} index={index} content={todo} />
        ))}
      </Box>
    </Box>
  )
}

export default Home

解説

  • Box コンポーネント

    • <div>の代わりに使用できる汎用的なコンテナコンポーネントです
    • sxプロパティを使用して、簡単にスタイリングを適用できます
    • mt: 2は「margin-top: 2」を意味し、上部に余白を追加します
      2 は Material UI の spacing ユニット(通常は 8px × 値)
  • リストの変更

    • 従来の<ul><li>タグを使用したHTMLリストから、Material UIのコンポーネントを使用したレイアウトに変更しています
    • これにより、TodoItemのCardコンポーネントがより自然に表示されます

4.6 TodoDetails.tsx: 詳細ページのレイアウト改善

最後に、TODO詳細ページのUIも改善します
従来のHTMLタグを使用した実装から、Material UIのコンポーネントを使用した実装に変更します

// src/pages/TodoDetails.tsx
import { useParams, Link } from 'react-router-dom'
import { Box, Typography, Button, Card, CardContent } from '@mui/material' // add: Material UI の各種 UI コンポーネントをインポート

function TodoDetails(props: { todos: string[] }) {
  const { id } = useParams<{ id: string }>()
  const todo = props.todos[Number(id)]

  if (!todo) {
    return (
      <Box> {/* edit: <div> を Box に変更し、MUI スタイリングを適用可能に */}
        <Typography variant="h6" color="error">TODO not found</Typography> {/* add: Typography を使ってエラーメッセージを赤色でスタイリング */}
        <Button component={Link} to="/" variant="contained" sx={{ mt: 2 }}> {/* add: 戻るリンクを Button に変更し、操作性を向上 */}
          Back to TODO List
        </Button>
      </Box>
    )
  }

  return (
    <Box> {/* edit: <div> を Box に変更し、全体のレイアウトを整える */}
      <Typography variant="h4" gutterBottom>TODO Details</Typography> {/* add: タイトル見出しを Typography に変更してデザインを統一 */}
      <Card variant="outlined" sx={{ mb: 3 }}> {/* add: TODO 詳細を Card で囲み、視覚的に区切りを追加 */}
        <CardContent>
          <Typography variant="body1">{todo}</Typography> {/* add: 本文テキストも Typography に変更し、整った表示に */}
        </CardContent>
      </Card>
      <Button component={Link} to="/" variant="contained"> {/* add: ボタン型のナビゲーションリンクを追加 */}
        Back to TODO List
      </Button>
    </Box>
  )
}

export default TodoDetails

解説

  • エラー表示の改善

    • TODOが見つからない場合のエラーメッセージをTypographyコンポーネントで表示し、color="error"を指定して赤色で警告表示しています
    • 戻るボタンもButtonコンポーネントを使用して視認性を高めています
  • 詳細表示の改善

    • TODOの詳細をCardコンポーネントで囲み、視覚的に区切りを明確にしています
    • Typographyコンポーネントを使用してテキストのスタイルを整えています
  • ナビゲーションの改善

    • 「Back to TODO List」リンクをButtonコンポーネントに変更し、クリック領域を広げて視認性と操作性の両面で向上します
    • component={Link}を指定することで、Material UIのButtonコンポーネントとReact RouterのLinkコンポーネントを組み合わせています

5. 動作確認

  1. 以下のコマンドでアプリを起動します

    npm run dev -- --host 0.0.0.0
    

    ※ Material UI のインストール(ステップ3)が完了している必要があります

  2. 確認ポイント:

  • フォームが Material UI の TextFieldButton に変わっていること
  • 入力すると新しい TODO が追加されること
  • 各 TODO が Material UI の Card で表示されること
  • テーマ(フォントやカラー)が適用されており、ブラウザのデフォルトスタイルと異なること
  • リストが画面中央に表示されており、Container の効果があること
  • TODO をクリックすると URL が /todo/0 のように変わること(ルーティングが正しく機能していること)

必要に応じて、開発者ツールの「要素」タブや「ネットワーク」タブで各コンポーネントの描画や遷移動作を確認すると、より深く理解できます


6. まとめ

このチュートリアルでは、Material UI を使用して以下のことを学びました

  • Material UI の基本的な導入方法
  • TextField, Button, Card などのコンポーネントの利用
  • フォームとリストアイテムのデザイン向上

7. 次のステップ

次回の記事では、Material UI のレイアウトコンポーネントGridBox)を使用して、TODO リストを複数のペインに分割した UI を作成します
さらに、Material UI のドキュメントを活用して目的のコンポーネントを探す方法についても紹介します

この記事で得た知識を活用して、独自の UI カスタマイズにも挑戦してみてほしいです!

Discussion