余白の設計を考える①

marginの相殺

見た目を再現することだけを考えるならばmargin-topとmargin-bottomのどちらでも実現可能。しかし開発効率や保守性を考えた場合、どちらかに統一するのが一般的である。その理由は、「marginの相殺」をできるだけ避けるためである。
見出し側にmargin-bottom: 50px、テキスト側にmargin-top: 20pxが設定されていた場合、足して70pxになるのではなく、大きい方の値である50pxだけが適用されるのがmarginの相殺である。この仕様をうまく利用して設計することも可能ではあるが、かなり難易度が高いので、一般的にはできるだけmarginの相殺が発生しないように上下どちらか片方だけに付けるようにするのがベストプラクティスとされている。

margin-topかmargin-bottomか

近年はmargin-topで統一するように変えている。その理由は「要素同士の余白は新たに要素が下に追加された時に発生するものであり、その余白は追加された方の要素に由来するのだからmargin-topで付けたほうが自然ではないか?」と考えるようになったためである。
どちらが正しいとは言えないが、後から追加される要素のmargin-topに余白を付けるルールにしておくと、不要な要素間の余白調整があまり必要にならないことが多くなるだろう。
また、margin-bottomで統一した場合、必ず一番最後の要素の下マージンを何らかの方法で処理しなければならない。同じことはmargin-topで統一した場合でも発生するが、末尾の要素は常に不確定であるのに対し先頭の要素はなくなることはない。不確定要素が少ない分だけ考えることも少なくて済むため、僅かな差ではあるが、margin-topのほうがシンプルに作れるように思われる。

セクションの余白をmarginで設計する場合

デザイン的に大見出しの上に大きく余白を取る形でセクション間の余白を確保するようなケースでは、marginで余白を取っても特に問題はなさそう。各セクションともに上下に100pxずつの余白を付けたとしても、margin同士であれば隣接した場合には相殺されて100pxだけ設定されるので、余白の片側処理問題も考える必要がない。
margin相殺は不用意に発生してしまうことは避けるべきだが、このケースのように意図的に相殺を利用して設計することはその限りではない。ただ、背景色を伴うセクションの場合は、どうしてもpaddingで上下の余白を確保する必要があるため、同じレベルのセクションで余白サイズも同一であるにも関わらず、背景付きのセクションと背景なしのセクションで別ブロックにしなければならないのが難点である。

<section class="section">~</section>
<section class="section-bg">~</section>
/* 背景色なし */
.section {
 margin-top: 100px;
 margin-bottom: 100px;
}
/* 背景色あり */
.section-bg {
 margin-left: calc(50% - 50vw);
 margin-right: calc(50% - 50vw);
 padding-left: calc(50vw - 50%);
 padding-right: calc(50vw - 50%);
 padding-top: 100px;
 padding-bottom: 100px;
 background-color: #edf3fa;
}

セクションの余白をpaddingで設計する場合

上下余白をpaddingで指定することで、同一コンポーネントとして運用することが可能となるので、その点がpaddingで余白を取ることのメリットである。
ただし、marginと違ってpaddingは上下の相殺が効かないので、背景色なしのセクションが連続する場合は余白量が2倍になってしまう。この点に関しては隣接セレクタを活用して、背景色なしのセクションコンポーネントが連続した場合だけpadding-topを0にする指定を入れておくことで解決できる。
近年のデザイントレンドとしてはセクションごとに交互、あるいは任意の複数のセクションに対して背景色を付けることが一般的になっている。セクション間の余白に関しては、margin/paddingともにそれぞれメリットとデメリットがあるが、背景色が付くことが例外ではないことを考慮すると、セクション余白に関してはpaddingで指定する方に軍配が上がると言って良いだろう。

/* セクション間隔(共通) */
.section {
 padding-top: 100px;
 padding-bottom: 100px;
}
/* 背景なしのセクションが隣接した場合の調整 */
.section:not(.section--bg) + .section:not(.section--bg) {
 padding-top: 0;
}
/* 背景色付き */
.section--bg {
 margin-left: calc(50% - 50vw);
 margin-right: calc(50% - 50vw);
 padding-left: calc(50vw - 50%);
 padding-right: calc(50vw - 50%);
 background-color: #edf3fa;
}

セクション内の最後の要素のmargin-bottomを0に

コンテンツに対してmargin-bottomで統一して余白を付けている場合、セクション内の末尾の要素のmargin-bottomは必ず0としなければならない。「末尾の要素」は:last-child、「セクション直下の子要素」は子セレクタ(>)で指定できるので、上記のように指定しておくことでどんなものが来たとしても自動的に末尾の要素のmargin-bottomを0にしておくことができる。

.section p {
  margin-bottom: 20px;  /* 一律で下margin設定 */
  line-height: 2;
}
.section >:last-child {  /* セクション末尾の子要素の下marginを0にする */
  margin-bottom: 0;
}

セクション内の最初の要素のmargin-topを0に

セクション内の最初の要素のmargin-topも不要なはずなので、こちらは :first-childでmargin-topを0にしておくこともできる。ただし、margin-bottomで統一している場合には先頭の要素に上マージンが付くことはないので、こちらはmargin-topで統一しており、かつセクション先頭に配置された要素に万が一margin-topが付いていた場合に、それを無効化するための措置をとる。
(※複雑なレイアウトの場合、セクションの先頭に来る要素に敢えてmargin(ネガティブマージン含む)を取る必要が出てくることも考えられるので、先頭要素のmargin-topに関しては敢えて処理せず、必要な場合にのみユーティリティで打消しを入れる方法も考えられる)

.section >:first-child {  /* セクション先頭の子要素の上marginを0にする */
  margin-top: 0;
}

Block自体にはmarginを付けない

横並びのボタンのBlockに直接左右のmarginを20pxずつ設定した場合、横並びのボタンが中央配置なら良いが、左寄せ・右寄せの場合、ボタンの端が親Blockの端と揃わなくなってしまう。マークアップ面でも単にbutton要素を2つ並べるだけでは、スマホ表示で縦積みする時にどうすれば良いのか頭を悩ませてしまう(そもそも縦積みの場合に必要なmarginは左右ではなく上下)。
この問題の解決策は、2つのボタンを内包する親Blockを追加して、レイアウトは親要素に指定、ボタン同士の余白も親要素のElementとして指定することである。
Blockは他で使い回すことを前提とした、独立したコンポーネントである必要がある。余白は使いたい場所によって必要なサイズが異なることが多いため、様々な場所で使うことを前提とするBlockにはmarginは付けないのが鉄則である。

<div class="btns">
 <input type="reset" value="書き直す" class="btns__item btn btn--reverse">
 <input type="submit" value="確認する" class="btns__item btn">
</div>
.btns {
 display: flex;
 flex-direction: column;  /* SPでは縦並び */
}
.btns__item + .btns__item {  /* ボタンが2つ並ぶ場合だけボタン間に余白をつける */
 margin-top: 20px;
}
@media screen and (min-width: 768px) {
 .btns {
  flex-direction: row;  /* PCでは横並び */
 }
 .btns__item + .btns__item {  /* PCではボタン間の余白を左に変更 */
  margin-top: 0;
  margin-left: 40px;
 }
}
/* ボタン本体 */
.btn {
 /* margin: 0 20px; ←ボタンに直接marginはつけない */
}

Blockの余白は常に親のBlockから指定する

フォーム画面で横並びボタンBlockとフォームとの間の余白を取る場合、横並びボタンBlockを作るための親要素に直接margin-top: 60pxを指定するのはNGである。横並びボタンBlockの上の余白が常に60pxと決まっているわけではないためである。最小単位の汎用コンポーネントだけでなく、それらをラップしたレイアウトBlockであっても、それ自体が様々な場所で流用されるBlockのため、やはり同じようにBlock自体にmarginは付けないのが原則である。
この場合は、フォームとフォームの送信ボタンを内包するレイアウト用のBlockを追加し、そのElementとして横並びボタンに対しmargin-topを設定するようにする。
つまり、marginは常に親BlockのElementに対して設定する。こうすることで、汎用的なBlock自体はどこでもそのまま流用できる状態を保つことができる。
BEMではBlockを他のBlockのElementにする「Mix」の手法が許可されているので、入れ子が深くなりすぎることが懸念される場合にはMixの手法を活用するようにする。

<div class="blockA">
 <div class="blockB blockA__element">~</div>
</div>
<div class="form-layout">
 ~フォーム部分は略~
 <div class="form-layout__footer">
  <div class="btns btns--center">
   <input type="reset" value="書き直す" class="btns__item btn btn--reverse">
   <input type="submit" value="確認する" class="btns__item btn btn--large">
  </div>
 </div>
</div>
.form-layout__footer {
 margin-top: 60px;  /* 親BlockのElementにmarginをつける */
}
.btns {
 display: flex;
 flex-direction: column;
 /* margin-top: 60px; ←Blockには原則marginをつけない */
}
inserted by FC2 system