CSSのmarginで悩む人に試してほしい全称セレクタの活用方法

良いデザインにはマージン (余白) が適切に設定されているものです。

Webデザインの場合、マージンはCSSのmarginプロパティで設定しますが、「マージンの相殺 (margin collapse)」「入れ子になった要素への対処」「汎用性」などを考慮しなければなりません。

そこで今回は、長年の研究により編み出した、全称セレクタ (ユニバーサルセレクタ) を活用した、使い勝手の良い垂直マージンの設定方法をご紹介します。

まずは、おさらいです。

全称セレクタ (ユニバーサルセレクタ) とは?

「すべての要素」や「なんらかの要素」を表す「*」です。セレクタとしての詳細度は0で、あってないようなものなので、簡単に上書きできるという特徴があります。

次の場合、すべての要素の上下に2remのマージンが設定されます。

* {
  margin: 2rem 0;
}

次の場合、article要素内のすべての要素の上下に2remのマージンが設定されます。

article * {
  margin: 2rem 0;
}

次の場合、article要素のすべての子要素の上下に2remのマージンが設定されます。

article > * {
  margin: 2rem 0;
}

このように、全称セレクタは一括して何かを設定したい場合に有用です。

マージンの相殺 (margin collapse) とは?

余白を一定に保つための特殊な仕様です。

次のように、あるp要素の下側マージンに4rem、それに続くp要素の上側マージンに2remを設定した場合、この2つの要素間には合計値の6remではなく、一方の大きい数値である4remの余白が設定されます。

p {
  margin-bottom: 4rem;
}
p + p {
  margin-top: 2em;
}

また、次のように、あるdiv要素の上側マージンに4rem、その最初の子要素の上側マージンに2remを設定した場合、div要素の上側には合計値の6remではなく、一方の大きい数値である4remの余白が設定されます。

div {
  margin-top: 4rem;
}
div > :first-child {
  margin-top: 2rem;
}

マージンの相殺は、要素間の余白が広くなりすぎてしまう問題を防ぐための便利な仕様です。

しかし、マージンの相殺が発生しない特殊なケースや、マージンを別途設定する場合に祖先要素・子孫要素・隣接要素のマージンを設定する必要が出てくるといった問題があるため、上側マージンか下側マージンの一方のみを設定するのが一般的です。

上側マージン (margin-top) か下側マージン (margin-bottom) か?

上側マージンを設定するのがオススメです。CSSの仕様上「なんらかの要素の次の要素」は設定できますが「なんらかの要素の前の要素」は設定できないからです。

通常、余白は複数ある要素の間に設定したいはずです。例えば、p要素に上側マージンを設定する場合、なんらかの要素が前にあるp要素にだけ上側マージンを設定すればいいということになります。

* + p {
  margin-top: 2rem;
}

子要素が必ずマージンを設定したい要素だけになるなら、子セレクタで設定するといいですね。

blockquote > * + * {
  margin-top: 2rem;
}

以上のことを踏まえて全称セレクタを活用する

全称セレクタは、特定の要素内でプロパティを一括設定したり、何らかの要素の次にある要素にプロパティを設定する場合に、便利であることがお分かりいただけたかと思います。

それでは本題です。

正直、全称セレクタって使いにくいですよね。それは、プロパティを設定したくない要素にも意図せず適用される可能性があるからだと思います。ならば、プロパティを設定しない要素を決めてしまいましょう。

ということで、全称セレクタで垂直マージンを設定するCSSコードは、次のようになります。

/* 全称セレクタによるデフォルトマージンの設定 */
* {
  margin: 0;
}
* + * {
  margin-top: 2rem;
}
* + article,
* + section,
* + nav,
* + aside,
* + h1,
* + h2,
* + h3,
* + h4,
* + h5,
* + h6,
* + header,
* + footer,
* + hr,
* + main,
article + *,
section + *,
nav + *,
aside + *,
header + *,
footer + *,
hr + *,
main + * {
  margin-top: 4rem;
}
header * + *,
footer * + *,
blockquote * + *,
li * + *,
dd * + *,
figure * + *,
td * + *,
fieldset * + * {
  margin-top: 1rem;
}

/* マージンのリセット */
li,
li > *,
li > *:not(div):not(blockquote):not(ol):not(ul):not(dl):not(figure):not(table):not(fieldset) *,
dl > *,
dt,
dd,
dd > *,
dd > *:not(div):not(blockquote):not(ol):not(ul):not(dl):not(figure):not(table):not(fieldset) *,
figure > *,
figure > *:not(div):not(blockquote):not(ol):not(ul):not(dl):not(figure):not(table):not(fieldset) *,
*:not(article):not(section):not(nav):not(aside):not(h1):not(h2):not(h3):not(h4):not(h5):not(h6):not(header):not(footer):not(address):not(p):not(hr):not(pre):not(blockquote):not(ol):not(ul):not(li):not(dl):not(dt):not(dd):not(figure):not(main):not(div):not(table):not(form):not(fieldset) {
  margin-top: 0;
}

マージンを設定しない要素を決めている箇所は /* マージンのリセット */ 以降の部分になります。

  • デフォルトでは上側マージンを設定しない要素
    • li
    • dl > *
    • dt
    • dd
  • 特定の子孫要素に上側マージンを設定しない要素
    • li > *
    • li > *:not(div):not(blockquote):not(ol):not(ul):not(dl):not(figure):not(table):not(fieldset) *
    • dd > *
    • dd > *:not(div):not(blockquote):not(ol):not(ul):not(dl):not(figure):not(table):not(fieldset) *
    • figure > *
    • figure > *:not(div):not(blockquote):not(ol):not(ul):not(dl):not(figure):not(table):not(fieldset) *
  • 基本的に上側マージンを設定しない要素
    • *:not(article):not(section):not(nav):not(aside) … :not(form):not(fieldset)

ポイントは、マージン設定する要素をnot疑似クラスで連結して、マージン設定しない要素を指定することです。

これにより、全称セレクタでマージンを一括設定できるだけでなく、複雑に入れ子になった要素でも意図したとおりのマージンが設定できます。

ただ、これだけだと汎用性がありません。そこで、セレクタの詳細度を意図的に上げます。ポイントは上書きされないけど上書きしやすい詳細度にすることです。

/* マージンのリセット */
li:not(._),
li:not(._) > *:not(_),
li:not(._) > *:not(div):not(blockquote):not(ol):not(ul):not(dl):not(figure):not(table):not(fieldset) *,
dl:not(._) > *,
dt:not(._),
dd:not(._),
dd:not(._) > *:not(_),
dd:not(._) > *:not(div):not(blockquote):not(ol):not(ul):not(dl):not(figure):not(table):not(fieldset) *,
figure:not(._) > *:not(_),
figure:not(._) > *:not(div):not(blockquote):not(ol):not(ul):not(dl):not(figure):not(table):not(fieldset) *,
*:not(._):not(article):not(section):not(nav):not(aside):not(h1):not(h2):not(h3):not(h4):not(h5):not(h6):not(header):not(footer):not(address):not(p):not(hr):not(pre):not(blockquote):not(ol):not(ul):not(li):not(dl):not(dt):not(dd):not(figure):not(main):not(div):not(table):not(form):not(fieldset) {
  margin-top: 0;
}

このように、:not(_)でタイプセレクタ1つ分、:not(._)でクラスセレクタ1つ分、詳細度を上げました。これにより、マージンを設定しない要素を除外しつつ、後から異なったマージンを一括設定できるようになります。

/* 垂直マージン無し */
._example-1 * {
  margin-top: 0;
}

/* 垂直マージンを狭める */
._example-2 * + * {
  margin-top: 1rem;
}
._example-2 li * + *,
._example-2 dd * + *,
._example-2 figure * + * {
  margin-top: 0.5rem;
}
._example-2 li,
._example-2 dl > *,
._example-2 dt,
._example-2 dd {
  margin-top: 0;
}

クラスセレクタが2つ以上あれば、マージン設定しない要素にもマージンが設定できます。

今回、紹介したCSSコードは一例にすぎません。全称セレクタ・子セレクタ・not疑似クラスなどを組み合わせて、独自のマージン設定を模索してみてはいかがでしょうか?

関連記事