はじめに
業務で「日ごとの工数を一覧表示する機能」を作っていたとき、1つのテーブルセルの中に、複数の工程を色分けして横並びで表示するという要件が出ました。
完成イメージはこんな感じです:
- 月によって工程の数が 1個〜5個 と変わる
- でも常に セルの横幅いっぱい に色で埋めたい
- 工程ごとに色が違う
一見シンプルに見えるこの要件、意外と苦戦しました。
最初に困ったこと:「何て検索すればいいかわからない」
正直、これが一番つらかったです。
試した検索キーワード(❌ ハズレ)
| 検索キーワード | 結果 |
|---|---|
| 「ガントチャート CSS」 | Chart.js などのライブラリ記事ばかり |
| 「セル内 カラーバー CSS」 | プログレスバー(1色)の情報 |
| 「複数色 横並び CSS」 | グラデーションの話 |
| 「テーブル セル 色分け」 | セル背景色を1色で塗る話 |
「1つのセルの中に、可変個数の色付きブロックを横並びにする」 という概念に、名前がついていないんですよね。
たどり着いたキーワード(✅ ヒット)
- 「flex-grow 均等幅」
- 「Flexbox 子要素 可変個数」
ここでやっと flex-grow: 1 という解決策にたどり着きました。
ちなみに、近い概念としては「Stacked Bar(積み上げバー)」や「Segmented Bar」と呼ばれるものがありますが、ライブラリを使わず DOM 要素だけで実現する情報は少なかったです。
解決策:HTML + CSS
HTML
<td class="month-cell">
<div class="bar-container">
<div class="bar phase-a"></div> <!-- 工程A -->
<div class="bar phase-b"></div> <!-- 工程B -->
<div class="bar phase-c"></div> <!-- 工程C -->
</div>
</td>
CSS
/* セルの内側余白をゼロにする */
.month-cell {
padding: 0;
}
/* Flexbox コンテナ */
.bar-container {
display: flex; /* 子要素を横並びにする */
gap: 0; /* 子要素間の隙間なし */
height: 30px;
width: 100%;
}
/* 各工程バー */
.bar {
flex-grow: 1; /* 存在する工程で均等に幅を分割 */
height: 100%;
}
/* 工程ごとの色 */
.bar.phase-a { background-color: #ffe44e; } /* 黄色 */
.bar.phase-b { background-color: #b0ec40; } /* 黄緑 */
.bar.phase-c { background-color: rgba(104, 218, 155, 0.85); } /* ミント */
.bar.phase-d { background-color: rgba(134, 224, 247, 0.89); } /* 水色 */
.bar.phase-e { background-color: rgba(94, 140, 201, 0.90); } /* 青 */
たったこれだけで、工程数に応じて自動的にセル全幅を埋めてくれます。
なぜこれで動くのか
display: flex; — 横に並べる
display: flex を親要素に指定すると、子要素が自動的に横並びになります。
flex-grow: 1; — 均等に幅を分ける
flex-grow: 1 は 「余ったスペースを均等に分け合え」 という指示です。
固定幅(width: 33% など)を使わなくていいのがポイントです。
工程が何個あっても自動で均等割りしてくれます。
隙間をなくす3つの CSS 設定
きれいに色が並ぶためには、余白を徹底的にゼロにする必要があります。
| CSS 設定 | 対象 | 何の余白か |
|---|---|---|
padding: 0 |
セル(<td>) |
セルの枠と中身の間の余白 |
gap: 0 |
コンテナ(.bar-container) |
色バー同士の間の余白 |
width: 100% |
コンテナ | セル幅いっぱいに広がる |
⚠️ 注意:padding: 0 は必ず明示的に書こう
「padding を設定しなければ余白はつかないでしょ?」と思うかもしれませんが、ブラウザにはデフォルトスタイルがあります。
だから、明示的に padding: 0 を書いてリセットする必要があります。
.month-cell {
padding: 0 !important; /* 確実にリセット */
}
JavaScript で動的に生成する
実際の画面では、API から取得したデータに基づいてバーを動的に生成します。
受験スケジュールを例にすると、こんな JSON が返ってくるイメージです:
[
{ "month": "2026-04", "phase": "基礎", "hours": 80 },
{ "month": "2026-05", "phase": "基礎", "hours": 40 },
{ "month": "2026-05", "phase": "応用", "hours": 60 },
{ "month": "2026-06", "phase": "応用", "hours": 50 },
{ "month": "2026-06", "phase": "演習", "hours": 30 }
]
これを描画する JavaScript:
// フェーズ名 → CSSクラス名の変換
function getPhaseClass(phaseName) {
const map = {
'基礎': 'basic',
'応用': 'applied',
'演習': 'exercise',
'模試': 'mock',
'本番': 'exam'
};
return map[phaseName] || '';
}
// 1つの月セルのHTMLを生成
function renderMonthCell(effortList) {
if (!effortList || effortList.length === 0) {
return '<td class="month-cell"></td>'; // 空セル
}
let html = '<td class="month-cell"><div class="bar-container">';
effortList.forEach(effort => {
const phaseClass = getPhaseClass(effort.phase);
html += `<div class="bar ${phaseClass}" style="flex-grow: 1"></div>`;
});
html += '</div></td>';
return html;
}
ポイント:
effortListの要素数がいくつでも、flex-grow: 1のおかげで自動的に均等幅になります。ループで<div>を追加するだけで OK。
まとめ
使った CSS はたった3つ
display: flex; /* 横に並べる */
flex-grow: 1; /* 均等幅にする */
padding: 0; /* 余白をなくす */
苦戦した原因
- 検索キーワードがわからなかった — 「1セルに可変個数の色バー」に名前がない
- 固定幅(%指定)で実装しようとした — 科目数が変わるたびに計算が必要で破綻
- デフォルトの余白に気づかなかった — ブラウザやフレームワークが勝手に padding を付けていた
ライブラリなしで、CSS 20行以下で実現できます。 同じ要件で困っている方の参考になれば幸いです。


