ボタンコンポーネントからはじめるCSS設計

# はじめに

Webサイトの構築においては、ページ上に繰り返し現れる各種UIをメンテナンス性・再利用性の観点からコンポーネント化し、共通のソースコード（HTML/CSS/JavaScript）で管理することが一般的です。


数あるUIコンポーネントの中で、サイト規模の大小を問わずほとんどすべてのWebサイト（ページ）に存在すると言えるのが **ボタンコンポーネント** です。

ボタン自体はシンプルな見た目であることが多く、一見すると実装は簡単そうに思えますが、さまざまな場面に登場することから機能やデザイン（見た目）のバリエーションが意外と多くなり、サイト制作を進めるうちにいつの間にか複雑な実装になってしまった、ということも少なくありません。

私自身、1ページ完結のLPから数1000ページに及ぶ大規模サイトまでさまざまなタイプのWebサイト制作に携わる中で、「ボタン」を設計する難しさと奥深さを日々感じています。

本記事では、ボタンコンポーネントのCSS設計において私が意識している8つのポイントをご紹介します。

# ボタンコンポーネントのCSS設計で意識する8つのポイント

## 1. 特定のclassのみでボタンコンポーネントを表現する

```html

<a class="button" href="#">ボタン（a要素）</a>
<button class="button" type="button">ボタン（button要素）</button>
```

```css

/* OK */
.button {
  /* ボタンコンポーネントのスタイル指定 */
}

/* NG */
a.button,
button,
p.button a {
  /* ボタンコンポーネントのスタイル指定 */
}
```

ボタンコンポーネントは **特定のclassのみ** で表現できるようにし、**HTML要素に依存したスタイル指定は避ける** ようにします。

上記のOK例では、ボタンコンポーネントを表現するためのclassとして `.button` [^1] を用意し、対象となるHTML要素が何であれ（a要素、button要素、input要素、div要素、span要素など [^2] ）classの付与のみでボタンコンポーネントのスタイルを適用できるようにしています。

[^1]: CSSの命名規則については本記事では取り扱いません。

[^2]: div要素やspan要素はtabキー操作時にフォーカスが当たらないため、「ボタン」として振る舞わせる場合にはtabindex属性の追加など工夫が必要です。

## 2. a要素やbutton要素のデフォルトCSSを意識して、ボタンコンポーネントの初期スタイルを調整する

ボタンコンポーネントとして利用されることが多いa要素やbutton要素にはユーザーエージェントごとのデフォルトCSSが指定されているため、それらを踏まえて `.button` に対してスタイルの調整をおこないます。

```css

.button {
  -webkit-appearance: none;
  box-sizing: border-box;
  margin: 0;
  border: 0;
  background-color: transparent;
  color: inherit;
  font-size: inherit;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}
```

button要素のデフォルトスタイルをリセットするための `-webkit-appearance: none;` や、a要素のデフォルトスタイルをリセットするための `color: inherit;`, `text-decoration: none;` などを記述しています。

## 3. displayの値はinline-flexにする

一般的にボタンコンポーネントのdisplay値は `inline-block` とされる場合が多いように思いますが、自分の経験上では `inline-flex` がもっともスタイリングの融通が効く指定だと感じています。

ボタンの内容となる **テキストやアイコンは上下左右中央寄せに配置されることが多く**、そのようなレイアウトを `justify-content` と `align-items` の指定によって簡単に実現することができます。

```css

.button {
  -webkit-appearance: none;
  box-sizing: border-box;
  margin: 0;
  border: 0;
  background-color: transparent;
  color: inherit;
  font-size: inherit;
  text-align: left;
  text-decoration: none;
  cursor: pointer;

  /* 追記 */
  display: inline-flex;
  justify-content: center;
  align-items: center;
}
```

### inline-blockとの比較

display値が `inline-block` の場合、例えば「ボタンが **特定の高さを保ちつつ**、ボタン内の **テキストやアイコンは上下中央寄せにしたい**」といったデザイン的な要求があった場合に `padding` や `font-size`, `line-height` など複数の数値を絡めた調整が必要になり、扱いづらいコードになりがちです。

その点、display値が `inline-flex` であれば、`min-height` と `align-items` を指定することで同様のデザインも簡単に実現可能です。
また、「`font-size` を少し変えたらボタンの高さが微妙に変わってしまった」といったことも起こりにくくなります。

```css

.button-inline-block {
  display: inline-block;
  /* 省略 */

  font-size: 20px;
  line-height: 1;
  padding-top: 12px;
  padding-bottom: 12px;
  /* 20 + 12 + 12 = 44pxの高さを確保する */
}

.button-inline-flex {
  display: inline-flex;
  /* 省略 */

  min-height: 44px; /* 最低限の高さ44pxを保ちつつ */
  align-items: center; /* ボタンの中身は上下中央寄せ */
}
```


## 4. widthやheight、paddingなど、ボタンの大きさに影響する「固定値」の指定は慎重におこなう

1つ前のセクションで「高さ指定」の話をしたばかりですが、ボタンコンポーネントに対して `width`, `height`, `padding`, `font-size` などサイズに関する固定値を指定する際には、本当にそれが **ボタンのデザインルールとして妥当な実装であるかを十分に検討すべき** です。

### デザインの意図を考える

例えば、とあるWebサイトを制作する際、デザインカンプ内に次のようなボタンコンポーネントがあったとします。
調べてみると **ボタンの横幅はだいたい200px** になっていました。
このボタンコンポーネントをCSSで実装する際、どのようなコードが妥当と言えるでしょうか？

![qiita_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25032/3e4e19b2-911a-20cf-2cce-c3bd2a42884d.png)

結論から言うと、この時点ではまだ **ボタンコンポーネントのもつデザインルールや特性が判断できない** ため、**ボタンコンポーネントとして妥当なCSSを書くことはできません。**

このデザインカンプから読み取れる範囲内でも、少なくとも下記3つのようにまったく異なるパターンのデザインルールが考えられます。

1. ボタンの横幅は **必ず200px** である
2. ボタンの横幅は **テキスト量によって伸縮** し、ボタン内側に **必ず左右45pxの余白（padding）** が確保される
3. ボタンの横幅は **テキスト量によって伸縮** するが、どんなにテキスト量が少ない場合でも **最低200pxの横幅** が確保される

個人的には、2か3のどちらかかな？ と考えると思います。

CSS設計時に大切なことは、このようなデザインルールについて **デザイナーの意図を確認し、それらを言語化し、実装まで落とし込む** ことです。

そのような検討プロセスを省略して、デザインカンプの見た目から安易に `width: 200px;` といった固定値を指定してしまうと、本来のデザインルールと相反するコンポーネントとなって後々の制作や運用に支障をきたす場合が多いです。


## 5. ボタンの種類が明確にある場合のみ、Modifierを利用して派生パターンを作成する

「コンテンツ幅100%に広がるボタン」「サイト内で重要度の高いボタン」など、**ボタンの種類や意味合いが明らかに他のものと区別できる場合** は、Modifierを利用して新たなclassを作成し、差分となるスタイルを指定します。

<p class="codepen" data-height="300" data-theme-id="18225" data-default-tab="result" data-user="shouchida" data-slug-hash="MWYvoZM" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="qiita_c382cf78f8ffe6fbe65b_03">
  <span>See the Pen <a href="https://codepen.io/shouchida/pen/MWYvoZM">
  qiita_c382cf78f8ffe6fbe65b_03</a> by Sho Uchida (<a href="https://codepen.io/shouchida">@shouchida</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

```html

<a class="button button-full" href="#">コンテンツ幅100%に広がるボタン</a>
<button class="button button-primary" type="button">サイト内で重要度の高いボタン</button>
```

```css

.button {
  /* 省略 */
}

.button-full {
  width: 100%;
}

.button-primary {
  background-color: red;
  color: white;
}
```

このようなclassを作る際も、**「どこまでが標準的なボタンコンポーネントが持っている特性（スタイル）で、どこからが例外的な特性（スタイル）なのか」** についてデザイナーと議論をし、可能な限りそれらを言語化した上で実装を進めていくことが大切です。

逆に、きちんと言語化できないことを「何となく」で実装してしまうと、それが後になって矛盾を引き起こす場合が多いと感じます。

## 6. marginなど、レイアウトに影響するスタイル指定はおこなわない

あらゆる場所で利用される可能性のあるコンポーネントは **「それ自身がどのようにレイアウトされるかは知らない」** という状態にしておくべきであり、`margin` や `float` 、`text-align` など **レイアウトに関するスタイルをコンポーネント自身に持たせることは避けます。**

```css

/* NG */
.button {
  margin: 30px 0 0;
  text-align: center;
}
```

レイアウトに関するスタイルは祖先要素にもたせるか、コンポーネント要素に固有のclassを新たに付与して、そのclassに対してスタイル指定します。

```html

<div class="centerButton">
  <button class="button" type="button">ボタン</button>
</div>
```

```css

/* 祖先（親）要素にレイアウトスタイルをもたせる */
.centerButton {
  text-align: center;
}
```

## 7. target="_blank" やPDFリンクの場合に自動的にアイコンが表示されるようにする

Webサイトのデザインルールにもよるところですが、ページ内の「別ウィンドウ（タブ）で遷移するリンク」や「PDFダウンロードリンク」に対して、それとわかるよう特定のアイコンを表示することが一般的です。

ボタンコンポーネントにもそのようなデザインルールが適用されるのであれば、**属性セレクタと疑似要素を利用してアイコン表示を自動化** しておきます。

これにより、個々のボタンにアイコンを追加していく作業負荷が軽減し、長期的なサイト運用におけるデザインルールのバラつきを抑える効果も期待できます。

<p class="codepen" data-height="300" data-theme-id="18225" data-default-tab="result" data-user="shouchida" data-slug-hash="zYxzEdM" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="qiita_c382cf78f8ffe6fbe65b_01">
  <span>See the Pen <a href="https://codepen.io/shouchida/pen/zYxzEdM">
  qiita_c382cf78f8ffe6fbe65b_01</a> by Sho Uchida (<a href="https://codepen.io/shouchida">@shouchida</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

```css

.button[target="_blank"]::after {
  font-family: "Font Awesome 5 Free";
  content: "\f24d";
  margin-left: 0.4em;
}

.button[href*=".pdf"]::after {
  font-family: "Font Awesome 5 Free";
  content: "\f1c1";
  margin-left: 0.4em;
}
```

上記の例では、[Font Awesome](https://fontawesome.com/)によるアイコンフォントを利用して、「`target` 属性値が `_blank` である場合」と、「`href` 属性値に `.pdf` が含まれる場合」にそれぞれアイコンを表示させています。
プロジェクト独自のアイコン画像を使用する場合は、`background` として画像を指定し、サイズの調整をおこないます。

```css

.button[href*=".pdf"]::after {
  content: "";
  background: url(icon_pdf.svg) no-repeat center / contain;
  width: 0.8em;
  height: 0.8em;
  margin-left: 0.4em;
}
```

## 8. disabled など非活性状態のスタイルも忘れずに実装する

フォームに含まれるボタンなど、「操作ができない（disabled）状態」のボタンもWebページには頻繁に登場するため、ボタンコンポーネントを作成する際はそれらのスタイルも忘れずに用意しておきます。

<p class="codepen" data-height="300" data-theme-id="18225" data-default-tab="result" data-user="shouchida" data-slug-hash="YzPQrrP" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="qiita_c382cf78f8ffe6fbe65b_02">
  <span>See the Pen <a href="https://codepen.io/shouchida/pen/YzPQrrP">
  qiita_c382cf78f8ffe6fbe65b_02</a> by Sho Uchida (<a href="https://codepen.io/shouchida">@shouchida</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>

```html

<button class="button" type="button" disabled>非活性状態のボタン</button>
```

```css

.button[disabled] {
  pointer-events: none;
  background-color: gray;
}
```

ここでも属性セレクタを利用し、「`disabled`属性がある場合」という条件でスタイルを記述します。


# おわりに

ボタンコンポーネントは **ひとつのWebサイト内で数多く使用される** という特徴からも、設計段階における少しの差が後々のサイト運用負荷に大きく影響する可能性があります。

「たかがボタン」ですが、本記事内で繰り返し述べたように、最適なボタンコンポーネントを作るためには **デザインの意図やルールについてデザイナーと実装者との間でしっかりと認識を合わせ、言語化していく** ことが非常に大切です。

そして、そのようなコミュニケーションの積み重ねがWebサイト全体の品質向上に繋がっていくと考えています。