Skip to main content

ボックスモデル — width が何を指すかで崩れる

レイアウトが 1 ピクセル単位で合わない、横に 4 つ並べたカードが折り返してしまう。こうした崩れの多くは、width に指定した値が実際には何の幅を指しているかを取り違えたことから起きます。

CSS はすべての要素を長方形の箱として扱います。この箱の構造を決めているのがボックスモデルです。

この章で学ぶこと

  • ボックスモデルを構成する 4 つの領域と、それぞれの役割
  • width が既定でコンテンツ領域だけを指すこと、box-sizing でその解釈を変えられること
  • 背景がどこまで描かれ、どこから描かれないか
  • 上下のマージンが相殺される条件と、相殺を止める方法
前提知識

CSS で widthpadding を書いた経験があれば読めます。

4 つの領域

要素の箱は、内側から外側へ 4 つの領域が入れ子になった構造をしています。

順序は内側から コンテンツ → パディング → ボーダー → マージン です。この順序は覚える価値があります。パディングが境界線の内側にあり、マージンが外側にあるという関係が、後述する背景の描画範囲とマージンの相殺の両方を説明するからです。

領域役割背景が描かれるか他の要素との関係
コンテンツテキストや画像そのものが入る描かれる
パディングコンテンツと境界線の間の余白描かれる箱の内側なので他要素に影響しない
ボーダー箱の境界線既定では描かれる箱の外周を決める
マージン箱と箱の間の余白描かれない隣の要素を押しのける

パディングとマージンはどちらも「余白」ですが、性質が違います。パディングは箱の内側を広げるので、背景色や背景画像がその分だけ広がります。マージンは箱の外側の空間なので、背景は一切かかりません。

4 辺は個別に指定できる

paddingmarginborder も、上下左右をまとめて書くショートハンドと、辺ごとに書くロングハンドの両方があります。

/* ロングハンド ー 辺ごとに指定する */
.card {
padding-top: 8px;
padding-right: 16px;
padding-bottom: 8px;
padding-left: 16px;
}

/* ショートハンド ー 値の個数で解釈が変わる */
.card {
padding: 8px; /* 上下左右すべて 8px */
padding: 8px 16px; /* 上下 8px / 左右 16px */
padding: 8px 16px 24px; /* 上 8px / 左右 16px / 下 24px */
padding: 8px 16px 24px 32px; /* 上 → 右 → 下 → 左 (時計回り) */
}

4 値のショートハンドは時計回りに上・右・下・左の順です。3 値のときは左が省略されて右の値が使われ、2 値のときは上下と左右のペアになります。

width が指すもの

ここがボックスモデルで最も誤解されやすい点です。

**既定では、width はコンテンツ領域の幅だけを指します。**パディングとボーダーはその外側に足されます。

.card {
width: 350px;
padding: 25px;
border: 5px solid;
}

この要素が画面上で占める横幅は 350px ではありません。

350px (content) + 25px×2 (padding) + 5px×2 (border) = 410px

コンテンツの幅を指定したつもりが、実際には 60px はみ出した箱ができます。3 カラムのグリッドを組んで width: 33.3% と書いたのにパディングのぶんだけ折り返してしまう、という崩れはこれが原因です。

この既定の挙動を 標準ボックスモデル (box-sizing: content-box) と呼びます。

box-sizing: border-box で解釈を変える

box-sizing プロパティを border-box にすると、width の意味が変わります。

.card {
box-sizing: border-box;
width: 350px;
padding: 25px;
border: 5px solid;
}

今度は箱全体が 350px に収まります。パディングとボーダーは内側に食い込み、コンテンツ領域はその残りになります。

350px (箱全体) − 25px×2 (padding) − 5px×2 (border) = 290px (content)
content-box (既定)border-box
width が指すものコンテンツ領域だけコンテンツ + パディング + ボーダー
パディングを足すと箱全体が大きくなるコンテンツ領域が狭くなる
箱全体の幅指定値より大きい指定値と一致する

レイアウトを組む立場では border-box のほうが直感に合います。「この箱は 350px」と指定したら 350px を占めてほしいからです。そのため、プロジェクト全体に適用するリセットが定石になっています。

*,
*::before,
*::after {
box-sizing: border-box;
}

疑似要素も対象に含めるのは、::before::after にパディングやボーダーを付けたときに同じ計算規則で揃えるためです。Tailwind CSS ガイドで扱うようなユーティリティ系のフレームワークは、このリセットを既定で組み込んでいます。そのため Tailwind を使っていると border-box が当たり前に感じられますが、素の CSS では content-box が既定であることは意識しておいてください。

box-sizing はマージンを含みません

border-box にしても、マージンは width の外側のままです。「箱全体」に含まれるのはボーダーまでで、マージンは箱と箱の間の空間だからです。横並びのレイアウトで幅が合わないときは、まずマージンを疑ってください。

背景はどこまで描かれるか

背景色や背景画像の描画範囲は background-clip プロパティが決めます。初期値は border-box です。

つまり既定では、背景はコンテンツ領域だけでなく パディング領域とボーダー領域にも広がります。マージン領域には広がりません。

.card {
background-color: #eef;
padding: 24px; /* この 24px にも背景色が乗る */
border: 8px dashed; /* 破線の隙間からも背景色が見える */
margin: 24px; /* ここには乗らない */
}

ボーダーが実線なら気づきませんが、dasheddotted、あるいは半透明のボーダーにすると、その隙間から背景が見えます。これは背景がボーダーの下まで描かれている証拠です。

background-clip の値を変えると範囲を狭められます。

背景が描かれる範囲
border-box (初期値)ボーダーの外縁まで
padding-boxパディングの外縁まで。ボーダーの下には描かれない
content-boxコンテンツ領域だけ
text文字の形に切り抜かれる

「パディングで余白を作ったら背景色もそこまで広がる」という挙動は、カードやボタンの設計で日常的に使われています。ボタンの当たり判定と見た目の面積をパディングで広げても、背景が付いてくるので違和感が出ません。

マージンの相殺

上下に並んだ 2 つの要素に、それぞれ margin-bottom: 20pxmargin-top: 30px を指定したとします。要素の間隔は 50px になりそうですが、実際には 30px になります。

これが マージンの相殺 (margin collapsing) です。縦方向のマージンだけで起き、左右のマージンでは起きません。

相殺が起きる 3 つの状況

1. 隣り合う兄弟要素の間

<p style="margin-bottom: 20px">前の段落</p>
<p style="margin-top: 30px">次の段落</p>

隙間は 20px + 30px = 50px ではなく、大きいほうの 30px になります。

2. 親要素と最初または最後の子要素の間

親に border-top もパディングもインラインコンテンツも無いとき、親の margin-top は最初の子の margin-top と相殺します。下端も同様で、親に height / min-height / border-bottom / padding-bottom が無ければ、親の margin-bottom は最後の子の margin-bottom と相殺します。

子に付けたはずのマージンが親の外側に飛び出してしまう、という現象はこれです。

3. 中身が空のブロック

ボーダー・パディング・インラインコンテンツ・heightmin-height のどれも無いブロックは、自分自身の上マージンと下マージンが相殺します。

符号によって結果が変わる

組み合わせ相殺後の値
どちらも正大きいほう
どちらも負小さいほう (最も負の値)
正と負が混在最大の正の値と最も負の値の

たとえば margin-bottom: 30pxmargin-top: -10px なら、30 + (−10) = 20px になります。

相殺を止める方法

意図しない相殺を防ぐには、マージン同士を何かで隔てます。

  • 親に padding-top / padding-bottom を 1px でも付ける
  • 親に border-top / border-bottom を付ける
  • 親を display: flex または display: grid にする
  • 親に新しいブロック整形コンテキストを作る (overflow: auto など)

Flexbox と Grid のコンテナ内でマージンが相殺しないのは、この 2 つが独自のレイアウト規則を持つためです。近年のレイアウトで相殺に悩まされる場面が減ったのは、この 2 つが主流になり、間隔の指定に gap を使うようになったからです。実際の組み方は Flexbox レイアウトGrid レイアウトで扱っています。

フロート要素と絶対配置の要素のマージンは、そもそも相殺しません。

よくある誤解

paddingborderwidth に必ず含まれる」 — 含まれるかどうかは box-sizing 次第です。既定の content-box では含まれず、外側に足されます。border-box を指定して初めて含まれるようになります。多くのプロジェクトがリセットで border-box を当てているため、「含まれるのが普通」と感じることはありますが、それは CSS の既定ではなくプロジェクトの設定です。

padding は上下左右が連動していて個別に設定できない」 — 個別に設定できます。padding-top などのロングハンドでも、ショートハンドに値を 2 個から 4 個並べる書き方でも指定できます。

「背景色はコンテンツ領域にしか付かない」 — 既定では background-clip: border-box なので、パディング領域とボーダー領域にも広がります。付かないのはマージン領域だけです。

「マージンは常に足し算される」 — 縦方向は相殺します。左右方向は相殺しないので足し算になります。方向によって挙動が違う点が混乱の元になります。

box-sizing: border-box にすればマージンも含まれる」 — 含まれません。border-box が含めるのはボーダーまでです。

確認問題

問 1. 次の要素が画面上で占める横幅は何ピクセルですか。
.box {
width: 200px;
padding: 10px 20px;
border: 2px solid;
margin: 16px;
}

答え: 244px

box-sizing の指定が無いので既定の content-box です。width はコンテンツ領域だけを指します。

200px (content) + 20px×2 (左右 padding) + 2px×2 (border) = 244px

マージンは箱の外側の空間なので、箱そのものの幅には含めません。ただし隣の要素との間隔には効くので、レイアウト上の占有幅を考えるときは別途 32px を見込む必要があります。

上下方向は 10px×2 + 2px×2 = 24px がコンテンツの高さに足されます。

問 2. 上の .boxbox-sizing: border-box を足すと、コンテンツ領域の幅は何ピクセルになりますか。

答え: 156px

border-box では width が箱全体 (コンテンツ + パディング + ボーダー) を指すので、コンテンツはその残りになります。

200px − 20px×2 (左右 padding) − 2px×2 (border) = 156px

箱全体は指定どおり 200px に収まります。

問 3. 次の 2 つの段落の間隔は何ピクセルになりますか。
.first { margin-bottom: 24px; }
.second { margin-top: 16px; }
<p class="first">前の段落</p>
<p class="second">次の段落</p>

答え: 24px

隣り合う兄弟要素の縦マージンは相殺します。どちらも正の値なので、大きいほうの 24px が採用されます。40px にはなりません。

親を display: flex にして flex-direction: column を指定すると相殺が起きなくなり、40px になります。

問 4. 破線のボーダーを付けた要素で、破線の隙間から背景色が見えます。これは仕様どおりですか。

答え: 仕様どおりです。

background-clip の初期値は border-box で、背景はボーダーの外縁まで描かれます。ボーダーは背景の上に重なって描画されるため、dasheddotted のように隙間があるボーダーでは、その隙間から下の背景が見えます。

隙間から背景を見せたくない場合は background-clip: padding-box を指定します。背景の描画がパディングの外縁で止まり、ボーダーの下には回り込まなくなります。

まとめ

  • 要素の箱は内側から コンテンツ → パディング → ボーダー → マージン の 4 領域でできています
  • width は既定 (content-box) ではコンテンツ領域だけを指し、パディングとボーダーは外側に足されます
  • box-sizing: border-box にすると width がボーダーまでを含むようになります。マージンは含みません
  • 背景は既定でパディング領域とボーダー領域まで描かれます (background-clip: border-box)。マージンには描かれません
  • 縦方向のマージンは相殺します。隣接する兄弟・親と最初または最後の子・空のブロックの 3 つの状況で起きます
  • パディングもマージンも 4 辺を個別に指定できます
関連リファレンス

次に読む