実例 Qiitaのcssで余白を書くときのルール 後編

# これはなに？

この記事は下記の記事の後編になります。

https://qiita.com/gilly/items/26963124ddb4cbe15648

cssで余白を付ける方法はたくさんあります。
一定のルールがないと、あっちではmarginで、こっちではpaddingで、方向も色々で…と崩壊し始めてしまいます。

Qiitaでもルールは特別設けられていませんでした。
2020年頃から、ルールを設定し始めました。

10年以上続くサービスですので、全適応を一度にするのではなく、新しく作ったコンポーネントや変更を加えるときに、徐々に対応していきました。

個人的にはどのようなルールであっても、開発に関わるメンバーの合意が取れたルールは必要だと思います。

この記事はQiitaでの事例を紹介したいと思います。
絶対これが最高！というものではなく、こんなルールで作っているサービスもあるんだと知っていただければ幸いです。

Qiitaのcssガイドラインには「marginは、要素が追加される方向にあわせてつける」という文が含まれています。
後編では、その理由と、例外的に使う例を紹介したいと思います。

# 対象読者

* デザイナー
* スタイルのコーディングをするエンジニア
* 記事を書くときにスタイルをいじることがある人

# 「余白は追加される方につける」を基準にしたい

冒頭でも述べましたが、Qiitaのcssガイドラインには「marginは、要素が追加される方向にあわせてつける」という文が含まれています。

今回は理由として二点紹介します。

* 末尾の要素の存在が任意である場合が多い
* 隣接セレクターを使った場合分けがしやすい


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

Webでは重要な要素（ユーザーに見てほしい情報）ほど先に出すことが多いと思います。
ということは、あとから出てくる要素は任意になって、表示される場合とされない場合があり得ます。

私も最初このルールを理解するときに下記の記事を参考にさせていただきました。
カードのレイアウトを例に解説していらっしゃいます。

https://qiita.com/ln-north/items/dcf305d80edde550645e

https://yuheiy.com/20210127-prefer-margin-top

### Qiitaの事例 「記事ページ」

Qiitaの事例としては、この記事を書いた時点では記事ページに直したい要素が存在しています。
次の要素との余白をとるために`margin-bottom` をつけているけれど次の要素が存在していない状態です。

|Qiitaの記事ページの一番下|
|---|
|![スクリーンショット 2023-12-05 23.38.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215239/470a0d0d-6c4f-d98a-c6fb-21472008cbd5.png)|

Qiitaではemotionを使って、CSS in JS で実装しているので、class名がランダムな文字列に書き出されていますが、`contentBlockStyle` というコンポーネントに `margin-bottom: 24px;` がついています。
そのため一番最後の要素は、下に要素が追加されないのに余分な余白がついている状態です。

この事例は下記のように処理できます。

```react
const contentBlockStyle = css({
  //その他のスタイル省略
  '&:last-child': {
    marginBottom: 0,
    //その他のスタイル省略
  },
})
```

ですが、そもそも自分自身（この場合`contentBlockStyle`）が表示されるときに `margin-top: 24px;` を持っていれば、このような処理も不要になります。
なので、書き方としては下記が好ましいと考えています。

```react
const contentBlockStyle = css({
  marginTop: 24,
  //その他のスタイル省略
})
```

React component化していく中で、他の要素によって余白がコントロールされるよりも、自分自身で管理している方がコードの可読性は良いだろうと私は思います。

### 自分で書いておいてツッコミますが…

`:last-child`じゃないけど、`:first-child`で最初の要素の余白指定を打ち消すことがあると思います。
先程の記事の例を、リファクタリングするなら下記の書き方になると思います。

```react
//Wrapする要素

const containerStyle =css({
  //その他のスタイル省略
  padding: '24px 24px 0';
})
```

```react
//Wrapする要素に対して一つ階層が下の要素

const contentBlockStyle = css({
  //その他のスタイル省略
  'label:not(:first-child)': {
    marginTop: 24;
    //その他のスタイル省略
  },
})
```

こうなると、手間は一緒では？と疑問に思ったあなたは鋭いです。

でもよく考えてください。
`:not(:first-child)` にすると、最初の要素以外に「 **追加** 」する書き方です。
`:last-child`では、最後の要素から「 **削除** 」する書き方です。

必要なときに「 **追加** 」される方が、ブラウザのInspectorの見通しも良いです。
「 **削除** 」する書き方にした場合、打ち消したはずのものをまたどこかで上書きする可能性もあります。
そうなるとコードが複雑化していってしまいます。
ですから、Qiitaでは好ましくないとしています。

ただコーディングルールでは縛ってはいません。
`:last-child`が必要になることもあると思うからです。

## 隣接セレクターを使った場合分けがしやすい

### Qiitaのコードでよくあるパターン

Qiitaで一番隣接セレクターが登場する場面はリストのように、同じものが並ぶときだと思います。
Internet Explorerのサポートが終了してから、 FlexboxやGrid layoutで `gap` の指定をすることも増えていますが、まだまだ使う場面は残っています。

scssで書くとこのような感じです。

```scss
.list {
  //その他のスタイル省略
  '& + &' {
    margin-top: 32px;
    padding-top: 32px;
    //その他のスタイル省略
  }
}
```

|例：Qiita Jobs Plusの一覧ページ|
|---|
|![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215239/275a4596-25d8-db65-f21b-a2b687ef3f33.png)|

この例だと若干最初の理由も包括している部分がありますが、同じ要素が隣接する場合のみ`border`,`margin`,`padding`を追加して管理しています。

### 特定の組み合わせの場合だけ余白を変えたいパターン

他の例としては、記事ページ（Qiita、Team、Qiita Jobs Plusなど）で編集者が任意で要素を組み替える場所だと、特定の組み合わせの場合だけ`margin`を変更したいこともあります。

Qiitaの実例ではありませんが、下記のようなパターンが想定されます。

 **例:地のテキストは、段落が変わったら24pxの余白。h2と隣接するときだけ余白を16pxに縮めたい。** 

```scss
p {
  margin-top: 24px;
}

h2 + p {
  margin-top: 16px;
}
```

隣接セレクタは「Aの隣のBに対して宣言する」という表現になる以上、AとBとの間の余白を設定するためにはmargin-topを使うしかありません。
h2で`margin-bottom`を管理している場合、広げるならなんとかなると思いますが縮めたい場合、`p`タグ側ではなく`h2`タグ側のmargin-bottomを打ち消すのは難しいです。

# 例外と注意の紹介

## ［例外］量がコントロールできず、かつ折り返しが発生する要素

 **例：Flexboxでタグの並びを実装して、`flex-wrap: wrap;` で画面幅に応じて折返しさせたい時** 

|タグがいっぱい並ぶようなUI|
|:---|
|![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215239/2f5e399d-a468-4278-3ac2-e97cf617ddbe.png)|

ここまでの余白のルールを踏まえてスタイルを当てるなら、下記のように書くと思います。

```scss
.tagContainer {
  display: flex;
  flex-wrap: wrap;
  //他のスタイル省略 
}

.tag {
  & + & {
    margin-left: 8px;
    margin-top: 8px;
    //他のスタイル省略 
  }
}
```

こうすると、

* 1段目に不要なmargin-topが生まれるのでBAD :x:
* 2段めが生まれたときに、2段めの最初のタグに不要なmargin-leftができてしまうのでBAD :x:

なので、例外的にこうしていました。

```scss
.tagContainer {
  display: flex;
  flex-wrap: wrap;
 //他のスタイル省略 
}

.tag {
  & + & {
    margin-right: 8px;
    margin-bottom: 8px;
    //他のスタイル省略 
  }
}
```

しかし、Internet Explorerのサポートが切れた今なら　`gap` で処理できます。

```scss
.tagContainer {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
 //他のスタイル省略 
}
```

## ［注意］任意要素が先に表示される

「余白は追加される方につける」のルールに則っていると別に例外ではないのですが、「`margin-top`,`margin-left`で管理する」みたいな覚え方をしているとハマりやすいので「注意」として開発チームには共有しています。
ここでいう「任意」とは、「なくても成立する」、「ない場合がある」ものを指します。


例：任意（なくても成立する）のアイコンが文頭にくるとき

|ユーザーが行動条件を満たしたときにチェックアイコンが表示されるUI|
|---|
|![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215239/0f771c3f-ea5f-6cf8-36ab-1a3076699961.png)|

テキストの方に `margin-left` をもたせると、文字組みがずれてしまいます。
隣接セレクタなどで、`アイコンがある場合margin-leftをつけること`も可能ですが、それよりは任意の要素のチェックマークが`margin-right`を持つほうが、コードがシンプルになります。

| :x: テキストが`margin-left`を持っている| :white_check_mark:  アイコンが`margin-right`を持っている|
|---|---|
|![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215239/de3fd134-4a2f-c061-7769-5fb44c58804d.png)|![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215239/bfaaa240-0a14-0864-4f85-c5689e7abc03.png)|
|センタリングがずれてしまう|任意のもので調整できる|

# まとめ

* 余白は追加される方につける
    * 末尾の要素の存在が任意である場合が多いため
    * 隣接セレクターを使った場合分けがしやすいため
* [例外]量がコントロールできず、かつ折り返しが発生する要素は`margin-right`や`margin-bottom`を使う
* [注意］任意要素が先に表示される場合は、`margin-right`を使う

この記事が誰かのお役に立てると幸いです。

# 参考

* [デザイナーの考える「余白」とは何か書き下してみる - Qiita](https://qiita.com/ln-north/items/dcf305d80edde550645e)
* [垂直方向のマージンにはmargin-topを優先的に使う理由 - Yuhei Yasuda](https://yuheiy.com/20210127-prefer-margin-top)