ボタンから学ぶコンポーネントのデザインと実装の考え方

## はじめに
今年でNSSOLのアドベントカレンダーへの参加は2回目になりました。
[去年の記事(何となく好きだと思っていたUIデザインと1から向き合う)](https://qiita.com/kinakome/items/a598b9d60862e2aad16a)
今回の記事では、**コンポーネントがどのようにデザインされ、どのように実装されるのか**に焦点を置いています。
もう知ってるよ！という内容が多いかもしれませんが、コンポーネントの作成過程で**デザイナーがどのような観点でデザインを行い、エンジニアがどのような考え方で実装を行うのか**、この記事を通して伝えることができればと思っています。

※ 本記事はデザインから実装までのプロセスの概要を紹介することが目的のため、具体的なコードについては省略しています。またこの記事の内容がデファクトスタンダードという訳ではなく、あくまで私の個人的な見解なのでご了承ください！

### この記事の対象者
- **「UIデザインってやっぱりセンスなの？」**と思っているエンジニアさん
- **「デザインしたUIってどうやって実装されていくの？」**と思っているデザイナーさん

### キーワード
- Material Design Guidlines
- Webアクセシビリティ
- React (Next.js)
- Storybook

## 0章 お題
この記事では最もシンプルなコンポーネントである**「ボタン」**のデザインから実装までを追っていきます。
今回作るボタンは**とあるサービスのアイテム削除モーダルの「キャンセル」ボタンと「削除」ボタン**をデザインおよび実装していきます。
![お題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270206/0b5c4d5a-59e2-1357-e42d-1c8158f10aa8.png)


## 1章 ボタンをデザインする
1章ではボタンのデザインに焦点を当てて進めていきます。
#### Material Design Guidlinesについて
FigmaやAdobe XDなどのプロトタイピングツールを使って手を動かす前に、まずは[Material Design Guidlinesのボタンの項目](https://material.io/components/buttons)を読みましょう。
Material Design Guidlinesは、Googleが公開しているデザインガイドラインです。デザイナーがデザインの勉強をするとき、**「まず初めに読みなさい！！」**と言われるサイトの１つです。
GoogleのサービスやMaterial Designを踏襲したサービスなどに無意識のうちに触れる機会が多いため、このガイドラインを参考にすることで、ユーザーが違和感を感じないデザインをデザイン経験の少ないエンジニアやデザイナーでも実現することができます。
今回はMaterial Design Gudlinesの解説を中心に、ユーザーが当たり前のように感じているデザインは、実は様々な根拠をもとにデザインされているということをお伝えできればと思っています！
※2021年に[Material Design 3](https://m3.material.io/)が公開されましたが、この記事は[従来のMaterial Design(M2)](https://material.io/)に則って作成しています。
#### ボタンの種類 (Material Design)
ボタンには**Text Button**、**Outlined Button**、**Contained Button**、**Toggle Button**の4種類のタイプがあります。Toggle Buttonは他の3つのボタンに比べて利用頻度が低いため、今回は取り扱いません。
ボタンの重要度は**Contained > Outlined > Text**の順となっています。
![ボタンの種類.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270206/c3b0c728-29f7-78b2-1b90-4e79db127b18.png)
#### ボタンの配置 (Material Design)
ボタンの配置として以下のようなパターンが紹介されています。中でも**text + Contained**のパターンは、重要なアクションを示したいときに推奨されています。
また、**Outlined + 違う色のOutlined**を並べる事はNGとされています。
![パターン.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270206/3d4b2992-8597-802e-c0af-038cb2d13b50.png)

#### ボタンの順序
主要なガイドラインにおいて、ダイアログ内のボタンの順序がどのように言及されているのか確認していきます。
[human Interface Guidelines(Apple)](https://developer.apple.com/design/human-interface-guidelines/macos/windows-and-views/dialogs/)
→**主要なアクションを開始するアクションボタンは、最も右側にある必要があり**、**キャンセルボタンはアクションボタンのすぐ左にある必要がある**
[Material Design Guidelines(Google)](https://material.io/components/dialogs)
→**アクションを確認するためのボタンは画面の右側に配置され、アクションを却下するボタンは、確認アクションの左側に配置される**
上記二つのガイドラインに則ると、以下の画像のように実行ボタンは右側に、キャンセルボタンは左側に置く必要があります。
※ この順序ではないガイドラインもあります
![ボタンの配置１.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270206/e1f95bc5-985c-94ab-5c4d-678fcd9f3721.png)


次に考えるべき事は、アクションの種類です
ボタンを押した時のアクションは大きく分けて2つあります

- **破壊的なアクション**
    - アイテムを削除する
    - サービスから退会する など
- **非破壊的なアクション**
    - ユーザー登録をする
    - アイテムを登録する　など

非破壊的なアクションについては単純に「実行ボタンは右側に、キャンセルボタンは左側」とすれば良いのですが、非破壊的なアクションは一度押してしまうともとに戻すのが大変なので、工夫が必要です。
最も単純な工夫としては、**非破壊的なアクションには赤色を使う**ことです。
モーダルの目的はあくまでも削除なので削除ボタンは右側に配置していますが、危険を感じる赤色のボタンであることから一回押すことを躊躇い、誤って削除するリスクを小さくします。
![キャンセルボタンの色.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270206/579d21d6-367f-a025-e044-22aad2470380.png)
キャンセルボタンをどうするか論争については、色々な意見があるので、時間があるときに是非調べてみてください！

#### ここまでの情報をもとにリデザインすると...
「キャンセル」に比べて「削除」というアクションを強調したいので、**Contained + Text**のパターンでリデザインしました。
これで必要なボタンは、「Text Button」と「DangerなContained Button」ということがわかりました。
![リデザイン１.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270206/5beb788d-7207-4268-5b58-14332f88b868.png)

#### ボタンの間隔を決める
ボタンに限らず、コンポーネントとコンポーネントの間の間隔を何pxにすれば良いか悩むことがあると思います。UIデザインにおいて余白のサイズを決めるとき、**8の倍数にする**ことで美しく見えるということが言われています。(全てのデザインが8の倍数で表現できるわけではないが、迷ったら8もしくは4の倍数にするのが無難)
今回は8の倍数の法則に則って、ボタン間の余白を16pxに設定しました。

![8の倍数.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270206/5c6f9767-2a40-66a8-59d6-5d7d823a50e3.png)


8の倍数についてさらに知りたい方は以下の記事がとても参考になります！

- [デザインは8の倍数でできている](https://ameblo.jp/ca-1pixel/entry-11837685575.html)
- [8の倍数ルールでデザインする理由とメリット・デメリット](https://yuyakinoshita.com/blog/2019/02/10/design-by-multiple-of-8/)

#### ボタンの状態とアクセシビリティ
ボタンの状態は主に以下の5つに分類されます

- **Enabled**：デフォルトの状態
- **Disabled**：押せない状態。opacityを小さくしたり、グレースケールにして表現することが多い。
- **Hover**：カーソルが乗っている時の状態。デフォルトの背景色の明度を変化させることが多い。
- **Focused**：フォーカスが当たっている時の状態。ボタンの外周に別の色の枠線をつけて表現することが多い。
- **Active**：ボタンが押された時の状態。あたかも押し込まれているようなデザインにすることが多い。

それぞれの状態を正しくデザインすることは、**Webアクセシビリティ**の観点から非常に重要です。
**「Webアクセシビリティを確保する」=「障害者や高齢者、また怪我をして一時的に操作が困難な人、マウスを忘れてキーボード入力しかできない人、など様々なケースを考慮した上で、誰でも操作ができる」**ということです。
Webアクセシビリティには[**Web Contents AccessibilityGuidlines**](https://waic.jp/docs/WCAG21/)という世界共通のガイドラインが作成されており、各項目に対しA(最低限のレベル)・AA・AAA(最高レベル)の三段階のレベルが設定されています。欧米ではAAまでの対応を必須としている地域も存在するほど、重要な内容になっています。
アクセシビリティの観点から各ボタンのデザインや実装を考える場合、以下のような対応が考えられます。

- **Enabled**
    - 文字を認識しやすいようにボタンの背景色と、**文字色と背景色のコントラストは4.5:1以上**にする。([WCAG 1.4.3 コントラスト (最低限)](https://waic.jp/docs/WCAG21/#contrast-minimum))

![コントラスト.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270206/44aff496-7a49-317e-d9cb-fca9d7c93bbf.png)

- **Disabled**
    - 色を判別し辛いユーザーのために、色だけでDisableを表現するのではなく、カーソルをnot-allowedにする。([WCAG 1.4.1 色の使用](https://waic.jp/docs/WCAG21/#use-of-color))

![disable.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270206/0f64cc43-dcf8-ffaa-4bd9-9afd1288f9d6.png)


- **Hover**
    - 色を判別し辛いユーザーのために、色だけでHoverを表現するのではなく、カーソルをpointerにする。([WCAG 1.4.1 色の使用](https://waic.jp/docs/WCAG21/#use-of-color))

![hover.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270206/b42bad84-b99e-685a-1f2a-bff83e374cd2.png)

- **Focused**
    - 一見重要ではないように感じるが、アクセシビリティの観点ではとても重要。
    - マウスを使えないユーザーがTabで操作する時に、適切にFocusを表示する必要がある。
    - ブラウザ標準のFocus時のStyleをそのまま使用しても問題ない。

![focus.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270206/7548d34c-7c76-de65-73b1-1d58edd024cf.png)


- **Active**
    - Activeをスタイルを設定することで「ボタンを押した感」を強調できる。
    - アクセシビリティ的にはあまり重要度は高くない。

今回はアクセシビリティ的に重要な、Activeを除いた4つの状態のボタンをデザインします。
![ボタンのタイプ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270206/a5cd9b4f-957b-1668-88f2-df212ebdc64b.png)


これでボタンのデザインはおしまいです！

## 2章 ボタンを実装する
2章では1章でデザインしたボタンを、ReactのフレームワークであるNext.jsを使うことを前提に、実装の考え方をご紹介します。
※ Next.jsではなくても考え方は同じだと思います。

#### ディレクトリ構造を考える
`yarn create next-app`でNext.jsのアプリケーションを作った場合、**「pages(URLに対応したページ本体を置くディレクトリ)」**と**「styles(CSSなどのStyleを置くディレクトリ)」**がroot直下に作成されます。
ReactやNext.jsはディレクトリ構成の自由度が高く、人によって構成が大きく違うと思うのですが、root直下にsrcを作成し「pages」と「styles」を移動した後、**コンポーネントを管理するための「src/components」**を作成する場合が多いと思います。
この「src/components」以下のディレクトリ構成をどのようにすればコンポーネントを管理しやすいか議論されることが多いです。
![ディレクトリ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270206/079c236c-af2c-8640-b5e2-fe00c4d169e8.png)

#### Atomic Designでコンポーネントを分割する
コンポーネントをどのように分割し、どのように管理するかについてはいくつか方法があるのですが、その中の一つに**「Atomic Design」**と呼ばれる手法があります。
Atomic Designではコンポーネントを以下の5つに分類して管理します

- **Atoms**： formのラベル、テキストボックス、ボタンなど、機能を壊さずこれ以上分解できない基本的なHTML要素
- **Molecules**：ユニットとして一緒に機能するUIの単純なグループ。たとえば、フォームラベル+テキストボックス+ボタンの検索フォームなど。
- **Organisms**：Atoms/Molecules/または他のパーツで構成される比較的複雑なUIコンポーネント。ヘッダーなど。
- **Templates**：レイアウト内にコンポーネントを配置し、デザインのベースとなるコンテンツ構造を明確にするページ単位のオブジェクトです。(ワイヤーフレームみたいなもの)
- **Pages**：テンプレートのから作成されたインスタンス。最終的なUI。

※ 原文は[Atomic Design Methodology](https://atomicdesign.bradfrost.com/chapter-2/)
※ Atomic Designについてもっと詳しく知りたい方は、[Atomic Design を分かったつもりになる(DeNA Design)](https://design.dena.com/design/atomic-design-%E3%82%92%E5%88%86%E3%81%8B%E3%81%A3%E3%81%9F%E3%81%A4%E3%82%82%E3%82%8A%E3%81%AB%E3%81%AA%E3%82%8B)を参照してください。

**Atomic Designをそのままプロジェクトに導入しようと人によって感覚が異なり、逆に複雑になってしまう可能性があります。**そのためエンジニアやデザイナーが集まって話し合い、Atomic Designの中でどの項目を使い、チームの中ではそれぞれどのような定義にするか明確にする必要があります。
※ Atomic Designを使うことが最適解という訳ではないです
#### 本記事におけるAtomic Design
本記事ではAtoms、Molecules、Organismsを以下のような定義で用いることにします。
※ この分け方が正解ではありません。それぞれのチームで最適な分割方法を見つけてみてください！

- **Atoms**：ボタンやテキストボックスなど一つの図形で表せるもの。またコンポーネントの中にドメイン(特定の場所で使うための具体的な機能)を持たず、常に汎用的であるもの。
- **Molecules**：Atoms同士、もしくはAtomsと他のパーツを組み合わせたもの。検索フォームやメニューなど、様々なページで再利用可能な汎用的なコンポーネント。単一の機能を持つ。
- **Organisms**：ヘッダーなど、ページに属さない共通コンポーネント。(not 汎用コンポーネント)

↓Atomic Designで分けた例
![Atomic Design.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270206/4514af1f-8e0c-3a79-7bc2-8d1185da91d5.png)

本記事ではAtomic Designをベースにコンポーネントを分割しているので、ディレクトリ構造は以下のようになり、ボタンは**src/components/atoms/Button**以下に実装してくことになります。
![ディレクトリ2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270206/9b79f62a-53d8-8024-8e54-d31ce501338c.png)

#### ベースのボタンを用意する
今回必要な「Dangerボタン」と「text」ボタンは、見た目は違うものの押せる範囲は同じです。(hover時に塗りつぶされる部分が押せる範囲)
また今後「PrimaryなContainedボタン」や「Outlinedボタン」が実装されたとしても形は同じはずです。
それぞれのボタンを全く別なコンポーネントとして実装してしまうとメンテナンスが大変になってしまうため、**ベースとなるボタンを用意**し、ベースのボタンをオーバーライドする形で各ボタンを実装していきます。
![実装.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270206/449846e8-b71f-d365-dc5b-be0a75df0cfc.png)
↓以下がButtonフォルダ内の構成例です
![ディレクトリ3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270206/fe0cb8c4-a3c5-0081-d406-47c681e32694.png)



#### 汎用的なコンポーネントのルール
AtomsやMoleculesなどの汎用的なコンポーネントを作る場合、以下のような事を意識すると、より汎用性が高まります。

**widthに具体的な値を設定するのではなく、paddingで可変長にする**
汎用的なコンポーネントに渡されるコンテンツは未知数です。どんなコンテンツがきてもいいように、可能な限り長さを固定せず、paddingでコンテンツに合わせた幅になるように設定します。(paddingのサイズも変更できるようにするば、さらに汎用的になりますが、どこまで汎用的にするかは考えものです)

**コンポーネントの外周にmariginを設定しない**
コンポーネントの外周にmarginを設定しまうと、一気に汎用性が低くなってしまいます。パズルのピースの周りに余白があると困るのと同じです。いつどこに置かれるか分からないので、marginはコンポーネントの呼び出し先で設定するようにしましょう。

![Atoms.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270206/c193f8c2-fc2b-a432-0f7e-cf26e1582359.png)

以上がコンポーネントを実装する時の考え方になります！
実際の実装方法まで載せようと思ったのですが、ボリュームが大きくなりすぎるので割愛します。(別の記事でチャレンジしたい...)
次の章では**作成されたコンポーネントをどのように管理するか**、について記載しています。

## 3章 Storybookでコンポーネントを管理する
サービスの規模が大きくなってくると、「もっとわかりやすくコンポーネントを管理したい！」、「異なるサービスにも作成したコンポーネントを流用したい！」という要望が出てくるかと思います。
近年コンポーネント管理で使われる代表的なツールとして[**Storybook**](https://storybook.js.org/)があります。
Storybookを使うと以下のようなことができるようになります。

- **コンポーネント単位での仕様書の作成** (storybook-readme)
- **コンポーネント単位での動作確認** (Storybook Controls)
- **コンポーネントのリグレッションテスト** (Storycap + reg-suit)
- **コンポーネントのアクセシビリティチェック** (@storybook/addon-a11y)

↓Storybookで作成した仕様書
![storybook.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270206/8fa83307-aa88-84d8-faf7-4a7511cfa22d.png)

Storybookファーストな開発という言葉も出てきたように、ページ本体やAPIが実装されていない状態でも、Storybook上でプロパティの値をモックしてコンポーネント開発をすることができます。かなり快適なので、私はStorybook無しの生活が考えられません...
Storybookはエンジニアとデザイナーの共通言語になり得るツールだと思っています。
**皆さんもStorybookを使って快適なコンポーネント開発ライフを送ってください！**


## まとめ
以上で本記事は終了になります。
繰り返しになりますが、この記事の内容はあくまで個人的な見解です！
デザイン手法やコンポーネントの管理手法は、デザイナーやエンジニアによって様々です。
この記事が、自分なりのベストプラクティスを探すきっかけになれば幸いです！