コンテンツにスキップ

10-1. 要件を読み、UIの土台を作る

  • 書籍管理アプリに必要な画面要素を HTML で整理する方法
  • header / main / section / form / table などの意味のあるタグの使い分け
  • CSS でフォーム・一覧・状態バッジを読みやすく整える方法
  • JavaScript を書く前に、なぜ静的な UI を先に作るのか
  • Elements / Styles パネルで画面崩れを自力で確認する基本

第10章では、1つの書籍管理アプリをフロントエンドからバックエンドまで少しずつ育てていく。
最初のこのセクションでは、まだボタンは動かさず、「どこに何を表示するのか」 がはっきり分かる静的な画面を作る。


このセクションの終わりでは、次のような静的画面ができている状態を目指す。

  • ページの目的が分かるヘッダー
  • 書籍を登録するためのフォーム
  • 登録済み書籍を確認する一覧テーブル
  • 「未読 / 読書中 / 読了」が見分けやすい状態バッジ
  • 次のセクションで JavaScript をつなぎやすい、整理された HTML 構造
+----------------------------------------------------------------+
| 書籍管理アプリ |
| いま読んでいる本と、これから読む本を整理する |
| [登録冊数 3] [読書中 1] [読了 1] |
|----------------------------------------------------------------|
| 書籍を登録する |
| タイトル [________________] 著者 [________________] |
| カテゴリ [select] 価格 [____] |
| 状態 [select] |
| メモ [______________________________________________] |
| [書籍を追加] |
|----------------------------------------------------------------|
| 登録済み書籍 |
| JavaScript入門 | 山田太郎 | フロントエンド | 2800円 | 未読 | 削除 |
| Git実践入門 | 佐藤花子 | 開発ツール | 2400円 | 読了 | 削除 |
+----------------------------------------------------------------+

JavaScript を先に書きたくなるかもしれないが、初心者ほど 画面の骨組みを先に固定する ほうが安全である。

理由は 3 つある。

  1. どのデータを扱う画面なのかが明確になる
  2. JavaScript から取得したい要素に名前や役割を付けやすくなる
  3. 見た目の崩れとロジックのバグを分けて考えられる
HTML = 何の部品があるか
CSS = どう見せるか
JS = いつ、どう動くか

この順で考えると、

  • 画面がないのか
  • 画面はあるが崩れているのか
  • 画面は正しいが JavaScript が動かないのか

を切り分けやすくなる。

今回の書籍管理アプリでは、まず次の 3 領域を作る。

領域役割このあと何に使うか
ヘッダー何のアプリかを伝える件数表示をあとで JS から更新する
登録フォーム新しい本の入力欄submit イベントをあとで受け取る
一覧テーブル登録済みの本を表示するDOM 操作で行を追加・削除する

10-0 で book-app/book-frontend/ フォルダは作成済みである。 このセクションでは book-frontend/ の中に 2 ファイルを追加していく。

book-app/
├── book-frontend/ ← このフォルダを VS Code で開く
│ ├── index.html ← 画面の部品を書く
│ └── style.css ← 見た目のルールを書く
└── bookmanager/

VS Code で book-app/book-frontend/ フォルダを開いてから作業を始める(File > Open Folder...)。

「HTML に見た目も全部書く」こともできるが、役割を分けたほうが後から修正しやすい。


まずはページ全体の骨組みを作る。

index.html
<!doctype html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>書籍管理アプリ</title>
<link rel="stylesheet" href="./style.css" />
</head>
<body>
<main class="app-shell">
<header class="page-header">
<p class="page-kicker">Chapter 10 Book App</p>
<h1>書籍管理アプリ</h1>
<p class="page-description">
いま読んでいる本、次に読みたい本、読み終えた本を整理するための練習用アプリです。
</p>
</header>
<section class="summary-grid" aria-label="現在の集計">
<article class="summary-card">
<p class="summary-label">登録冊数</p>
<p class="summary-value">3</p>
</article>
<article class="summary-card">
<p class="summary-label">読書中</p>
<p class="summary-value">1</p>
</article>
<article class="summary-card">
<p class="summary-label">読了</p>
<p class="summary-value">1</p>
</article>
</section>
</main>
</body>
</html>

div だけでも画面は作れる。
しかし、意味のあるタグを使うと、人が見ても、ブラウザが見ても構造が分かりやすい

main = このページの主役の内容
header = タイトルと導入
section = まとまりごとの領域

この「意味」があると、あとで JavaScript で要素を探すときにも頭の中が整理しやすい。


4. HTTP サーバーを起動してブラウザで確認する

Section titled “4. HTTP サーバーを起動してブラウザで確認する”

index.html ができたら、すぐにブラウザで確認できる状態にしておこう。 10-5 以降では fetch() で API を呼び出すため、最初から HTTP サーバー経由で開く習慣にしておくと、各ステップの変更をそのままブラウザで確認し続けられる。

VS Code のターミナルを新しく開き(Ctrl + @)、book-app/book-frontend/ にいることを確認してから次を実行する。

Terminal window
npx --yes serve . --listen 4173

コマンドを実行したままにして、ブラウザで http://localhost:4173/ を開く。

確認が終わったら、そのターミナルで Ctrl + C を押して停止する。

このターミナルは開いたままにしておく 以降のセクションで HTML や CSS を更新したあと、ブラウザで F5(または Ctrl+R)を押すだけで最新の表示を確認できる。 ターミナルを閉じると停止した場合は、次回作業を再開するときに同じコマンドを再実行する。

style.css はまだ存在しないので見た目は整っていないが、次のことは確認できる。

  • 書籍管理アプリ という見出しが表示される
  • 集計カードの「登録冊数・読書中・読了」のラベルが見える
  • フォームと一覧テーブルは次のセクション以降で追加する

次に、入力欄を追加する。 書籍管理アプリでは、少なくとも次の情報を扱えるようにしておくと練習しやすい。

  • タイトル
  • 著者
  • カテゴリ
  • 価格
  • 状態
  • メモ

追加する場所: セクション3で書いた <section class="summary-grid"> の閉じタグ </section> の直後、</main> の手前。

index.html
</section><!-- summary-grid の閉じタグ(すでにある) -->
<section class="panel">
<h2>書籍を登録する</h2>
<form class="book-form">
<div class="form-grid">
<label class="form-field" for="title">
<span>タイトル</span>
<input id="title" name="title" type="text" placeholder="例: JavaScript入門" />
</label>
<label class="form-field" for="author">
<span>著者</span>
<input id="author" name="author" type="text" placeholder="例: 山田太郎" />
</label>
<label class="form-field" for="category">
<span>カテゴリ</span>
<select id="category" name="category">
<option value="">選択してください</option>
<option value="フロントエンド">フロントエンド</option>
<option value="バックエンド">バックエンド</option>
<option value="データベース">データベース</option>
<option value="開発ツール">開発ツール</option>
</select>
</label>
<label class="form-field" for="price">
<span>価格</span>
<input id="price" name="price" type="number" min="0" step="1" placeholder="2800" />
</label>
<label class="form-field" for="status">
<span>状態</span>
<select id="status" name="status">
<option value="未読">未読</option>
<option value="読書中">読書中</option>
<option value="読了">読了</option>
</select>
</label>
</div>
<label class="form-field" for="memo">
<span>メモ</span>
<textarea id="memo" name="memo" rows="4" placeholder="この本で何を学びたいかを書いておく"></textarea>
</label>
<button class="primary-button" type="submit">書籍を追加</button>
</form>
</section>
</main><!-- main の閉じタグ(すでにある) -->

見た目だけなら、

<p>タイトル</p>
<input type="text" />

でも表示はできる。
しかし label for="title"input id="title" を対応させておくと、

  • どの入力欄の説明かが明確になる
  • ラベルをクリックすると入力欄にフォーカスできる
  • JavaScript から #title として取り出しやすい

という利点がある。

つまり label は飾りではなく、入力欄の意味を固定するための部品 である。

index.html を保存してブラウザで F5 を押す。

  • タイトル・著者・カテゴリ・価格・状態・メモの入力欄が表示されている
  • 「書籍を追加」ボタンが見える
  • まだ CSS が当たっていないためスタイルは整っていないが、構造が確認できれば次へ進んでよい

フォームの次は、登録済み書籍を表示する一覧を作る。 最初は静的な行を 2〜3 件入れて、画面の形だけ確認すればよい。

追加する場所: セクション5で追加したフォームの </section> の直後、</main> の手前。

index.html
</section><!-- フォームの閉じタグ(セクション4で追加済み) -->
<section class="panel">
<div class="section-heading">
<h2>登録済み書籍</h2>
<p class="section-note">この段階ではまだサンプル表示です</p>
</div>
<div class="table-wrapper">
<table class="book-table">
<thead>
<tr>
<th>タイトル</th>
<th>著者</th>
<th>カテゴリ</th>
<th>価格</th>
<th>状態</th>
<th>メモ</th>
<th>操作</th>
</tr>
</thead>
<tbody id="book-table-body">
<tr>
<td>JavaScript入門</td>
<td>山田太郎</td>
<td>フロントエンド</td>
<td>2800円</td>
<td><span class="status-badge status-unread">未読</span></td>
<td>DOM 操作の章まで読む</td>
<td><button class="ghost-button" type="button">削除</button></td>
</tr>
<tr>
<td>Git実践入門</td>
<td>佐藤花子</td>
<td>開発ツール</td>
<td>2400円</td>
<td><span class="status-badge status-finished">読了</span></td>
<td>ブランチ運用を復習済み</td>
<td><button class="ghost-button" type="button">削除</button></td>
</tr>
</tbody>
</table>
</div>
</section>
</main><!-- main の閉じタグ(すでにある) -->

なぜ最初から tbody を分けるのか

Section titled “なぜ最初から tbody を分けるのか”

次のセクションでは JavaScript で行を追加・削除する。
そのとき、操作対象を tbody にまとめておくと、

thead = 見出し(固定)
tbody = データ部分(あとで動的に書き換える)

という役割分担ができる。
つまり tbody を作っておくことは、あとで DOM を更新しやすくする準備 である。

index.html を保存してブラウザで F5 を押す。

  • テーブルの見出し行(タイトル・著者・カテゴリ・価格・状態・メモ・操作)が表示されている
  • サンプル 2 行(JavaScript入門・Git実践入門)が表示されている
  • 「削除」ボタンが各行に見える
  • CSS は次のセクションで追加するためまだ見た目は整っていないが、行が並んでいれば次へ進んでよい

HTML だけでも情報は並ぶが、そのままだとフォームも表も読みづらい。
ここでは、余白・色・並び方を整えて「読み間違いにくい UI」を作る。

style.css
:root {
--bg: #f5f7fb;
--panel: #ffffff;
--line: #d7dce5;
--text: #1f2937;
--subtle: #667085;
--primary: #2563eb;
--primary-soft: #dbeafe;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: linear-gradient(180deg, #eef4ff 0%, var(--bg) 240px);
color: var(--text);
}
.app-shell {
width: min(1100px, calc(100% - 32px));
margin: 0 auto;
padding: 48px 0 80px;
}
.page-header {
margin-bottom: 24px;
}
.page-kicker {
margin: 0 0 8px;
color: var(--primary);
font-size: 0.85rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.page-header h1 {
margin: 0;
font-size: clamp(2rem, 3vw, 2.8rem);
}
.page-description {
margin: 12px 0 0;
color: var(--subtle);
line-height: 1.8;
}

style.css を保存してブラウザで F5 を押す。

  • ページの背景がうっすら青みがかった色に変わっている
  • コンテンツが画面中央に収まり、左右に余白がついている

追加する場所: style.css の末尾に続けて追記する。

style.css
.summary-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 16px;
margin-bottom: 24px;
}
.summary-card,
.panel {
background: var(--panel);
border: 1px solid var(--line);
border-radius: 16px;
box-shadow: 0 12px 30px rgba(15, 23, 42, 0.06);
}
.summary-card {
padding: 20px;
}
.summary-label {
margin: 0;
color: var(--subtle);
font-size: 0.9rem;
}
.summary-value {
margin: 8px 0 0;
font-size: 2rem;
font-weight: 700;
}
.panel {
padding: 24px;
margin-bottom: 20px;
}
.section-heading {
display: flex;
justify-content: space-between;
align-items: baseline;
gap: 12px;
margin-bottom: 16px;
}
.section-note {
margin: 0;
color: var(--subtle);
font-size: 0.9rem;
}

style.css を保存してブラウザで F5 を押す。

  • 集計カード 3 枚が横並びになっている
  • フォームと一覧が白い枠付きのパネルで囲まれている

追加する場所: style.css の末尾に続けて追記する。

style.css
.form-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px;
}
.form-field {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 16px;
}
.form-field span {
font-weight: 600;
}
.form-field input,
.form-field select,
.form-field textarea {
width: 100%;
padding: 12px 14px;
border: 1px solid var(--line);
border-radius: 12px;
font: inherit;
background: #fff;
}
.primary-button,
.ghost-button {
border-radius: 999px;
padding: 10px 18px;
font: inherit;
cursor: pointer;
}
.primary-button {
border: none;
background: var(--primary);
color: #fff;
}
.ghost-button {
border: 1px solid var(--line);
background: #fff;
color: var(--text);
}
.table-wrapper {
overflow-x: auto;
}
.book-table {
width: 100%;
border-collapse: collapse;
}
.book-table th,
.book-table td {
padding: 14px 12px;
border-bottom: 1px solid var(--line);
text-align: left;
vertical-align: top;
}
.book-table th {
color: var(--subtle);
font-size: 0.9rem;
}
.status-badge {
display: inline-flex;
align-items: center;
padding: 4px 10px;
border-radius: 999px;
font-size: 0.82rem;
font-weight: 700;
}
.status-unread {
background: #e5e7eb;
color: #374151;
}
.status-reading {
background: #fef3c7;
color: #92400e;
}
.status-finished {
background: #dcfce7;
color: #166534;
}
@media (max-width: 720px) {
.summary-grid,
.form-grid {
grid-template-columns: 1fr;
}
}

CSS で大事なのは「映えること」より「読み間違えないこと」

Section titled “CSS で大事なのは「映えること」より「読み間違えないこと」”

初心者は色や装飾に意識が向きやすいが、実務でより重要なのは次の点である。

  • 入力欄がどこか一目で分かる
  • ボタンが押せそうに見える
  • 状態の違いが色と文字の両方で分かる
  • スマホ幅でも崩れにくい

つまり CSS は飾りではなく、利用者が迷わないための設計 でもある。

style.css を保存してブラウザで F5 を押す。

  • フォームの入力欄が 2 列で並んでいる
  • 「書籍を追加」ボタンが青い丸ボタンになっている
  • テーブルの行に罫線が入っている
  • 「未読」バッジがグレー、「読了」バッジが緑で表示されている

8. ここまでを 1 画面として見る

Section titled “8. ここまでを 1 画面として見る”

この時点ではまだデータは固定だが、すでに次の準備ができている。

[フォーム]
title / author / category / price / status / memo
まだ送信はしない
[一覧テーブル]
あとで JavaScript が tbody に行を追加する
[集計カード]
あとで JavaScript が数字を書き換える

画面の置き場所が決まっていると、次のセクションでは

  • フォームの値を読む
  • 一覧に新しい行を出す
  • 件数を更新する

という処理に集中できる。


失敗起きること直し方
label forinput id が一致していないラベルをクリックしても入力欄へ移動しないfor="title"id="title" のように同じ名前へそろえる
display: grid.form-grid ではなく各 label に書いている期待した列配置にならない「複数の項目を並べたい親要素」に grid を指定する
tbody を作らず、行を直接 table の下に書いているあとで JavaScript でどこを書き換えるか分かりにくいtheadtbody を分ける
状態バッジのクラス名を打ち間違えている色が変わらず、見分けにくいstatus-unread / status-reading / status-finished を確認する
ボタンに type="button" を付けていない後でフォームの中に置いたとき、意図せず submit されることがある送信用は submit、その他は button と明示する

HTML/CSS の不具合は、勘で直すより DevTools で事実を見る ほうが早い。

確認したい点:

  • main.app-shell があるか
  • form.book-form の中に #title#author があるか
  • table > thead + tbody の形になっているか
  • 状態バッジに期待したクラスが付いているか
Elements
└─ main.app-shell
├─ header.page-header
├─ section.summary-grid
├─ section.panel (form)
└─ section.panel (table)

2. Styles / Computed で CSS の効き方を見る

Section titled “2. Styles / Computed で CSS の効き方を見る”
  • 期待した CSS が打ち消されていないか
  • display: gridpadding が本当に適用されているか
  • width: 100% が効いているか

特に「見た目が崩れた」ときは、要素にクラスが付いていないのか、クラスはあるが CSS が当たっていないのか を分けて考える。

Chrome のデバイスツールバーで幅を狭め、

  • 集計カードが縦に並ぶか
  • フォームが 1 列になるか
  • テーブルが横スクロールできるか

を確認すると、あとから大きく直しにくい崩れを早めに見つけられる。


項目ポイント
HTML の骨組みmain / header / section で役割を分ける
フォームlabelid を対応させて入力欄の意味を固定する
一覧表示theadtbody を分けると次の DOM 操作が楽になる
CSS の役割余白・配置・状態の違いを整理して読みやすくする
状態バッジ色だけでなく文字でも状態を伝える
デバッグElements / Styles で「構造」と「適用中の CSS」を切り分ける

演習問題 で、静的な UI を自分の手で組み立てられるか確認しよう。

できたら 10-2. フロントエンドで一覧表示とフォーム入力を作る へ進み、同じ画面に動きを付けていこう。