Flexboxで要素を横並びにしたとき、一部のカードだけ高さが違うという経験はありませんか?
特にカード型レイアウトや料金プランの比較UIなど、実務では「高さを揃えたい」場面が頻繁に出てきます。
この記事では、高さが揃わない原因の特定方法から、基本の解決策、実務で使える応用テクニックまで体系的に解説します。
この記事で分かること
- align-items: stretch のデフォルト動作と高さ揃えの仕組み
- 高さが揃わない代表的な7つの原因と解決策
- カード型レイアウトでの高さ揃えテクニック
- flex-grow でコンテンツ領域を伸ばす方法
- ネストしたFlexboxでの高さ揃え
- CSS Grid での代替アプローチ
- フッターをカード下部に固定する実装
- 実務パターン(商品カード・記事カード・料金プラン)
なぜ高さが揃わないのか
まず、Flexboxの高さに関するデフォルト動作を正確に理解しましょう。
align-items: stretch がデフォルト
Flexコンテナのalign-itemsプロパティは、デフォルトでstretchが適用されています。つまり、何も指定しなくても子要素の高さは自動的に揃うのが本来の動作です。
CSS – デフォルトの動作
/* Flexコンテナのデフォルト値 */
.container {
display: flex;
align-items: stretch; /* デフォルト値 - 明示不要 */
}
この状態では、最も高さのある子要素に合わせて他の子要素も伸びます。
ポイント:「高さが揃わない」のは、何かがstretch動作を打ち消しているからです。原因を特定することが解決の第一歩になります。
高さが揃わなくなる主な原因
| 原因 |
説明 |
align-itemsの変更 |
center / flex-start 等に変更している |
align-selfの指定 |
子要素個別にstretch以外を指定 |
heightの明示指定 |
子要素にheightやmax-heightを設定 |
| 内側のコンテンツ量 |
テキスト量や画像サイズの違い |
flex-wrap: wrap |
折り返し時に行ごとの高さが異なる |
min-heightの指定 |
最小高さが固定されている |
overflowの影響 |
overflowがhiddenやscrollで高さが制限される |
align-items: stretch で高さを揃える(基本解決)
最もシンプルな解決方法は、align-items: stretchがデフォルトのまま残っていることを確認し、それを阻害する要因を取り除くことです。
基本パターン
HTML
<div class="container">
<div class="item">短いテキスト</div>
<div class="item">長いテキストが入る要素。<br>複数行になります。<br>3行目もあります。</div>
<div class="item">中くらいのテキスト</div>
</div>
CSS – 高さが揃う基本パターン
.container {
display: flex;
gap: 16px;
/* align-items: stretch はデフォルトなので省略可 */
}
.item {
flex: 1;
padding: 16px;
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 8px;
/* height を指定しない! */
}
この状態では、最も高さのある2番目の要素に合わせて、1番目・3番目の要素も同じ高さになります。
align-items を変更してしまっている場合
もしalign-items: centerやalign-items: flex-startを指定していると、子要素はコンテンツの高さに縮まります。
CSS – 高さが揃わなくなる例
/* NG:高さが揃わない */
.container {
display: flex;
align-items: center; /* stretchではなくなった */
}
/* OK:高さが揃う */
.container {
display: flex;
align-items: stretch; /* 明示的に戻す */
}
注意:CSSフレームワーク(Bootstrap, Tailwind等)がデフォルトでalign-itemsを変更している場合があります。DevToolsで実際に適用されている値を確認してください。
子要素の height / min-height が原因のケース
align-items: stretchが効くのは、子要素にheight(高さ)が明示的に指定されていないときだけです。
height指定が stretch を無効にする
CSS – heightがstretchを打ち消す
/* NG:heightが指定されているとstretchが効かない */
.item {
height: 100px; /* この指定がstretchを無効にする */
}
/* NG:max-heightも高さを制限する */
.item {
max-height: 200px; /* 200pxより伸びなくなる */
}
/* OK:heightを削除すればstretchが効く */
.item {
/* heightは指定しない */
padding: 16px;
}
min-height は stretch と共存できる
min-heightは「最低限の高さ」を保証するだけなので、stretchと組み合わせて使えます。
CSS – min-heightとstretchの共存
.item {
min-height: 80px; /* 最低80px、それ以上は伸びる */
/* height は指定しない */
}
| プロパティ |
stretchとの関係 |
結果 |
height |
stretchを無効にする |
高さが揃わない |
max-height |
上限で制限する |
上限を超えると揃わない |
min-height |
共存できる |
最低高さ以上で揃う |
| 指定なし |
stretchが完全に効く |
高さが揃う |
カード型レイアウトでの高さ揃え
実務で最も多いのが、カード型レイアウトで高さを揃えたいというケースです。
基本のカードレイアウト
HTML – カードレイアウト
<div class="card-container">
<div class="card">
<img src="image1.jpg" alt="">
<h3>タイトル</h3>
<p>短い説明文</p>
</div>
<div class="card">
<img src="image2.jpg" alt="">
<h3>長いタイトルが入る場合</h3>
<p>長い説明文が入ります。複数行にわたるテキストで高さが変わります。</p>
</div>
<div class="card">
<img src="image3.jpg" alt="">
<h3>タイトル</h3>
<p>中くらいの説明文</p>
</div>
</div>
CSS – カードの高さを揃える
.card-container {
display: flex;
gap: 20px;
/* align-items: stretch がデフォルトで適用される */
}
.card {
flex: 1;
background: #fff;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
overflow: hidden;
/* heightは指定しない */
}
.card img {
width: 100%;
height: 180px;
object-fit: cover;
}
.card h3,
.card p {
padding: 0 16px;
}
flex: 1で幅を均等にし、heightを指定しないことで、stretchにより全カードが同じ高さになります。
flex-grow でコンテンツ領域を伸ばす
カードの高さが揃っても、カード内のテキスト領域が均等に伸びないケースがあります。この場合、カード自体もFlexboxにして、伸ばしたい領域にflex-grow: 1を指定します。
CSS – flex-growでコンテンツ領域を伸ばす
.card {
flex: 1;
display: flex;
flex-direction: column; /* カード内を縦並びに */
}
.card-body {
flex-grow: 1; /* この領域が残りスペースを埋める */
padding: 16px;
}
.card-footer {
padding: 12px 16px;
border-top: 1px solid #e2e8f0;
}
flex-grow の仕組み
flex-grow: 0(デフォルト): コンテンツの高さのまま
flex-grow: 1: 親要素の残りスペースをすべて占める
- 複数の子要素に
flex-growを指定すると、比率に応じて分配される
ネストしたFlexboxでの高さ揃え
Flexboxをネスト(入れ子)にすると、外側の高さ揃えが内側まで伝播しないことがあります。
問題:外側は揃うが内側のカードが揃わない
HTML – ネストしたFlexbox
<div class="outer">
<div class="column">
<div class="inner-card">カードA</div>
<div class="inner-card">カードB</div>
</div>
<div class="column">
<div class="inner-card">カードC(長いコンテンツ)</div>
<div class="inner-card">カードD</div>
</div>
</div>
解決:各階層でFlexboxを設定する
CSS – ネストしたFlexboxの高さ揃え
/* 外側:カラムの高さを揃える */
.outer {
display: flex;
gap: 20px;
/* align-items: stretch でカラムの高さが揃う */
}
/* 中間:カラム自体もFlexboxにする */
.column {
flex: 1;
display: flex;
flex-direction: column;
gap: 16px;
}
/* 内側:カードが均等に伸びる */
.inner-card {
flex: 1; /* 残りスペースを均等に分配 */
padding: 16px;
background: #fff;
border-radius: 8px;
}
ポイント:ネストしたFlexboxで高さを揃えるには、「外側→中間→内側」のすべての階層でFlexboxの設定が必要です。中間の.columnをdisplay: flexにし忘れると、内側のカードの高さは揃いません。
CSS Grid での代替アプローチ
FlexboxではなくCSS Gridを使うと、より簡潔に高さを揃えられるケースがあります。
grid-auto-rows で行の高さを統一
CSS – CSS Gridで高さを揃える
.grid-container {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
/* グリッドは同じ行のアイテムを自動的に同じ高さにする */
}
CSS Gridでは、同じ行に配置されたアイテムは自動的に同じ高さになります。Flexboxのようにalign-itemsを気にする必要がありません。
grid-auto-rows: 1fr で全行統一
複数行にまたがる場合、各行で高さが変わることがあります。grid-auto-rows: 1frを使うと、すべての行を同じ高さにできます。
CSS – 全行の高さを統一
.grid-container {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: 1fr; /* すべての行を同じ高さに */
gap: 20px;
}
Flexbox vs CSS Grid の使い分け
| 比較項目 |
Flexbox |
CSS Grid |
| 高さ揃えの仕組み |
align-items: stretch |
自動(同じ行は同じ高さ) |
| 複数行での高さ揃え |
行ごとに高さが異なる |
grid-auto-rows: 1frで統一可 |
| 列数の固定 |
flex-basisやwidthで調整 |
grid-template-columnsで簡単 |
| 向いている場面 |
1行の横並び、可変数 |
複数行のグリッドレイアウト |
| ブラウザ対応 |
IE11一部対応 |
IE11非対応 |
Flexbox + overflow での対処
カード内のコンテンツが多すぎて溢れる場合、overflowプロパティで制御しながら高さを揃える方法があります。
テキストが溢れる場合の処理
CSS – overflow制御付きの高さ揃え
.card-container {
display: flex;
gap: 20px;
}
.card {
flex: 1;
display: flex;
flex-direction: column;
}
.card-text {
flex-grow: 1;
overflow-y: auto; /* テキストが多い場合はスクロール */
padding: 16px;
}
注意:overflow: hiddenをカード自体に付けると、box-shadowが切れたり、ホバーエフェクトが欠けたりすることがあります。overflowは溢れる可能性のある内部要素にだけ指定しましょう。
テキストの行数を制限する(-webkit-line-clamp)
CSS – 行数制限で高さを統一
.card-description {
display: -webkit-box;
-webkit-line-clamp: 3; /* 最大3行 */
-webkit-box-orient: vertical;
overflow: hidden;
line-height: 1.6;
}
行数を制限することで、コンテンツ量に関係なくテキスト領域の高さを揃えることも可能です。ただし、テキストが途中で切れるため「続きを読む」リンクと組み合わせるのが一般的です。
フッターを下部に固定する(カード内レイアウト)
高さが揃ったカードレイアウトで、もうひとつよくある要望が「ボタンやリンクをカードの一番下に固定したい」というケースです。
margin-top: auto を使う方法
HTML – フッター固定のカード
<div class="card-container">
<div class="card">
<h3>プランA</h3>
<p>短い説明</p>
<a href="#" class="card-btn">詳細を見る</a>
</div>
<div class="card">
<h3>プランB</h3>
<p>長い説明文が入ります。複数行になって高さが変わります。</p>
<a href="#" class="card-btn">詳細を見る</a>
</div>
</div>
CSS – margin-top: auto でフッター固定
.card-container {
display: flex;
gap: 20px;
}
.card {
flex: 1;
display: flex;
flex-direction: column;
padding: 24px;
background: #fff;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.card-btn {
margin-top: auto; /* 残りスペースを上マージンに */
display: inline-block;
padding: 10px 20px;
background: #0284c7;
color: #fff;
border-radius: 8px;
text-align: center;
text-decoration: none;
}
margin-top: auto が効く仕組み
- Flexboxでは、
autoマージンは残りの空きスペースをすべて吸収します
margin-top: autoは「上方向のスペースを最大限取る」= 要素を一番下に押し下げます
- これは
flex-growを使う方法よりもHTML構造を変えずに実装できるメリットがあります
flex-grow との違い
| 方法 |
仕組み |
使い分け |
margin-top: auto |
スペースをマージンに変換 |
ボタンを下に押し下げるだけ |
flex-grow: 1 |
要素自体が伸びる |
テキスト領域を広げたいとき |
レスポンシブ対応での注意点
レスポンシブデザインでは、画面幅に応じてFlexboxの方向やラップを変更しますが、そのときに高さ揃えが崩れることがあります。
flex-wrap: wrap の高さ問題
flex-wrap: wrapを使うと、折り返された行ごとに高さが計算されるため、行をまたいだ高さ揃えはできません。
CSS – flex-wrapでの高さ問題
/* 行をまたいだ高さ揃えは不可能 */
.container {
display: flex;
flex-wrap: wrap;
gap: 20px;
}
.item {
flex: 0 0 calc(33.333% - 14px);
/* 同じ行の要素は揃うが、行をまたぐと揃わない */
}
解決策:CSS Gridに切り替える
行をまたいだ高さ揃えが必要な場合は、CSS Gridのgrid-auto-rows: 1frを使いましょう。
CSS – レスポンシブ対応のGridレイアウト
.card-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: 1fr;
gap: 20px;
}
/* タブレット */
@media (max-width: 768px) {
.card-grid {
grid-template-columns: repeat(2, 1fr);
}
}
/* スマホ */
@media (max-width: 480px) {
.card-grid {
grid-template-columns: 1fr; /* 1列表示 */
grid-auto-rows: auto; /* 1列なら揃え不要 */
}
}
縦並びに切り替える場合の注意
CSS – レスポンシブでの方向切り替え
.container {
display: flex;
gap: 20px;
}
@media (max-width: 768px) {
.container {
flex-direction: column;
/* 縦並びでは align-items: stretch は「幅」を揃える */
/* 高さはコンテンツに依存する(これが正しい動作) */
}
}
ポイント:flex-direction: columnに切り替わると、主軸が縦方向になります。このときalign-items: stretchは幅方向に作用するため、高さ揃えとは無関係になります。縦並び時は各要素がコンテンツに応じた高さになるのが自然な動作です。
よくある失敗パターンと解決策
実務でハマりやすいパターンをまとめます。
失敗①:画像の高さがバラバラ
CSS – 画像の高さを揃える
/* NG:アスペクト比が異なる画像でカードの高さがバラバラ */
.card img {
width: 100%;
/* height未指定 → 画像本来のアスペクト比で表示 */
}
/* OK:高さを固定してobject-fitでトリミング */
.card img {
width: 100%;
height: 200px;
object-fit: cover; /* 比率を保ちつつ領域を埋める */
}
/* OK:aspect-ratioで比率を統一(モダンブラウザ) */
.card img {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover;
}
失敗②:align-self を個別に指定している
CSS – align-selfの落とし穴
/* NG:特定の要素だけ高さが揃わない */
.item:nth-child(2) {
align-self: flex-start; /* この要素だけstretchが解除される */
}
/* OK:align-selfを削除またはstretchに */
.item:nth-child(2) {
align-self: stretch; /* 明示的にstretchを指定 */
}
失敗③:子要素に100%以外のheightを指定
CSS – heightの指定ミス
/* NG:heightがstretchを打ち消す */
.card {
height: fit-content; /* コンテンツの高さに固定 → 揃わない */
}
/* NG:パーセント指定も注意 */
.card {
height: 50%; /* 親の50%に固定 → 揃わない */
}
/* OK:heightを指定しない */
.card {
/* height は削除する */
}
失敗④:CSSフレームワークとの競合
CSS – フレームワーク競合の解決
/* Bootstrapの .row は align-items が変更される場合がある */
/* DevToolsで確認し、必要に応じてオーバーライド */
.row.equal-height {
align-items: stretch;
}
/* Tailwind CSSの場合 */
/* items-stretch クラスを追加する */
/* <div class="flex items-stretch gap-4"> */
実務パターン集
最後に、実務でそのまま使えるパターンを3つ紹介します。
パターン①:商品カード
CSS – 商品カード(高さ揃え + フッター固定)
.product-list {
display: flex;
flex-wrap: wrap;
gap: 24px;
}
.product-card {
flex: 0 0 calc(33.333% - 16px);
display: flex;
flex-direction: column;
background: #fff;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
transition: box-shadow 0.2s;
}
.product-card:hover {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}
.product-card img {
width: 100%;
aspect-ratio: 4 / 3;
object-fit: cover;
}
.product-info {
flex-grow: 1; /* テキスト領域が伸びる */
padding: 16px;
}
.product-price {
padding: 12px 16px;
border-top: 1px solid #e2e8f0;
font-weight: 700;
color: #dc2626;
}
パターン②:記事カード(ブログ)
CSS – 記事カード(高さ揃え + 行数制限)
.article-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 24px;
}
.article-card {
display: flex;
flex-direction: column;
background: #fff;
border-radius: 12px;
overflow: hidden;
}
.article-card img {
width: 100%;
height: 180px;
object-fit: cover;
}
.article-body {
flex-grow: 1;
padding: 16px;
}
.article-excerpt {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
color: #64748b;
line-height: 1.6;
}
.article-meta {
margin-top: auto;
padding: 12px 16px;
border-top: 1px solid #f1f5f9;
font-size: 13px;
color: #94a3b8;
}
パターン③:料金プラン
CSS – 料金プラン(高さ揃え + 推奨プラン強調)
.pricing-container {
display: flex;
gap: 24px;
align-items: stretch; /* 明示的に指定 */
max-width: 960px;
margin: 0 auto;
}
.pricing-card {
flex: 1;
display: flex;
flex-direction: column;
padding: 32px 24px;
background: #fff;
border: 2px solid #e2e8f0;
border-radius: 16px;
text-align: center;
}
/* 推奨プランの強調 */
.pricing-card.recommended {
border-color: #0284c7;
box-shadow: 0 8px 24px rgba(2, 132, 199, 0.15);
position: relative;
}
.pricing-features {
flex-grow: 1; /* 機能一覧が残りスペースを埋める */
text-align: left;
list-style: none;
padding: 0;
margin: 24px 0;
}
.pricing-features li {
padding: 8px 0;
border-bottom: 1px solid #f1f5f9;
}
.pricing-btn {
margin-top: auto; /* ボタンを一番下に固定 */
padding: 14px;
background: #0284c7;
color: #fff;
border: none;
border-radius: 8px;
font-size: 16px;
cursor: pointer;
}
まとめ
Flexboxで高さが揃わない問題の原因と解決策を整理します。
| 状況 |
解決策 |
プロパティ |
| 基本の高さ揃え |
align-items: stretchを維持 |
デフォルトのまま |
| heightが邪魔をしている |
heightを削除する |
min-heightなら共存可 |
| カード内の領域を伸ばしたい |
flex-grow: 1を使う |
flex-direction: column |
| フッターを下に固定 |
margin-top: autoを使う |
またはflex-grow |
| 行をまたいだ高さ揃え |
CSS Gridに切り替え |
grid-auto-rows: 1fr |
| 画像の高さがバラバラ |
object-fitで統一 |
height固定 + object-fit: cover |
| フレームワークとの競合 |
DevToolsで確認して上書き |
align-items: stretch |
最も重要なポイントは、Flexboxはデフォルトで高さが揃うということです。揃わない場合は「何かがstretchを阻害している」ので、DevToolsでalign-itemsと子要素のheightを確認するのが解決の近道です。
カードレイアウトでは、外側の高さ揃え(align-items: stretch)に加えて、内側の構造(flex-direction: column + flex-grow)を設計することで、美しく整ったUIを実現できます。
関連記事