コンテンツにスキップ

fukuemon.dev のデザインシステム

この文書は、fukuemon.dev の画面とコンポーネントのコードを書く人が、書く前に読む規則である。
値は fukuemon.dev が決め、アクセシビリティ、タイポグラフィ、配色、レイアウトの指標と参考に、デジタル庁デザインシステム(DADS)を使う。
構成の判断は Design Doc と、ADR-0021、ADR-0022、ADR-0024、ADR-0028、ADR-0062、ADR-0075 にある。

カタログは https://design.fukuemon.dev で開ける。
ローカルでは、リポジトリのルートで pnpm --filter @fukuemon/ui storybook を流し、http://localhost:6106 を開く。

話題ごとの文書

デザインシステムの文書は、この文書と、次の 3 つの文書でできている。
画面とコンポーネントのコードを書く前に、この文書と 3 つの文書を全て読む。 この文書だけを読むと、トークンの使う場面と、コンポーネントの使い方と状態を知らないまま書くことになる。

文書 持つ規則
トークン セマンティックトークンの使う場面、テーマ、文字、レイアウト、区切り、重なり、動き
コンポーネントとパターン コンポーネントごとの使う場面、変種、状態、内容の制約、キーボードの操作と、パターン
カタログ カタログのページ、題の決め方、カタログに載せてよいもの

知りたいことごとの参照先

デザインシステムの文書には、使い方の規則だけを書き、値と振る舞いはコードを参照先にする。

知りたいこと 参照先
色、文字、動きの時間と緩急の値 src/tokens/primitive.css
セマンティックトークンの名前と、プリミティブトークンのどの値を指すか src/tokens/semantic.css(ライト)と src/tokens/semantic-dark.css(ダーク)
画面のコードで書くユーティリティの名前 src/tokens/theme.css
静的なファイルが読むトークンの CSS src/tokens/tokens.gen.css(生成物)
エンジンに渡すコードの色 src/tokens/code-theme.gen.json(生成物)と、VS Code の形式のテーマを作る src/tokens/code-theme.ts
コンポーネントの props と振る舞い src/base/、src/case/ のコードと、カタログのコンポーネントのページの「props」と「状態」
構成の判断と、却下した案 docs/adr/

元にしたデザインシステムとの関係

DADS から外れる規則は、この表に並べず、外れる規則の隣に、DADS が求めることと外れる理由を書いている。

項目 DADS の扱い このプロダクト
主の色 使う側に任せる くすんだ緑(primary)
余白の段 使う側に任せる Tailwind のデフォルトの段(1 段 4px)。ブロックの間は 8px の倍数
角丸と影 コンポーネントごとに値を持つ 角丸は 2px の 1 つだけを、操作できる要素と重なる面に使う。影は、ずらした影の 1 つだけを、重なる面に使う。外れる理由は トークンの「レイアウト、区切り、重なり、動き」にある
コントラスト 変えない(文字 4.5:1、文字でないもの 3:1) 確認: pnpm run check:contrast。ライト、OS の設定のダーク、Storybook のダークの 3 つの条件で見る
状態の色相と意味の対応 変えない(成功は緑、エラーは赤、注意は黄か橙) 確認: レビュー。注意だけを錆にする理由は トークンの「色」にある

個性として残すもの

運営者は、旧サイトの和の感じを残すと決めた(ADR-0024、ADR-0026、ADR-0028、ADR-0075)。
規則の本文は、右の列の文書にある。
残すものと使う場所は次のとおり。

残すもの 使う場所 規則の場所
生成りの地、墨の文字、温かい灰の罫 全ての画面 トークンの「色」
くすんだ緑、藍、錆の差し色 緑は主の色と内部のリンク、藍は外部のリンクと情報、錆は目次の現在地、ヘッダーのナビの今いるページ、注意 トークンの「色」
記事の題と見出しの明朝 記事の題、見出し、小見出し、ダイアログの題 トークンの「文字」
罫線の版面 全ての画面。区切りは罫線と余白で示す。角は操作できる要素と重なる面だけを 2px で丸め、影は重なる面だけに付ける トークンの「レイアウト、区切り、重なり、動き」
版画風の挿絵 記事の一覧の冒頭だけ。トップは水墨の門の絵と立体の庭にし、庭の上端と下端に霞(garden-mist-day、夜は garden-mist-night)を敷いて字を載せる 挿絵の Design Doc、ArtBand、トップの庭の Design Doc の「ヘッダーと霞」
筆の線を引く動きと、墨のにじみ 選んだタブの下、検索の欄のフォーカス、ダイアログの上端、ヘッダーのナビ、パンくずリスト、押したボタン、画面を覆った面の中央に出す待ちの印(PageLoading) Design Doc の「動き」

画面を作るときの原則

  • COLOR-TOKEN: 色、文字、動きは、テーマ変数のユーティリティだけで書く。色の値とプリミティブトークンの名前を、画面のコードに直に書かない。
    • 確認: 色の値とプリミティブトークンの名前は、チェック(pnpm run check:raw-values)で見る。文字、動きの時間と遅れの値は、レビューで見る。アニメーションと緩急の任意値は、MOTION-TOKEN のチェックで見る。
  • STATE-NOT-COLOR-ONLY: 状態を色だけで伝えない。エラーと注意には、文と印を色と一緒に出す。
    • 確認: レビューで、色を見分けられない人がエラーの場所と内容を読み取れるかを見る。
  • TEXT-MIN-SIZE: 14px より小さい文字を使わない。本文と操作の文字は 16px にし、14px は補足だけに使う。
  • PRIMARY-ACTION-ONE: 主のボタンは、1 つの画面かダイアログに 1 つまでにする。
    • 確認: レビューで、画面の主の操作が 1 つに決まるかを見る。
  • FOCUS-ONE: フォーカスの表示は 1 つのユーティリティ(墨の 2px の 1 重の輪)だけを使い、コンポーネントごとに色や太さを変えない。例外の理由は DesignDoc の「フォーカスの表示」に書いた。
  • TARGET-SIZE: 押せる領域は 44×44 CSS px 以上にする。WCAG 2.2 の AA(2.5.8)の 24×24 より厳しい、AAA(2.5.5)の値を選んでいる。見た目を小さくするときは、見た目の外に押せる領域を広げる。
  • SPACING-SCALE: 余白は、トークンの文書の「レイアウト、区切り、重なり、動き」の表の余白の段のユーティリティだけで書く。任意値(p-[13px] のような書き方)を書かない。
    • 確認: pnpm run check:utilities
    • 入力欄と、その見出し、補足の文、誤りの文の間は、gap-2(8px)以上にする。フォーカスの表示(focus-ring)は、欄の外へ 4px 広がる。狭いと、フォーカスの表示が近くの文字に重なる。
      • 確認: レビューで、欄と近くの文字の間が、その段以上かを見る。
  • BODY-WIDTH: 文字の列(記事の本文、シリーズの説明、トップの Profile)の幅は、テーマ変数の --container-body(40rem)のユーティリティの max-w-body で抑える。max-w-prose を書かない。
    • max-w-prose は Tailwind の 65ch で、幅が本文の書体の「0」の幅で決まる。本文は端末に入っている書体で描くので、1 行の字数が端末ごとにずれる。
    • max-w-prose はテーマ変数を読まず、65ch に固定されている。
    • 確認: pnpm run check:utilities
  • LAYOUT-OVERFLOW: レイアウトのブレークポイントの幅ごとに、ページの横方向のオーバーフローを出さない。長い文字とデータの多い状態でも出さない。
    • 確認: ページの全体の幅は、pnpm run test:ui の src/screens/Playground.test.tsx の、名前に LAYOUT-OVERFLOW を含むテストで見る。320px、767px、768px、1280px の幅を見る。内側の領域で切れた文字とボタンは、撮った画像をレビューで見る。
  • MOTION-TOKEN: アニメーションと緩急は、トークンの文書の「レイアウト、区切り、重なり、動き」の表の動きのユーティリティだけで書く。任意値(animate-[ping_1s_infinite] のような書き方)を書かない。アニメーションの任意値はテーマ変数を通らないので、MOTION-REDUCE の止め方では止まらない。
    • 確認: pnpm run check:utilities
  • MOTION-REDUCE: 利用者が OS で動きを減らす設定(prefers-reduced-motion: reduce)にしたときは、位置、大きさ、回転を動かすアニメーションと遷移、スクロールに合わせた動きを止める。残すのは、状態の変化を伝える短い透明度の変化と、機能に欠かせない動きだけにする。今の止め方は、透明度の変化を含めて、時間で進むアニメーションと遷移を全て止める。WCAG 2.2 の AA に無い、AAA(2.3.3)の基準を選んでいる。止め方はテーマ変数のファイル(src/tokens/theme.css)の末尾の 1 か所に置き、画面のコードに設定ごとの条件を書かない。
    • 例外: スクロールに合わせた動きのうち、scroll-fade-end だけは残す。scroll-fade-end は右に続きがあることを知らせる印で、途中の濃さを出さず、右端まで送ったときに一度に消える。
    • 確認: レビューで、テーマ変数のファイルの止め方を見る。
    • 確認: 動きを減らす設定で描画した画面を調べるテストは、packages/e2e/tests/specs/articles/scroll-box-fade.e2e.ts だけである。この spec は記事のページの検証用の原稿を開くので、原稿を消すときは、開く記事を差し替える。
  • PLAYGROUND-ONLY: コードを動かす画面だけの面、角丸、高さの条件を、コードを動かす画面と、そのコンポーネントの外で使わない。対象は、surface-ground、surface-pane、surface-pane-subtle、rounded-pane、rounded-sheet、rounded-control-compact、short: である。packages/ui の Base では、density="compact" か size="XS" のときだけ使う。例外として、コードを動かす画面だけで使う PageLoading は、density と size によらず、覆いの面に surface-pane を使う。同じく、コードを動かす画面だけで使う Select は、density によらず、欄の面に surface-pane を使う。

原則と確認の方法の対応

ID 確認の方法 チェックが見ないもの
COLOR-TOKEN pnpm run check:raw-values 文字、動きの時間と緩急の値
STATE-NOT-COLOR-ONLY レビュー
TEXT-MIN-SIZE pnpm run check:utilities compact の 4 つの文字のトークンを、コードを動かす画面の外で使った書き方。クラスの名前を文字列の連結で組み立てた書き方
PRIMARY-ACTION-ONE レビュー
FOCUS-ONE pnpm run check:utilities フォーカスの変種に ring、outline、border、shadow を直に書いた書き方。クラスの名前を文字列の連結で組み立てた書き方
TARGET-SIZE pnpm run test:ui(名前に「44 px」、例外は「24 px」か「28 px」、仕切りは「8 px」を含むテスト。触る端末の大きさは、*.coarse.test.tsx をタッチのエミュレーションで流す) Link の 24×24 CSS px と、画面のコードで組んだ押せる要素
SPACING-SCALE pnpm run check:utilities 任意値でない段の選び方。入力欄と近くの文字の間の段
BODY-WIDTH pnpm run check:utilities max-w-body でない段(max-w-xl など)と ch の任意値(max-w-[65ch])で抑えた文字の列。スタイルに直に書いた max-width。クラスの名前を文字列の連結で組み立てた書き方
LAYOUT-OVERFLOW pnpm run test:ui(名前に LAYOUT-OVERFLOW を含むテスト) 内側の領域で切れた文字とボタン。見た目の崩れ。コードを動かす画面のほかの画面(記事の画面)。ダークのテーマ
MOTION-TOKEN pnpm run check:utilities 時間と遅れの任意値。任意のプロパティ([animation:...])と、スタイルに直に書いた animation。クラスの名前を文字列の連結で組み立てた書き方
MOTION-REDUCE レビュー
PLAYGROUND-ONLY pnpm run check:utilities Base の中の、compact の分岐の外での使用。クラスの名前を文字列の連結で組み立てた書き方

画面を作るときの手順

順

  1. 参照先の表、トークンの文書、コンポーネントの文書、カタログから、使うパターン、コンポーネント、トークンを探す。合うものがなければ、画面のコードで作らず、デザインシステムに足すかを尋ねる。
  2. テーマ変数のユーティリティと、このデザインシステムのコンポーネントだけで書く。
  3. 使ったコンポーネントの状態のうち、画面で起きるものを作る。
  4. 下の「流すコマンド」を、表の順に流す。
  5. 描画した画面を、トークンの文書のブレークポイントの幅ごとと、テーマごとに撮り、横方向のオーバーフローを確認する。撮った画像はレビューで見る。
  6. チェックが通らなければ直し、手順 4 から流し直す。チェックの出力を見て自分で直すのは、2 回までにする。
    • 2 回直しても通らないか、違反の数が減らないときは、直すのをやめて止まる。最後に全てのチェックが通ったコミットに、通らない変更を混ぜない。通らない変更は、コミットせずに差分として報告に添える。
    • 全てのチェックが通ったコミットが無いときは、作業を始めたときのコミットを代わりにし、そのときに既に通らなかった原則の ID を報告に書く。
  7. 下の「報告の形」で報告する。止まったときは、最後に通ったコミットの SHA と、通らないまま残った原則の ID を書く。

流すコマンド

コマンド 見るもの 原則の ID
pnpm run check:tokens 2 つの生成物が、トークンと合っているか
pnpm run check:contrast セマンティックトークンの組のコントラスト比
pnpm run check:raw-values 画面のコードの色の値とプリミティブトークンの名前 COLOR-TOKEN
pnpm run check:utilities 画面のコードの、原則に反するユーティリティ。規則の ID がこの文書にあるか TEXT-MIN-SIZE、FOCUS-ONE、SPACING-SCALE、MOTION-TOKEN、BODY-WIDTH、PLAYGROUND-ONLY
pnpm run lint テーマ変数にないクラスと、任意の値のクラス COLOR-TOKEN
pnpm run test:ui コンポーネントの状態、押せる領域の大きさ、コードを動かす画面の幅ごとのページの横方向のオーバーフロー、story のアクセシビリティ TARGET-SIZE、LAYOUT-OVERFLOW

受け入れ条件

  • [ ] 「流すコマンド」が全て終了コード 0 で終わった
  • [ ] 「確認の方法」がレビューの原則を、描画した画面で見た
  • [ ] 幅とテーマごとに撮った画像を、レビューで見た
  • [ ] 使ったコンポーネントの状態が、コンポーネントの文書のそのコンポーネントのセクションと合っている
  • [ ] 報告に、5 つの状態のどれでもない原則がない
  • [ ] 報告に、対象のコミット、コミットしていない変更の有無、デザインシステムの文書のバージョンがある

報告の形

状態 意味
通った チェックが流れ、基準を満たした
通らない 違反の証拠がある。場所と、出たチェックの出力を添える
レビューで見る 描画した画面かコードはあるが、判断はレビューの人がする
見ていない 確認の規則、チェック、証拠のどれかが無い。前のコマンドの失敗で流していないものも含む
対象外 作業の範囲から外れる。理由を添える
項目 書くこと
対象のコミット チェックを流したときの git rev-parse HEAD の SHA
コミットしていない変更 なし、またはあり。ありのときは、変えたファイルの一覧(git status --short の出力)
デザインシステムの文書のバージョン この文書と 3 つの文書のどれかを最後に変えたコミットの SHA(git log -1 --format=%H -- packages/ui/DESIGN.md packages/ui/design/)。どれかにコミットしていない変更があれば、そのことも書く

アクセシビリティ

項目 基準 確認の方法
文字のコントラスト 4.5:1 以上 pnpm run check:contrast
文字でないもののコントラスト 入力欄と押せる枠の境界、フォーカスの表示が 3:1 以上 pnpm run check:contrast(border-field、フォーカスの表示の組)
フォーカスの見え方 全ての操作できる要素で、フォーカスの表示を出す pnpm run check:utilities(FOCUS-ONE)と、レビュー
ターゲットの大きさ 44×44 CSS px 以上。AA の 2.5.8 の 24×24 より厳しい、このプロダクトの選択(AAA の 2.5.5)。コードを動かす画面は例外で、マウスで押す端末は 24×24 以上(ADR-0062)、SplitPane の仕切りは幅 8px(ADR-0073)、触る端末は 44×44 以上(ADR-0071) pnpm run test:ui(TARGET-SIZE)
キーボードの操作 コンポーネントの文書のとおりに動く pnpm run test:ui のコンポーネントのテスト
入力の誤りの伝え方 欄ごとの誤りの文は aria-describedby で欄に結び付け、role="alert" を付けない。送った後は最初の誤りの欄へフォーカスを移す。送れなかったときの文だけを role="alert" で読み上げる pnpm run test:ui の TextField のテスト(欄ごとの誤りの文)。送った後のフォーカスの移り方と、送れなかったときの文は、レビュー
動きを減らす設定 設定が reduce のとき、位置、大きさ、回転を動かすアニメーションと遷移、スクロールに合わせた動きを止める。このプロダクトの選択(AAA の 2.3.3) レビュー(MOTION-REDUCE)
ハイコントラスト forced-colors: active で、状態を枠と塗りの違いで見分けられる。背景の色と ring だけで状態を表さない pnpm run test:ui(*.forced-colors.test.tsx)と、レビュー

ハイコントラスト

Windows のハイコントラスト(forced-colors: active)では、ブラウザが背景の色と影(ring を含む)を消し、色をシステムの色に置き換える。 背景の色と ring だけで境界と状態を表す要素は、見分けられなくなる。

直し方は、src/tokens/theme.css の forced-* のユーティリティを、その要素に足すことである。 通常の表示には何も変わらない。

ユーティリティの一覧と使い方は design/tokens.md にある。 システムの色(CanvasText、GrayText、SelectedItem など)は、ブラウザが決める値で、テーマごとに変わらない。そのため、プリミティブトークンを通さず、theme.css の forced-* に直に書く。

  • フォーカスの輪は outline なので、ハイコントラストでも残る。focus-ring のユーティリティだけで書く。
  • 状態を背景の色か ring だけで表すコンポーネントを足すときは、forced-* を足す。
  • 状態の違いは、各コンポーネントの *.forced-colors.test.tsx が、Chromium を forced-colors: active にして確認する。見た目は、Chromium の DevTools の Rendering で forced-colors: active を再現して、カタログの story を開いて見る。

決まっていない選択

  • カタログのアクセシビリティの自動チェックを、テーマごとに動かすか: 今はデフォルトのテーマ(OS の設定。テストのブラウザではライト)だけで動く。案は、pnpm run test:ui の storybook の project を、ライトとダークの 2 つの条件で流すこと。ダークの組み合わせは pnpm run check:contrast が見ているが、描画した story の違反は見ていないためである。
  • LAYOUT-OVERFLOW のテストを、記事の画面とダークのテーマに広げるか: 今は、コードを動かす画面をライトで見るだけである。案は、記事の画面の story にも同じ幅のテストを足すこと。
  • 動きを減らす設定で、状態の変化を伝える短い透明度の変化を残すか: MOTION-REDUCE は残してよいとしているが、今の止め方は透明度の変化も止める。案は、今のまま全てを止めること。残したい動きが出たときに、止め方の例外として足す。