「position: stickyを書いたのに固定されない」。CSSではかなりよくある詰まりポイントです。stickyは壊れているように見えて、実際には基準となるスクロール領域・親要素の高さ・配置条件のどれかが成立していないケースがほとんどです。
この記事では、stickyが効かない代表的な原因を7つに分け、確認方法と直し方を実装例付きで整理します。サイドバー、目次、テーブル見出し、追従ナビのデバッグにそのまま使える内容です。
position: stickyの基本
sticky要素は、最初は通常の文書フローに配置されます。スクロールによって指定した境界へ到達すると、その要素を含むブロックの範囲内で追従します。fixedのように常に画面へ固定されるわけではありません。
.sidebar {
position: sticky;
top: 24px;
}
縦方向に追従させるなら、topまたは論理プロパティのinset-block-startが必要です。また、stickyが参照するのは必ずしも画面全体ではなく、overflowによって作られた最寄りのスクロール祖先です。
原因1:topを指定していない
position: stickyだけでは、どの位置から追従するのか決まりません。縦方向ならtopかbottom、横方向ならleftかrightの少なくとも一つを指定します。
/* 効かないように見える */
.sidebar {
position: sticky;
}
/* 上端24pxで追従 */
.sidebar {
position: sticky;
top: 24px;
}
固定ヘッダーがある場合は、ヘッダーの高さを避ける値にします。
:root {
--header-height: 72px;
}
.sidebar {
position: sticky;
top: calc(var(--header-height) + 16px);
}
原因2:祖先要素のoverflowがスクロール基準を変えている
祖先にoverflow: hidden、auto、scrollなどが指定されると、その要素がstickyの基準になり得ます。画面をスクロールしているのに、stickyは別の要素を基準にしている状態です。
.page {
overflow: hidden; /* 横はみ出し対策で付けた指定が原因になることも */
}
/* 横方向だけを制御したい場合 */
.page {
overflow-x: clip;
}
原因調査では、対象要素からhtmlまで祖先をたどり、overflowの計算値を確認します。
let element = document.querySelector('.sidebar');
while (element) {
const style = getComputedStyle(element);
console.log(element, style.overflow, style.overflowX, style.overflowY);
element = element.parentElement;
}
原因3:親要素が短く、追従する余地がない
stickyは親要素の外へは出ません。親要素の高さがsticky要素とほぼ同じなら、追従できる距離がなく、通常配置と同じように見えます。
<div class="layout">
<main class="content">長い本文...</main>
<aside class="sidebar">目次</aside>
</div>
.layout {
display: grid;
grid-template-columns: minmax(0, 1fr) 280px;
gap: 40px;
align-items: start;
}
.sidebar {
position: sticky;
top: 24px;
}
コンテンツとサイドバーを同じレイアウトコンテナへ入れ、長い本文の高さを親要素が持つ構造にします。
原因4:sticky要素がスクロール領域より高い
sticky要素自体が画面やスクロールコンテナより高い場合、上端と下端の条件を両立できません。長い目次やフィルター一覧で起こりやすい問題です。
.sidebar {
position: sticky;
top: 24px;
max-height: calc(100svh - 48px);
overflow-y: auto;
}
要素内をスクロール可能にする場合は、キーボード操作やスクロール領域の分かりやすさも確認します。スマートフォンでは追従を解除する判断も有効です。
@media (max-width: 767px) {
.sidebar {
position: static;
max-height: none;
overflow: visible;
}
}
原因5:FlexboxやGridでstretchされている
GridやFlexboxの交差軸では、初期値によって子要素が親の高さいっぱいへ引き伸ばされる場合があります。sticky要素に移動余地がなくなるため、親側へalign-items: start、または要素側へalign-self: startを指定します。
.layout {
display: grid;
grid-template-columns: 1fr 280px;
align-items: start;
}
.sidebar {
position: sticky;
top: 24px;
align-self: start;
}
原因6:スクロールしている要素を勘違いしている
モーダル、管理画面、横スクロールテーブルなどでは、bodyではなく内側のパネルがスクロールしていることがあります。この場合、sticky要素はそのパネル内に配置する必要があります。
.panel {
height: 70svh;
overflow-y: auto;
}
.panel-header {
position: sticky;
top: 0;
z-index: 2;
background: #fff;
}
DevToolsでスクロール時にscrollTopが変化する要素を確認すると、実際のスクロールコンテナを特定できます。
原因7:固定されているが、重なりで見えない
stickyは新しいスタッキングコンテキストを作ります。しかし、別の固定ヘッダーや親要素の重なり順によって裏側へ隠れることがあります。背景が透明だと、固定されていても本文と重なって読めません。
.sticky-heading {
position: sticky;
top: 0;
z-index: 10;
background: #fff;
border-bottom: 1px solid #d8d8d8;
}
z-index: 9999を闇雲に増やす前に、祖先のposition、z-index、opacity、transformなどが作るスタッキングコンテキストを確認します。
最短で原因を見つけるチェックリスト
topまたはinset-block-startを指定したか- 祖先要素に
overflow指定がないか - 親要素に十分な高さがあるか
- sticky要素がスクロール領域より高くないか
- Flexbox・Gridでstretchされていないか
- 実際にスクロールしている要素はどれか
- 固定ヘッダーやz-indexの裏に隠れていないか
まとめ
position: stickyが効かないときは、プロパティ単体ではなくスクロールコンテナと親子関係を確認するのが近道です。まずtop、次に祖先のoverflow、親の高さ、stretch、重なり順の順で調べると、多くの問題を短時間で切り分けられます。