結論:flexアイテムが縮まないのはflex-shrinkのせいではない。初期値のmin-width:autoが下限を作っている
- flex/gridアイテムは初期値が min-width:auto =「中身より小さくならない」。flex-shrink:1 でも中身の幅で止まる
- text-overflow:ellipsis が効かないのはほぼこれ。3点セット(nowrap/overflow:hidden/ellipsis)に min-width:0 を足す
- 入れ子のときは最深部だけでは効かない。テキストまでの flex の親を全段 min-width:0 にする
- Gridの 1fr は minmax(auto,1fr) の意味。はみ出すなら minmax(0,1fr) に書き換える
- flex-shrink:0 はアイコンやボタンなど「縮ませたくない側」だけ。テキスト側に付けると横スクロールの原因になる
「text-overflow: ellipsis を3点セットで正しく書いたのに、テキストが省略されず親からはみ出す」「flex-shrink は初期値の1なのに、アイテムがまったく縮まない」——レイアウトが完成間際で崩れるとき、原因の大半はこの1つの仕様に集約されます。結論から言うと、flexアイテムとgridアイテムは、初期値で「中身の最小幅より小さくならない」よう保護されているからです。この記事では、その下限がどこで生まれるのか、入れ子構造のどの段に min-width: 0 を入れるべきか、Gridの 1fr で同じ事故が起きる理由、そして犯人の段をコンソールで数秒で特定する方法までを、実案件で確認している順番のまま整理します。
flexアイテムの下限は「中身の最小サイズ」
flex-shrink の初期値は 1 なので、アイテムは縮む設定になっています。それでも縮まないのは、別の場所に下限があるからです。flexアイテムの min-width は初期値が auto で、これは 「中身が要求する最小の幅(min-content)より小さくしない」 という意味になります。
通常のブロック要素なら min-width: auto は 0 と同じ扱いですが、flexアイテムとgridアイテムのときだけ挙動が変わる、というのがつまずきの元です。つまり flex: 1(=flex: 1 1 0%)を指定して「基準幅0から伸び縮みする」つもりでも、min-widthの下限だけは残っているため、中の長い文字列や画像が床になってそこで止まります。
横方向のレイアウトなら min-width、flex-direction: column の縦積みなら min-height が同じ働きをします。縦スクロール領域が縮まないときは min-height: 0 を疑ってください。
症状は3つの顔で出る
- 省略されない:
white-space: nowrapとoverflow: hiddenとtext-overflow: ellipsisを書いたのに、「…」にならず1行がそのまま伸びる - 親からはみ出す:カードの中のテキストや
<pre>が枠の外へ飛び出し、背景色や枠線だけが元の幅で残る - ページ全体に横スクロールが出る:狭い画面でだけ右に余白ができ、body に
overflow-x: hiddenを当てて隠したくなる
3つ目は対症療法で塞ぐと、後からスクロール連動やスティッキーの挙動まで巻き込んで壊れます。ページ全体の横スクロールについては原因の切り分け手順をスマホで横スクロールが出る原因の記事にまとめているので、body側を触る前にそちらで犯人を特定してください。
直し方:min-width:0 を「テキストまでの全段」に入れる
基本形はこれだけです。
.card__body {
flex: 1;
min-width: 0; /* これが無いと下の3点セットが効かない */
}
.card__title {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
実務でハマるのはここからです。入れ子になっている場合、いちばん内側にだけ min-width: 0 を入れても効きません。 下限は各段のflexアイテムがそれぞれ持っているため、外側の段が中身の幅で止まっていれば、内側をいくら0にしても全体の幅は縮まないからです。
私の案件でも、ヘッダー → ユーザー情報のラッパ → 名前とメールの縦積み、という3段のflexで、最深部の行にだけ min-width: 0 を足して「効かない」と30分溶かしたことがあります。テキストからルート方向へたどって、途中にあるflexアイテムを全段 min-width: 0 にした瞬間に省略が効きました。 崩れているテキストから親を順に見上げ、display: flex の子にあたる要素を漏れなく拾うのが確実です。
共通クラスを1つ用意して、縮めたい列に付けて回る運用にしておくと事故が減ります。
/* 縮むことを許可する列に付ける */
.is-shrinkable { min-width: 0; }
/* 縦積み(column)のスクロール領域用 */
.is-shrinkable-y { min-height: 0; }
Gridの 1fr でも同じ事故が起きる
grid-template-columns: 200px 1fr と書いた列に、折り返せない長いURLやコードブロック、white-space: nowrap のテーブルが入ると、1fr の列が中身の幅まで広がってグリッド全体がはみ出します。 1fr は minmax(auto, 1fr) の短縮であり、この auto が先ほどと同じ下限だからです。
/* はみ出す */
.layout { grid-template-columns: 200px 1fr; }
/* 中身に引きずられない */
.layout { grid-template-columns: 200px minmax(0, 1fr); }
サイドバー+本文の2カラムで、本文に長いURLやコードを載せた瞬間だけレイアウトが崩れる場合は、ほぼこれです。1fr は minmax(0, 1fr) で書くのを既定の書き方にしてしまって構いません。auto-fit や auto-fill の repeat() でも同様に効きます。
犯人の段をコンソールで一発で見つける
目視で祖先をたどるのは骨が折れるので、はみ出している要素を選択した状態($0)でDevToolsのコンソールに貼り、下限を持っている段を一覧で出します。
// はみ出す要素をElementsで選択してから実行
(function (el) {
const rows = [];
for (let n = el; n && n !== document.documentElement; n = n.parentElement) {
const ps = n.parentElement ? getComputedStyle(n.parentElement) : null;
const isItem = ps && /flex|grid/.test(ps.display);
if (!isItem) continue;
const s = getComputedStyle(n);
rows.push({
要素: n.tagName.toLowerCase() + (n.className ? '.' + String(n.className).split(' ')[0] : ''),
親: ps.display,
minWidth: s.minWidth,
overflow: s.overflowX,
要対応: s.minWidth === 'auto' && s.overflowX === 'visible'
});
}
console.table(rows);
})($0);
要対応: true の行がすべて下限を持っている段です。ここに min-width: 0 を入れます。なお overflow が visible 以外(hidden・auto・scroll)の要素は、その時点で下限が 0 相当になるため対応不要です。「スクロール領域を作ったら急に省略が効き出した」のはこの仕様で、逆に言えば overflow: hidden を足すのも正解の一つになります。
実務で踏みやすい周辺の落とし穴
flex-shrink: 0の付け場所を間違える:アイコン・アバター・ボタンなど「潰れてはいけない側」に付けるのが正解です。テキスト側に付けると、狭い画面で必ずはみ出します- 画像の
max-width: 100%が効かない:画像自身ではなく、画像を包んでいるflexアイテムに下限が残っているケースです。包む側をmin-width: 0にします - 日本語は折り返るのに崩れる:原因はほぼ英数の長い連続文字列(URL・ID・トークン)です。全体に
word-break: break-allを当てると日本語の途中で切れて読みにくくなるので、overflow-wrap: anywhereを該当箇所に限定して当てます - テーブルとコードブロック:折り返せない中身は、縮めるのではなく
overflow-x: autoのラッパで包んでその枠内だけ横スクロールさせるのが、レイアウトを壊さない定石です - 親の高さが縮まない:
flex-direction: columnの中でスクロールさせたい領域にはmin-height: 0。これは画面高を基準にした全画面レイアウトで顕在化するため、100vhとdvhの記事の内容と併せて確認すると早いです
公開前チェックリスト
- 幅375pxで、長いメールアドレスと長いURLをダミーで流し込んで崩れないか確認した
- 省略されるべきテキストが「…」で止まり、親の枠を越えていない
- ページ全体に横スクロールが出ていない(body の
overflow-x: hiddenで隠していない) flex-shrink: 0がアイコン・ボタン側にだけ付いている- Gridの
1frがminmax(0, 1fr)になっている - ユーザー名やタイトルに実データ級の長さを入れて確認した(短いダミーテキストでは再現しない)
この手の崩れは「CSSの書き方のミス」に見えて、実際はどこまで縮んでよいかを宣言していないだけです。縮ませたい列に min-width: 0、縮ませたくない要素に flex-shrink: 0、Gridは minmax(0, 1fr)——この3つを最初から既定の書き方にしておくと、後から原因を探す時間がまるごと消えます。重なり順の不具合と同じく、症状が出てから数字を足すのではなく、構造の側で決めておくのが結局いちばん速い解法です(重なり順についてはz-indexが効かない理由の記事で同じ切り分け方を扱っています)。
関連記事(コーディング実務Tips)
- スマホで横スクロールが出る原因の特定手順と直し方
- z-indexが効かない本当の理由|スタッキングコンテキストの見つけ方
- 100vhがスマホでずれる原因とdvh/svh/lvhの使い分け
- Google FontsでLCPが遅くなる・文字がガタつく原因と直し方
- height:autoがアニメーションしない理由と直し方|max-height・grid 0fr→1fr・JSの使い分け
- object-fitとaspect-ratioが効かない・画像の縦横比が崩れる原因と直し方
- gapが効かない・隙間が2倍になる原因9パターン
- 文字の上下にできる余白の正体はhalf-leading
- overflow:hiddenが効かない・効きすぎる原因
- CSSで中央寄せできない原因は「誰が中心を決めているか」
- メディアクエリが効かない原因はviewportと単位
- inputやselectのCSSが効かない原因はappearance
- 画像の下の4pxの隙間とinline-blockの謎のすき間
- height: 100% が効かないのはなぜか
- ::before / ::after が表示されない原因と直し方
- 日本語の改行がおかしい・URLがはみ出す原因
- marginが効かない・余白が消える原因はマージンの相殺
- text-overflow: ellipsisが効かない原因と直し方
多言語・ベトナム語サイトの制作でお困りではありませんか?
フォント・文字化け・WordPress多言語化・サーバー選定まで、多言語サイト制作の実績を持つ制作会社が一括対応します。お見積り・ご相談は無料です。
多言語サイト制作の詳細を見る →すぐに相談したい方は お問い合わせフォームへ