垂直方向のマージンにはmargin-topを優先的に使う理由

margin-bottomではなくmargin-topを使う派である旨をツイートしたら理由を尋ねられたので、それに対する回答です。大きくは次の3つです。

  1. 末尾の要素の存在が任意である場合が多いため
  2. Stackレイアウトとの取り合わせやすさのため
  3. 隣接セレクターを使った場合分けができるようにするため

1. 末尾の要素の存在が任意である場合が多いため

コンポーネントやページなど、ある特定の区間内の末尾の要素は存在したりしなかったりすることが多い。重要度の高い情報を上から順に並べるとき、末尾の要素は相対的に重要度の低い情報を扱うことになり、ビューとしてはその有無が任意になったりする。

たとえばページタイトルは必須だけどページの説明文は任意になる場合。

<h1>{{title}}</h1>

{{#if description}}
<p class="mt-3">{{description}}</p>
{{/if}}

あるいはカードパターンの下部にあるリンクやボタンが任意になる場合。

<div class="card">
<p>{{title}}</p>
<p class="mt-2">{{description}}</p>

{{#if link}}
<p class="mt-3"><a href="{{link}}">さらに詳しく</a></p>
{{/if}}
</div>

カードパターンにはタグが含まれていることもあるかもしれない。

<div class="card">
<p>{{title}}</p>
<p class="mt-2">{{description}}</p>

{{#if tags}}
<ul class="mt-3">
{{#each tag in tags}}
<li>{{this}}</li>
{{/each}}
</ul>
{{/if}}
</div>

こういうとき、末尾の要素にはmargin-bottomがついていない方が都合がいい。

カードパターンなどでボックス状の見た目になっているときには、paddingborderを使ってマージンが相殺されない状態になっている。するとボックスの下部には、親のpadding-bottomと末尾の要素のmargin-bottomが足し合わされた量の余白ができてしまって不自然な見た目になる。

この問題を回避するために、要素内の末尾の要素からはmargin-bottomを取り除くやり方がある。

.card > :last-child {
margin-bottom: 0;
}

しかしそもそも最初からmargin-topを使うようにしておけば、このような規則を追加する必要がない。

もし先頭の要素の存在が任意である場合には、逆にmargin-bottomを使った方がよくなる。ただ末尾の要素が任意である場合に比べて、先頭の要素が任意である場合の方が経験則上は格段に少ない。僕は実際にそのような場面に出くわしたときのみ例外的にmargin-bottomを使うこともあるが、そうでない限りは基本的にmargin-topを使っている。

すべてをmargin-topに統一したければ、マークアップ側の工夫でmargin-bottomを使わないこともできる。

{{#if subtitle}}
<p>{{subtitle}}</p>
<div class="mt-2"></div>
{{/if}}

<h1>{{title}}</h1>

2. Stackレイアウトとの取り合わせやすさのため

StackレイアウトというのはEvery Layoutで紹介されているCSSのパターンのことで、具体的には次のような実装になる。

.stack > * + * {
margin-top: 1.5rem;
}
<div class="stack">
<p>{{title}}</p>
<p>{{description}}</p>
<p><a href="{{link}}">さらに詳しく</a></p>
</div>

こうすることで自動的に子要素の要素間にのみ同じ大きさの余白ができるようになる。すると末尾の要素の存在が任意になっていたとしても余白は自動的に調整されるので、余白の大きさが等しい場合にはStackレイアウトを採用すると先述の問題を回避できる。

あるいは余白の大きさが違ったとしても、都度隣接セレクターを使うことで、特定の要素同士が並んだ場合にのみ余白を設定する実装ができる。

いずれにしてもStackレイアウトを採用する場合にはmargin-topを必ず使うことになるので、Stackレイアウトを常用する僕としては、Stackレイアウトを使わない場面でも一貫性の意味でmargin-topに統一しておく方が混乱が少なくて済む。

また複数のStackレイアウトが横並びになった場合に、特定の要素の配置を下部に合わせる用法もある。

末尾の要素の前にmargin-bottom: autoを指定すると、要素の高さに関わらず次に来る要素が下部に揃うようになる。

.stack {
display: flex;
flex-direction: column;
height: 100%;
}

.stack > * + * {
margin-top: 1.5rem;
}

.stack > :nth-child(2) {
margin-bottom: auto;
}

例では2番目の要素にmargin-bottom: autoが指定されているが、同時に3番目の要素のmargin-topが引き続き生きていることが重要になる。2番目の要素のmargin-bottom: autoによって、余っている領域いっぱいまで3番目の要素が下部へ移動すると同時に、3番目の要素自身のmargin-topによって、余っている領域の大きさに関わらずつねに空く余白の最低限の大きさが設定される。つまりこのレイアウトにはmargin-topmargin-bottomの両方が必要になる。

margin-bottomはこのパターンを実現するために温存している。

3. 隣接セレクターを使った場合分けができるようにするため

コンポーネント等の組み合わせによって垂直方向の余白の大きさに違いがある場合、隣接セレクターを活用すると余白の大きさを場合分けできるようになる。「デフォルトでは段落の上に1.5remの余白ができて、段落が見出しと隣接する場合には少し狭めの1remにしたい」ということであれば次のようになる。

p {
margin-top: 1.5rem;
}

h2 + p {
margin-top: 1rem;
}

margin-bottomでは同様のやり方をするのは難しい。Aの隣のBに対して宣言するという表現になる以上、AとBとの間の余白を設定するためにはmargin-topを使うしかない。隣接セレクターを使ってmargin-bottomを調整できると役に立つ場面もあるかもしれないが、まれだろう。