Overflow の clip と hidden
軸ごとの overflow セマンティクス — 純粋なペイントクリッピングの clip とスクロールコンテナを作る hidden、強制変換ルール、Grid の描画突き抜けバグ
問題
AIエージェントは、望まないオーバーフローに対する万能の修正として overflow: hidden を使いがちです。これは正しくありません。hidden はペイントをクリップする以上のことを行い、その余分な挙動が特徴的なバグのクラスを生み出します。実際のレビューで見つかった次の2つの不具合は、どちらも overflow のセマンティクスに起因するものでした。
アプリシェルのグリッド(
grid-template-rows: auto 1fr auto)で、ボディ行の大きすぎるコンテンツが隣のフッター行を突き抜けて描画された。min-height: 0によってトラックのジオメトリは安定していたが、デフォルトのoverflow: visibleがコンテンツを次の行の上に重ねて描画させていた。素朴な修正 — ボディへの
overflow: hidden— は突き抜けを止めたが、全幅の子要素のインライン軸の box-shadow をパディング端で平らに切断してしまった。
どちらの不具合も、あまり知られていない2つのルールから生じます。第一に、hidden はスクロールコンテナを作り、頼んでいないスクロールの副作用を持ち込みます。第二に、overflow の2つの軸は独立ではありません。visible は、もう一方の軸の hidden・scroll・auto と共存できず、暗黙のうちに auto へ強制変換されます。このパネルの正しい修正は hidden でもラッパー要素でもなく、overflow-x: visible; overflow-y: clip でした。
解決方法
実際に必要なものに応じて overflow の値を選びます。
本物のスクロール領域(ユーザーによるスクロール、
scrollTo、1行省略記号のレシピ)が必要ならauto・scroll・hiddenを使います。ペイントのクリッピングだけが必要なら
clipを使います。スクロールコンテナを作らず、新しいフォーマッティングコンテキストを強制せず、ブラウザはスクロール機構を完全に省略でき、overflow-clip-marginも使えます。一方の軸をクリップしつつ、もう一方の軸で装飾(シャドウやアウトライン)を生かしたいなら
overflow-x: visible; overflow-y: clip(またはその転置ペア)を使います。visible+clipは、強制変換ルールがそのまま残す唯一の混合ペアです。
clip と hidden の違い
どちらの値もボックス端でペイントをクリップします。それ以外はすべて異なります。
| 挙動 | overflow: hidden | overflow: clip |
|---|---|---|
| ペイントのクリップ | あり(パディングボックスで) | あり(overflow clip edge で。overflow-clip-margin で調整可能) |
| スクロールコンテナの生成 | あり — プログラムからスクロール可能(scrollTop、scrollIntoView) | なし — ボックスは一切スクロールできない |
| フォーカス/フラグメント遷移でのスクロール | あり。スクロールバーがないままコンテンツが静かにずれ、元に戻す手段がない | なし |
| スクロールアンカリングへの参加 | あり | なし |
| 新しいフォーマッティングコンテキストの確立 | あり | なし |
もう一方の軸の visible との組み合わせ | 不可 — auto へ強制変換する | 可 |
/* A scroll container you cannot see or operate */
.card {
overflow: hidden;
}
/* Pure paint clipping */
.card {
overflow: clip;
}スクロールコンテナかどうかの違いは、机上の話ではありません。hidden のボックスは、ブラウザが「内部の何かを見せる必要がある」と判断するたびにスクロールします。キーボードフォーカスが画面外の子孫要素に移ったとき、location.hash による遷移、ライブラリからの scrollIntoView などです。コンテンツはずれ、戻すためのスクロールバーは存在せず、レイアウトは壊れたように見えます。clip のボックスは何をしてもスクロールできません。
Note
クリッピングの背後にフォーカス可能なコンテンツを隠すことは、それ自体がアクセシビリティ上の問題の兆候です。キーボードユーザーは見えない要素にフォーカスすることになります。このデモはスクロール可能性の違いを示すためだけのものであり、インタラクティブなコンテンツの本当の解決策は「クリップしないこと」です。
軸ごとの強制変換ルール
overflow は overflow-x と overflow-y のショートハンドですが、2つの軸は独立ではありません。片方の軸が hidden・scroll・auto のとき、もう一方の軸の visible は auto に、clip は hidden に計算されます。つまり、そっとしておきたかった軸に、暗黙のスクロールコンテナが生まれます。
| 宣言 | 計算結果 | 効果 |
|---|---|---|
overflow-y: hidden(x は visible のまま) | overflow-x: auto; overflow-y: hidden | x 軸に暗黙のスクロールコンテナ |
overflow-x: hidden(y は visible のまま) | overflow-x: hidden; overflow-y: auto | y 軸に暗黙のスクロールコンテナ |
overflow-x: clip; overflow-y: auto | overflow-x: hidden; overflow-y: auto | clip がスクロールコンテナ系の値へ昇格される |
overflow-x: visible; overflow-y: clip | 変化なし | 唯一の合法な混合ペア |
visible + clip は、書いたとおりに生き残る唯一の組み合わせです。したがって「ブロック軸のオーバーフローはクリップし、インライン軸の装飾は生かす」と表現できる唯一の方法でもあります。
/* What you wrote */
.track {
overflow-y: hidden;
}
/* What you got */
.track {
overflow-x: auto; /* forced — a scroll container you never declared */
overflow-y: hidden;
}
/* The only way to clip one axis and keep the other truly visible */
.track {
overflow-x: visible;
overflow-y: clip;
}Grid の描画突き抜けバグ
Grid や Flex のトラックでは、コンテンツが大きすぎるときの不具合が「ジオメトリ」と「ペイント」という2つの独立した層で起こります。min-height: 0(または min-width: 0)が修正するのはジオメトリです。トラックがコンテンツに合わせて伸びるのを止めます。しかしペイントには何もしません。デフォルトの overflow: visible のままでは、アイテムのコンテンツはトラック端を越えて描画され続け、下のフッター行に重なり、パネルの外まで逃げ出します。Grid アイテムはドキュメント順にアトミックに描画されるため、フッターに不透明な背景を付ければ自分の帯だけは覆い隠せますが、それは重なりを隠すだけです。オーバーフローはその下のパネルボーダーも越えて続きます。トラックのアイテムがクリップするまで、コンテンツを封じ込めるものは何もありません。
.app-panel {
display: grid;
grid-template-rows: auto 1fr auto;
height: 100dvh;
}
.app-panel__body {
/* Step 1: geometry — let the track shrink below its content */
min-height: 0;
/* Step 2: paint — stop content from overlaying the footer */
overflow-y: clip;
}両方のステップが必要です。min-height: 0 だけでは突き抜けが残り、overflow-y: clip だけではトラックが伸びたままです。さらに、クリップは軸別の形でなければなりません。ショートハンドの overflow: clip(や overflow: hidden)はインライン軸もクリップしてしまい、シャドウやアウトラインを切断します — 次のセクションで扱います。min-width: 0 / min-height: 0 のリセット自体は Flexboxパターン で解説しています。
シャドウの切断と軸別指定による解決
両軸のクリップは、レイアウト上のオーバーフローだけでなく装飾も切断します。overflow: hidden のコンテナ内にある全幅の子要素は、インライン軸の box-shadow をコンテナのパディング端で平らに切断されます。シャドウは子のボーダーボックスの外側に描画され、コンテナは自分の外側をすべてクリップするからです。軸別のペアはブロック軸のクリップを保ちながらインライン軸を解放するので、シャドウやアウトラインは完全な形で描画されます。シャドウの作り方自体は スムーズなシャドウトランジション で解説しています。
/* NG: shears the shadows flat at the inline edges */
.card-stack {
overflow: hidden;
}
/* OK: clips the block axis, lets inline-axis shadows breathe */
.card-stack {
overflow-x: visible;
overflow-y: clip;
}どちらのスタックも3枚目のカードを下端でクリップしています — ブロック軸のクリップは同じように機能します。異なるのはインライン軸だけです。左のスタックはすべてのシャドウを破線ボーダーの位置で平らに切断し、右のスタックは背景へシャドウがにじみ出ることを許しています。
overflow-clip-margin
clip には hidden にないもう1つの能力があります。クリップ境界を外側へ押し広げられることです。overflow-clip-margin は、コンテンツがクリップされる前にパディングボックスの外側どこまで描画してよいかを設定します。フォーカスリング、グロー、小さな装飾的なはみ出しを生かしつつ、暴走するコンテンツは封じ込められる、ちょうどよい余白です。
.glow-frame {
overflow: clip;
overflow-clip-margin: 16px; /* clip 16px outside the padding box */
}ブラウザサポート
overflow: clip 自体はすべてのブラウザでサポートされています(Baseline 2022)。overflow-clip-margin は Chromium と Firefox でサポートされていますが、Safari ではまだ出荷されていません。プログレッシブエンハンスメントとして扱ってください — クリップ自体は行われ、追加のマージンが失われるだけです。また、現在の実装は両軸が clip に計算される場合にのみこのプロパティを適用します。visible + clip のペアとは組み合わせられません。
クイックリファレンス
| シナリオ | テクニック |
|---|---|
| 本物のスクロール領域(ユーザーまたはプログラムによるスクロール) | overflow: auto、または hidden + スクリプトによるスクロール |
| 1行の省略記号 | overflow: hidden; text-overflow: ellipsis; white-space: nowrap |
| 装飾的なクリッピング — 一切スクロールしない | overflow: clip |
| ブロック軸をクリップし、インライン軸のシャドウ/アウトラインを保持 | overflow-x: visible; overflow-y: clip |
| Grid/Flex トラックのコンテンツが兄弟要素を突き抜ける | min-height: 0(ジオメトリ)+ overflow-y: clip(ペイント) |
| グローやフォーカスリングをクリップから守る | overflow: clip; overflow-clip-margin: 8px |
AIがよくやるミス
望まないオーバーフローすべてに
overflow: hiddenをデフォルトで使う。hiddenはスクロールコンテナを作ります。フラグメント遷移、scrollIntoView、キーボードフォーカスが静かにスクロールさせ、元に戻すスクロールバーもないままレイアウトがずれます。ペイントを切りたいだけならclipを使ってください。overflow-y: hiddenがもう一方の軸に影響しないと思い込む。 強制変換ルールにより、触れていないvisibleの軸はautoに計算されます — 宣言した覚えのない水平スクロールコンテナの誕生です。Grid や Flex のコンテンツがトラックからあふれたとき
min-height: 0で止める。 最小サイズのリセットが直すのはトラックのジオメトリだけです。アイテムがoverflow-y: clipでクリップするまで、コンテンツは後続の行を突き抜けて描画され続けます。突き抜けを
overflow: hiddenで直してシャドウが切断されたまま出荷する。 ショートハンドは両軸をクリップし、インライン軸の box-shadow やアウトラインをパディング端で平らに切断します。overflow-x: visible; overflow-y: clipを使ってください。overflow-clip-marginがhiddenでも効くと期待する。 このプロパティはclipにのみ適用され、現在の実装では両軸がclipの場合にのみ有効です。clipのボックスがスクロールできると期待する。clipのボックスはスクロールコンテナではありません。scrollTopへの代入は無視され、scrollIntoViewはクリップされたコンテンツを表示できません。ボックスを何かがスクロールする必要が少しでもあるなら、autoかhiddenを使ってください。
使い分け
hidden を使う場面
スクロールバーを見せないスクロールコンテナのセマンティクスが必要なとき。1行省略記号のレシピ(overflow: hidden; text-overflow: ellipsis; white-space: nowrap)、scrollTo ベースのスクリプト駆動スクローラー、スクロール可能なコンテンツの計測などです。フォーカスやフラグメント遷移がボックスをスクロールさせうるという副作用は受け入れることになります。
clip を使う場面
ペイントを切りたいだけで、何もスクロールすべきでないとき。装飾のはみ出しの封じ込め、レターボックスステージ、スクロールコンテナが意味を持たない印刷レイアウト、大きすぎる装飾的な背景などです。装飾目的のクリッピングすべてにおいて、clip は副作用がなく低コストなデフォルトです。
visible + clip ペアを使う場面
一方の軸はクリップし、もう一方の軸は本当に visible のままにしたいとき。コンテンツが兄弟の行を突き抜けるアプリシェルのトラックや、インライン軸のシャドウ・グロー・フォーカスアウトラインを完全な形で描画すべき子を持つコンテナです。visible + clip は、強制変換ルールが手を付けない唯一の軸別の組み合わせです。