CSSで、要素を画面の真ん中に置く方法
要素を画面の縦と横の真ん中に置くには、親の要素を display: grid にして place-items: center を付け、min-height: 100dvh で親を画面の高さいっぱいに広げます。スクロールしても同じ場所に出したいダイアログなどは、position: fixed で画面に固定してから真ん中に置きます。画面全体をおおう層の中でフレックスボックスを使って真ん中に置くと、中身が画面より高くなったときに上の端が切れることがあるので、その直し方も紹介します。
検証環境は Ubuntu 24.04、Node.js 22.23.4、Playwright 1.63.0、Chromium 153 です。HTML と CSS だけのページを Playwright で開き、画面の大きさを変えながら、getBoundingClientRect() で要素の位置を調べました。Firefox と Safari、スマートフォンの実機は試していません。ヘッドレスの Chromium では、スクロールバーが画面の幅を取りませんでした。スクロールバーが幅を取る環境での位置は調べていません。

グリッドの place-items: center で真ん中に置く
HTML は、真ん中に置きたい要素を body の中に入れるだけです。
<body class="center-grid">
<div class="box">真ん中に置く要素</div>
</body>
/* ブラウザが body に付ける既定の余白(8px)を消す */
body {
margin: 0;
}
/* グリッドで、中の要素を画面の真ん中に置く */
.center-grid {
display: grid;
place-items: center; /* 縦と横の両方を中央にそろえる */
min-height: 100vh; /* dvh を使えないブラウザ向け */
min-height: 100dvh; /* 画面の高さいっぱいに広げる */
}
place-items は、align-items(縦)と justify-items(横)をまとめて書くプロパティです。値を1つだけ書くと、両方に同じ値が入ります。Chromium で計算値を調べると、align-items と justify-items はどちらも center でした。
幅 300px・高さ 200px の箱を置き、画面の大きさを変えて位置を調べました。
| 画面の大きさ | 箱の左上の位置 | 左右の余白 | 上下の余白 |
|---|---|---|---|
| 800×600 | (250, 200) | 250px ずつ | 200px ずつ |
| 375×667 | (37.5, 233.5) | 37.5px ずつ | 233.5px ずつ |
どちらの大きさでも、左右と上下の余白は同じでした。上の GIF のように、画面の大きさを変えている途中でも、箱は真ん中に残ります。
親の高さを画面に合わせないと、縦は真ん中にならない
display: grid と place-items: center だけを書き、min-height を書かずに試すと、箱は横だけ真ん中になり、縦は画面の上の端(0px)にくっつきました。body の高さが、中身の箱と同じ 200px にしかならなかったからです。縦の真ん中にそろえるには、親の高さが画面の高さまで広がっている必要があります。
min-height の代わりに height: 100% を書いた場合も、html に高さを書いていなければ、body の高さは 200px のままでした。height のパーセントの値は、包含ブロック(ここでは html)の高さをもとに決まるので、html と body の両方に height: 100% を書く必要があります。両方に書いて試すと、body の高さは 600px になり、箱は (250, 200) に来ました。ビューポートの高さを直接使える 100vh や 100dvh のほうが、書く場所が1つで済みます。
body { margin: 0; } を書くのにも理由があります。Chromium で調べると、ブラウザが付ける body の既定の余白は 8px でした。この余白を残したまま min-height: 100vh にすると、画面の高さ 600px に対して、文書の高さが 616px になりました。上下の余白の分だけ縦にスクロールできるようになり、箱の位置も 8px 下にずれます。
vh と dvh の違い
<length> の MDN の説明では、vh は大ビューポートの高さ(lvh)と同じ、とされています。スマートフォンのブラウザでアドレスバーが隠れたときの、いちばん大きい画面の高さです。アドレスバーが出ているときは、100vh で大きさを決めた要素の一部が、アドレスバーなどに隠れることがあると書かれています。
dvh は動的ビューポートの単位で、アドレスバーが出たり隠れたりするのに合わせて大きさが変わります。そのため、上のコードでは min-height: 100dvh を使い、その前の行に、dvh を使えないブラウザ向けの min-height: 100vh を置いています。CSS のエラー処理についての MDN の説明にあるとおり、ブラウザは解釈できない宣言を無視します。dvh を知らないブラウザは2つ目の宣言を読み飛ばし、1つ目の 100vh を使います。
デスクトップの Chromium で画面の高さを 600px にして計算値を調べたところ、100vh、100dvh、100svh はどれも 600px でした。アドレスバーが出たり隠れたりするスマートフォンのブラウザでの違いは、実機がないため試していません。
フレックスボックスで書くときは、2つのプロパティを中央にする
フレックスボックスでも同じように置けます。
/* フレックスボックスで、中の要素を画面の真ん中に置く */
.center-flex {
display: flex;
justify-content: center; /* 横(主軸)を中央に */
align-items: center; /* 縦(交差軸)を中央に */
min-height: 100vh;
min-height: 100dvh;
}
フレックスボックスでは、並べる向き(主軸)の配置を justify-content で、それと直角の向き(交差軸)の配置を align-items で決めます。flex-direction を書かなければ、横に並べる row になります(Chromium で調べた計算値も row でした)。そのため、justify-content が横、align-items が縦になります。
同じ箱で試すと、800×600 でも 375×667 でも、グリッドのときと同じ位置に来ました。
スクロールしても動かない要素は、position: fixed で置く
ダイアログやお知らせのように、ページをスクロールしても画面の真ん中に出し続けたい要素は、position: fixed で画面に固定します。
<div class="box fixed-center">真ん中に固定する要素</div>
/* スクロールしても動かない、画面の真ん中の要素 */
.fixed-center {
position: fixed;
top: 50%;
left: 50%;
translate: -50% -50%; /* 自分の幅と高さの半分だけ、左上へ戻す */
}
top: 50% と left: 50% で、要素の左上の角を画面の真ん中に置きます。このままでは、要素は真ん中から右下にずれます。800×600 の画面で translate を書かずに試すと、幅 300px・高さ 200px の箱の左上の角は (400, 300) で、画面の真ん中から右下へ広がっていました。
そこで、translate で要素を左上へ戻します。translate() の MDN の説明にあるとおり、パーセントの値は、横は要素自身の幅、縦は要素自身の高さをもとにします。-50% -50% なら、要素の大きさが分からなくても、ちょうど半分だけ戻せます。
高さ 3000px のページで試すと、箱の左上の位置は 800×600 で (250, 200)、375×667 で (37.5, 233.5) でした。1200px 下までスクロールしても、位置は変わりませんでした。
中身が画面より高いと、上の端が切れることがある
画面全体をおおう層(モーダルの背景など)の真ん中にダイアログを置くときは、層を position: fixed と inset: 0 で画面いっぱいに広げ、その中で中央にそろえます。
/* 画面全体をおおう層(モーダルの背景など)の真ん中に置く */
.overlay {
position: fixed;
inset: 0; /* top・right・bottom・left をすべて 0 に */
display: flex;
justify-content: center;
align-items: center;
overflow: auto; /* 中身が画面より高いときはスクロールさせる */
}
中身が小さいうちは、ここまでと同じく真ん中に来ます。困るのは、中身が画面より高くなったときです。高さ 600px の画面で、高さ 900px の箱をこの層の中に置くと、箱の上の端は -150px になりました。箱は真ん中を基準に、上と下へ 150px ずつはみ出します。下にはみ出した分はスクロールで見られますが、いちばん上までスクロールしても、箱の上の端は -150px のままでした。上の 150px は、どうやっても見られません。

これを避けるには、center の前に safe を付けます。
/* 中身が画面より高くなっても、上の端が切れない */
.overlay-safe {
align-items: safe center;
}
align-items の MDN の説明では、safe を付けると、要素がはみ出して見えない部分が出るときには、start(上の端)にそろえたかのように置かれる、とされています。.overlay と .overlay-safe の両方を付けて同じように試すと、箱の上の端は 0px になり、300px 下までスクロールして、下の端まで見られました。中身が高さ 200px の箱のときは、safe を付けても真ん中(上の端が 200px)に来ました。
高さ 600px の画面に高さ 900px の箱を置いたときの結果を、書き方ごとに並べます。箱の上の端は、いちばん上までスクロールしたときの位置です。
| 書き方 | 箱の上の端 | 切れるかどうか |
|---|---|---|
.overlay(フレックスボックスと align-items: center) |
-150px | 切れる |
.overlay に .overlay-safe(align-items: safe center)を足す |
0px | 切れない |
.overlay で align-items と justify-content を書かず、中の箱に margin: auto を付ける |
0px | 切れない |
層を display: grid と place-items: center で書く |
0px | 切れない(グリッドの行が箱に合わせて 900px に伸びる。grid-template-rows の計算値は 900px) |
.center-grid、.center-flex(min-height で書く) |
0px | 切れない(body が箱に合わせて 900px まで伸びる) |
body をフレックスボックスにして中央にそろえ、height: 100dvh を書く |
-150px | 切れる |
切れたのは、フレックスボックスで、親の高さを画面の高さに決めたときでした。親やグリッドの行が中身に合わせて伸びる書き方や、safe か margin: auto を使う書き方では、上の端は切れませんでした。





