ヘッダーに position: sticky と top: 0 を書いた。動かない。top を 1px にしてみる、z-index を足してみる、height を明示してみる——それでも動かない。
この段階で疑っているのは、たいてい宣言を書いた要素そのものです。けれど解くべき問いは「この書き方のどこが間違っているか」ではなく、「この要素は、どの箱の内側に制約されているか」です。sticky は単独の要素に効く指定ではなく、祖先のどこかにある箱との関係で決まります。そしてその箱を作るのは、position ではなく overflow です。
この記事では、仕様とリファレンスの条文から、その箱がどこで生まれ、何が判定を分けるのかを順に見ていきます。扱わないのは、ブラウザごとの対応状況と実装差です。参照したのはMDN の position、CSS Positioned Layout Module Level 3、CSS Overflow Module Level 3 の3件で、いずれも2026年10月28日に取得しました。後者2つは W3C のワーキングドラフト(2025年10月7日版)であり、勧告(Recommendation)ではありません。
30秒で要点
- 仕様は sticky を「最も近い scrollport を基準に、inset プロパティが定める矩形の内側へ位置を制約する」指定として定義している。画面に固定する指定ではない
- 貼り付く先は「実際にスクロールしている祖先」ではなく「スクロール機構を持つ最も近い祖先」である
overflow: hiddenは、仕様上スクロールコンテナに分類される。一方overflow: clipはスクロールを一切禁じるため分類されない。この2分類が、基準の箱がどこに移るかを決める- ただし
overflow-xとoverflow-yの一方がvisibleでもclipでもない場合、clipはhiddenに計算される。片軸だけclipに替える処方は、ここで破れる - 判定は軸ごとに行われる。縦の追従を書いたつもりでも、横では何も起きていないことがある
- 追従する距離の上限は包含ブロックの内側に限られる。祖先が短ければ、少し動いて止まる
stickyは「固定する」指定ではなく「矩形の内側に制約する」指定である
まず、この値が何を指示しているかを言い直します。
仕様(CSS Positioned Layout Module Level 3 の3.4節)は、sticky を relative に似たものとしながら、オフセットが最も近い scrollport を基準に自動計算されると定義しています。inset プロパティ(top / right / bottom / left など、辺からの距離を指定するプロパティ群)は、その scrollport の各辺からの inset を表し、それが sticky view rectangle という矩形を定めて、ボックスの位置を制約します。この用途では auto はゼロの inset として扱われます。
Sticky positioning is similar to relative positioning except the offsets are automatically calculated in reference to the nearest scrollport. For a sticky positioned box, the inset properties represent insets from the respective edges of the nearest scrollport, defining the sticky view rectangle used to constrain the box's position.
ここが出発点です。top: 0 は「上端に貼り付けろ」という命令ではなく、「基準となる矩形の上辺から0の位置に境界を置く」という矩形の定義です。矩形がどこにあるかが決まらなければ、値だけでは何も決まりません。
矩形がボックスより小さくなる場合の規定もあります。仕様は、sticky view rectangle の大きさがボックスのボーダーボックスより小さくなる軸について、end 側の inset を(負の値になりうる形で)縮め、矩形をボーダーボックスの大きさまで広げると書いています。条文はこれを具体例で示しています——最も近い scrollport が300pxの高さ、sticky ボックスのボーダーボックスが200pxの高さ、top: 20px なら、上辺の inset は20px、下辺の inset は0pxで、矩形は280pxになる。しかし scrollport が100pxしかなければ、下辺の実効 inset は-120pxになり、矩形は200pxになる。これらの数値は条文に置かれた例であって、特定のブラウザで測った値ではありません。
押し戻される方向にも規定があります。top が auto 以外で bottom が auto の sticky 要素は、下方向にしか押されず、上方向にずれることはない、と仕様は注記しています。
貼り付く先は、実際にスクロールしている祖先ではない
次が、この問題の中心にある一文です。MDN は position の sticky 値について、次のように注記しています。
Note that a sticky element "sticks" to its nearest ancestor that has a "scrolling mechanism" (created when overflow is hidden, scroll, auto, or overlay), even if that ancestor isn't the nearest actually scrolling ancestor.
読み取るべき点が2つあります。
1つは、基準がスクロール機構を持つ最も近い祖先だということ。機構を持つかどうかは overflow の値で決まります。
もう1つは、その祖先が実際にスクロールしていなくてもよいということ。ページ全体がスクロールしていて、途中の祖先はまったくスクロールしていなくても、その祖先が機構を持っていれば、そこが基準になります。そして基準になった祖先がスクロールしなければ、scrollport は動かず、矩形も動かず、要素は何も起きていないように見えます。
この「何も起きていないように見える」が、宣言を書いた要素を疑わせる原因です。エラーも警告も出ません。値を変えても変わりません。変えるべき値が、別の要素に書かれているからです。
なおMDN のこの列挙に clip は入っていません。clip の扱いは仕様側で明示されています(次節)。また overlay が挙がっているのは、Overflow 3 が overlay を auto の旧来の別名としてサポートしなければならないと規定しているためで、現役の推奨値ではありません。
overflow: hidden は、スクロールしないのにスクロールコンテナである
では、どの値が機構を作るのか。CSS Overflow Module Level 3 は、overflow の各値について、スクロールコンテナになるかどうかを値ごとに明記しています。
visible: overflow の特別な扱いはなく、ボックスはスクロールコンテナではないhidden: スクロール操作のための表示(スクロールバーなどの操作部品)を出してはならず、利用者が直接スクロールすることもできない。しかし内容はプログラムからスクロール可能であり、したがってボックスはスクロールコンテナであるclip: いかなる機構によるスクロールも禁じる。したがってボックスはスクロールコンテナではないscroll: 内容はパディングボックスにクリップされるが、スクロールして表示できる(したがってスクロールコンテナである)auto: スクロール可能なあふれがあるときはscrollのように、そうでなければhiddenのように振る舞う
hidden の条文を引きます。
the UA must not provide any scrolling user interface(中略)However, the content must still be scrollable programmatically, for example using the mechanisms defined in [CSSOM-VIEW], and the box is therefore still a scroll container.
ここが「overflow: hidden を付けたら sticky が効かなくなった」の正確な根拠です。hidden は「スクロールしないのにスクロールコンテナ」という状態を作ります。スクロールバーが出ていないことは、スクロールコンテナでないことの証拠にはなりません。
仕様はこの2分類に名前を与えています。scroll / auto / hidden が overflow の scrollable values、visible / clip が non-scrollable values です。sticky の基準がどこに移るかを判定するときに見るのは、この分類です。
overflow: clip に替えれば直る、とは言えない
clip はスクロールコンテナを作らない。ならば、横スクロールを抑えるために書いた overflow-x: hidden を overflow-x: clip に替えればよい——そう読みたくなります。ここに条件があります。
Overflow 3 は次のように規定しています。
The visible / clip values of overflow compute to auto / hidden (respectively) if one of overflow-x or overflow-y is neither visible nor clip.
つまり、overflow-x: clip と書いても、overflow-y が auto / scroll / hidden のいずれかであれば、clip は hidden に計算されます。hidden は scrollable values です。スクロールコンテナが復活し、元の問題に戻ります。 片軸だけ clip に替える処方が破れるのはこの条件です。両軸を clip(または一方を visible)にしてはじめて、non-scrollable values の側に入ります。
副作用もあります。仕様は、clip は hidden と異なり要素に新しい整形コンテキストを作らせないと書いています。注記では、整形コンテキストも必要な場合に display: flow-root との併用を挙げています。
Unlike hidden, this value does not cause the element to establish a new formatting context.
overflow: hidden を書いた理由が「子要素の回り込みを解除する」だった場合、clip への置き換えはその効果を失わせます。hidden から clip への変更は、スクロールコンテナを消す変更と、整形コンテキストを消す変更の2つを同時に行う変更です。 片方だけを意図していたなら、もう片方が別の崩れとして現れます。
判定は軸ごとに行われる
ここまでは祖先の話でした。宣言した要素の側にも、見落としやすい規定があります。
MDN は、固定したい軸の inset プロパティのうち少なくとも1つを auto 以外にする必要があるとし、ある軸の両端の inset がどちらも auto なら、その軸では sticky は relative として振る舞うと注記しています。閾値を1つも指定しなければ、sticky は relative と区別できないとも書いています。
仕様側の値定義も同じ構造です。sticky は relative と同一で、ただし inset プロパティが両端とも auto ではない軸について、最も近い祖先スクロールコンテナの scrollport を基準に自動調整される、とされています。
実務で現れるのは、縦の追従を意図して top だけを書いた要素が、横スクロール時にはまったく追従しないという形です。これは不具合ではなく、横軸の両端が auto なので横軸では relative として扱われているだけです。「sticky が効いている/効いていない」は要素単位の状態ではなく、軸ごとの状態です。
親が短いと、少し動いて止まる
もう1つ、「中途半端に効く」の根拠になる規定があります。
仕様は、押し戻しが起きる条件を、対応する inset が auto でない辺に限り、かつ位置ボックスが包含ブロックの内側に収まる範囲に限ると書いています。位置ボックスは原則としてマージンボックスです(マージン辺と包含ブロックの対応辺の距離がマージンより小さい辺については、その距離がマージンの代わりに使われます)。
the box must be visually shifted (as for relative positioning) to be inward of that sticky view rectangle edge, insofar as it can while its position box remains contained within its containing block.
sticky の値定義にも、「利用者がスクロールするあいだ、包含ブロックの中でボックスを見える状態に保とうとする」と目的が書かれています。包含ブロックを超えては追従しません。
したがって「数十行スクロールしたら見出しが置いていかれた」という現象は、たいてい設定の誤りではなく、その見出しの包含ブロックがそこで終わっているということです。追従させたい距離は、top の値ではなく祖先の高さで決まります。
関連する注記も2つあります。同じコンテナ内の複数の sticky ボックスは独立にオフセットされるため、互いに重なることがあります。また、スクロール位置を対象とする操作(アンカーリンクでの移動など)では、sticky 要素はオフセット後の位置にあるものとみなされ、必要最小限だけ戻されます。
なぜ「書き方のミス」に見えるのか
構造を知っても、最初に疑うのは宣言を書いた要素です。この順序になる理由を、3つに分けて考えています。
1つ目は、原因の宣言が別の目的で書かれていること。 overflow: hidden は、スクロールコンテナを作るために書かれることはほとんどありません。横スクロールを抑えるため、子の回り込みを解除するため、はみ出した装飾を隠すため——どれも重なりや追従とは無関係の意図です。意図が無関係なら、容疑者リストに入りません。
2つ目は、見た目が反証にならないこと。 hidden の箱はスクロールしません。スクロールバーも出ません。「ここはスクロールしないのだから、スクロールの基準にはならないはずだ」という推論は自然ですが、仕様は逆を規定しています。確認したつもりの観察が、誤った結論を裏付けてしまう構造になっています。
3つ目は、簡単なページでは実際に効いてしまうこと。 祖先に overflow の指定が1つもない状態では、間に挟まる箱が無く、top を書くだけで期待どおり動きます。sticky を学んだ時期の経験は、「要素に書けば効く」という理解を裏切りません。誤解が顔を出すのは、コンポーネント分割が進み、カードやモーダルや横スクロール対策が入ってからです。そのとき「要素に書けば効く」は何度も成功しています。
判断軸:この要素は、どの矩形の内側にいるのか
点検の順序を次のように提案します。
- 宣言した要素から祖先をたどり、
overflowを指定している最初の祖先を探す。overflow/overflow-x/overflow-yのいずれかがscroll/auto/hidden(またはoverlay)なら、そこが基準の候補です。visibleとclipは飛ばせますが、片軸だけclipのときは計算後の値を確かめます(hiddenに化けている可能性があります) - その宣言が何のために書かれたかを特定する。 横スクロール抑止なのか、回り込み解除なのか、装飾のはみ出し対策なのか。目的が分かれば、どの代替手段が取れるかが決まります。回り込み解除が目的なら
clipへの置き換えにはdisplay: flow-rootの併用が必要になります - 包含ブロックの高さを見る。 基準の箱が意図どおりでも、包含ブロックが短ければ追従はそこで止まります。「追従させたい距離」を先に決めて、それを収める祖先があるかを確認します
- 軸ごとの inset を確かめる。 固定したい軸の inset が両端とも
autoになっていないか。縦と横で別の判定になっていることを前提に見ます - 最後に値を調整する。 矩形の位置と大きさが決まってはじめて、
topの値が意味を持ちます
この順序の要点は、1〜4を飛ばして5から入ると、効かないうえに原因が隠れるところです。値を増減した痕跡が残ると、次に読む人は「ここは値で制御されている」と読み、同じ探索をやり直します。
手元で試せる最小の確認
いま追従しない sticky 要素があるなら、次の手順で基準の箱を特定できます。コードを1行ずつ戻せる状態で試してください。
- その要素から祖先をたどり、
overflow系の宣言を持つ最初の祖先を1つ選ぶ - その宣言を一時的に
visibleに変える(clipではなくvisibleにします。clipは前述の計算規則でhiddenに化けることがあり、変化の有無が判定材料になりません) - 追従するようになったかを見る
追従したなら、原因はその祖先です。変わらないなら、さらに上に overflow を持つ祖先があるか、または包含ブロックの高さが足りていません。
棚卸しをするなら、次の指標で現状を数えられます。
- 分子:コードベース内の
position: sticky宣言のうち、「基準になる scrollport がどの祖先か」をコードを見て即答できる件数 - 分母:コードベース内の
position: sticky宣言の総数 - 単位:%(件数/件数)
この比率の高低そのものが目的ではありません。分母に入っていて分子に入らない宣言が、次に「効かない」と報告される候補であることを把握するための数え方です。分子に入らない1件を選び、祖先をたどって基準を書き出す——それが最初の一手になります。
確かなことと、まだ確かでないこと
確かなこと(2026年10月28日に情報源を取得して確認した範囲):sticky が最も近い scrollport を基準に sticky view rectangle の内側へ制約される定義であること。貼り付く先が「スクロール機構を持つ最も近い祖先」であり、実際にスクロールしていなくても基準になること。hidden がスクロールコンテナに分類され、clip が分類されないこと。visible / clip が、他軸が visible でも clip でもない場合に auto / hidden に計算されること。clip が整形コンテキストを作らないこと。判定が軸ごとであり、両端 auto の軸では relative として振る舞うこと。押し戻しが包含ブロックの内側に限られること。
まだ確かでないこと:ブラウザの対応状況と実装差。今回参照した3件はリファレンスと仕様であり、どのブラウザのどの版が clip や overlay をどう扱うかは確認していません。重要な判断の前には、対応状況の資料を別途開くか、対象のブラウザで実際に動かしてください。また CSS Positioned Layout Module Level 3 と CSS Overflow Module Level 3 はいずれもワーキングドラフト(2025年10月7日版)であり、条文は今後変わりえます。
本記事が踏み込んでいないこと:開発者ツールの具体的な操作手順(一次情報にはなく、今回自分で操作していません)。本文の280px・200pxといった数値は条文の例であり、測定値ではありません。
追従で迷ったときに戻る3つの問い
- この要素の基準になっている祖先は、どれか。 答えられないうちは、値の議論に入れません
- その祖先の
overflowは、何のために書かれたか。 追従のために書かれたoverflowはまれで、たいていは別の目的の宣言です - 追従させたい距離は、どの祖先の内側に収まるか。 距離を先に決めないと、どこまでが仕様どおりでどこからが設計の問題か切り分けられません
もう1点、接続しておきたい事実があります。MDN は、sticky が常に新しいスタッキングコンテキストを作ると定義しています。つまり sticky を1つ書くたびに、重なり順を内側で決着させる箱が1つ増えます。追従の問題を直した結果として重なり順が変わることがあるのは、このためです。重なり順の側の判定についてはz-indexを上げても前に来ない理由で扱っています。また、今回参照した2つの仕様がどちらもワーキングドラフトであるように、「仕様があるかどうか」と「何がどこまで決まっているか」は別の問いです。その読み方は冪等性キーは標準ではないに書きました。
参考資料・引用元
- position — CSS | MDN(2026年10月28日に取得・HTTP 200)
- CSS Positioned Layout Module Level 3 — W3C Working Draft(2026年10月28日に取得・HTTP 200。確認時点の版は2025年10月7日版)
- CSS Overflow Module Level 3 — W3C Working Draft(2026年10月28日に取得・HTTP 200。確認時点の版は2025年10月7日版)