CSSで、中身が少なくてもフッターを画面の下に置く方法

​​CSS​CSS Grid,Flexbox,HTML,Playwright

中身が少ないページでフッターが画面の途中に浮いてしまうときは、body を「画面の高さ以上」の縦並びの flex にして、main に flex: 1 を付けます。main が余った高さを受け取って伸びるので、フッターは画面の下に来ます。中身が画面より多いときは、フッターは本文の後ろに続き、本文に重なりません。

補足

検証環境は Ubuntu 24.04、Node.js 22.23.4、Playwright 1.63.0、Chromium 153 です。HTML と CSS のページを Playwright で開き、主に幅390px・高さ700pxの画面で、getBoundingClientRect() で要素の位置を測りました。Firefox と Safari、アドレスバーが伸び縮みするスマートフォンの実機では試していません。

上下に2つの画面が並んでいる。上の「何もしないとき」は、青い「サイト名」のヘッダー、本文の段落1つ、黒いフッターが続き、フッターの下に白い空きが大きく残っている。下の「この記事の CSS を使ったとき」は、同じ中身で、黒いフッターが画面のいちばん下に付いている
同じ中身のページを、高さ400pxの画面で比べた様子。上が何もしないとき、下がこの記事の CSS を使ったとき

コード全体

HTML は、body の直下に header、main、footer を並べます。

html
<body>
  <header>サイト名</header>
  <main>
    <p>本文の段落 1。中身が少ないページでも、フッターが画面の下に来るかを確かめます。</p>
  </main>
  <footer>フッター</footer>
</body>

CSS は次のとおりです。色や余白などの見た目の CSS は、配置に関わらないので省いています。下で示す高さの数値は、省いた見た目の CSS(ヘッダーなどの padding や行の高さ)も入れたページで測ったものです。

css
body {
  margin: 0;
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}

main {
  flex: 1;
}

display: flex と flex-direction: column で、body の中の3つの要素を縦に並べます。min-height で、body の高さを画面の高さ以上にします。

main の flex: 1 は、flex-grow: 1 を含む一括指定です。flex-grow は、並べた要素の合計より入れ物が大きいときに、余った長さをどれだけ受け取るかを決めます。header と footer は初期値の0のままなので、余った高さはすべて main に入ります。

画面の大きさを幅390px・高さ700pxにして、段落が1つのページで測りました。

要素 上端 下端 高さ
header 0px 51.2px 51.2px
main 51.2px 648.8px 597.6px
footer 648.8px 700px 51.2px

footer の下端が、画面の高さと同じ700pxになりました。ページ全体の高さも700pxで、スクロールは出ません。画面の高さを500pxと900pxに変えても、footer の下端はそれぞれ500pxと900pxでした。main を flex-grow: 1 だけにしても、位置は同じでした。

段落を20個にすると、ページ全体の高さは1542pxになり、footer は最後の段落の後ろ(上端1490.7px)に来ました。いちばん下までスクロールしたとき、最後の段落の下端は画面の616.7px、footer の上端は648.7pxで、段落はフッターに隠れていません。

ボタンを押して段落を3つずつ足し、ページの高さの変わり方を見たのが、次の画像です。

上の帯に「クリック 0回目/3回」と「ページの高さ 480px」が出ている。マウスポインターが「段落を足す」ボタンへ動いて押すと、「クリック 1回目/3回」がピンクの地になり、段落が3つ増える。1回目は黒いフッターが画面の下に付いたままで、ページの高さは480pxのまま。2回目と3回目は、押すたびに「ページの高さ」が653px、864pxに変わってピンクの地になり、フッターは画面の外へ押し出される。最後にページを下までスクロールすると、最後の段落の後ろにフッターが出てくる
高さ480pxの画面で、段落を足していったときの動き。中身が画面に収まるうちはフッターが画面の下にあり、収まらなくなると本文の後ろへ押し下げられる。上の帯とカーソルは GIF 用に重ねたもの

min-height と 100dvh を2行に分けて書く理由

body の高さは、min-height で「最低でも画面の高さ」にします。段落20個のページで min-height を height に書きかえると、フッターの位置は変わりませんでしたが、body の高さは700pxで止まりました。700pxより下の main と、footer(1490.7px〜1541.9px)は、body の外へはみ出していました。中身に合わせて body が伸びるのは、min-height のほうです。

dvh は、スマートフォンのアドレスバーなどが出たり引っ込んだりするのに合わせて変わる単位です(長さの単位の説明(ビューポートに基づく単位))。1行目の 100vh は、dvh が分からないブラウザのために置いています。CSS のエラー処理の説明にあるとおり、ブラウザは分からない値の宣言を無視するので、その場合は1行目が残ります。2行目を存在しない単位の 100xvh に書きかえて試すと、body の min-height は1行目の値の700pxになり、footer の下端も700pxでした。

MDN の互換性データ(@mdn/browser-compat-data 8.1.8)では、dvh などの単位は Chrome 108、Firefox 101、Safari 15.4(iOS の Safari も 15.4)から使えます。

body の margin を 0 にする

margin: 0 を消すと、中身が画面に収まるページでも、スクロールが出ます。

HTML の仕様の、描画の章にある「The page」の項のとおり、body には既定で8pxの margin が付きます。body の高さが画面と同じ700pxでも、上下に8pxずつ足されるので、ページ全体の高さは716pxになりました。footer の下端は708pxで、画面の下から8pxはみ出し、16pxだけスクロールできる状態でした。

footer の margin-top: auto でも、フッターは下に来る

main に flex: 1 を付ける代わりに、footer に margin-top: auto を付けても、フッターは画面の下に来ます。flex の中では、auto の margin が余った長さを受け取るためです(flexbox の配置の説明の、auto マージンの節)。

css
footer {
  margin-top: auto;
}

段落が1つのページで、footer の位置は648.8px〜700pxで、main { flex: 1 } と同じでした。違うのは main の高さです。flex: 1 では main が597.6pxまで伸びましたが、margin-top: auto では main は中身の高さの102.4pxのままで、余りは main と footer の間のすき間になりました。

grid で書くときは、行の数を子要素に合わせる

grid でも同じ配置にできます。grid-template-rows で、auto、1fr、auto の3行を作ります。1fr の行が、残りの高さを受け取ります。

css
body {
  margin: 0;
  min-height: 100vh;
  min-height: 100dvh;
  display: grid;
  grid-template-rows: auto 1fr auto;
}

段落が1つのときも20個のときも、header・main・footer の位置は、flex で書いたときと同じでした。

ただし、grid の行は、子要素に上から順に割り当てられます。header と main の間に nav を足すと、2行目の 1fr を nav が受け取り、nav の高さが495.3pxになりました。main は546.4pxから始まり、メニューと本文の間に大きなすき間が空きます。フッターの下端は700pxのままなので、フッターだけを見ていると気づきにくい崩れ方です。

書き方 nav の高さ main の上端〜下端
grid(auto 1fr auto) 495.3px 546.4px〜648.8px
grid(auto auto 1fr auto) 43.2px 94.4px〜648.8px
flex(main { flex: 1 }) 43.2px 94.4px〜648.8px

grid-template-rows を auto auto 1fr auto にして、行を子要素の数に合わせると、main が伸びるようになりました。flex の書き方は、flex: 1 を main に付けているので、nav を足しても書きかえずに main が伸びました。

ページ全体を div で包んでいるときは、その div に書く

header から footer までを <div class="page"> のような要素で包んでいるページでは、body に書いても効きません。上の CSS を body に書いたまま包むと、body の高さは700pxになりましたが、footer の下端は204.8pxで、本文のすぐ下に来ました。body の中で並ぶのは div.page 1つだけで、main は flex で並べられる要素ではなくなるためです。

このときは、包んでいる要素に同じことを書きます。

css
body {
  margin: 0;
}

.page {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}

.page > main {
  flex: 1;
}

この CSS では、段落が1つのとき footer は648.8px〜700pxに来ました。段落が20個のときは、body に書いたときと同じく、最後の段落の後ろに続きました。

position: fixed で下に置くと、本文に重なる

footer を position: fixed で画面の下に固定しても、フッターは画面の下に来ます。

css
body {
  margin: 0;
}

footer {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
}

ただし、これはスクロールしてもずっと画面の下に付いたままのフッターで、本文の上に重なります。

段落20個のページでいちばん下までスクロールすると、最後の段落の下端は画面の667.7px、footer の上端は648.8pxで、段落の下の18.9pxがフッターに隠れていました。中身が少ないときだけ下に置き、多いときは本文の後ろに続けたいなら、ここまでの flex か grid の書き方を使います。

​Posted by 86