0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Fluent UI / Fluent 2 で raw 値より alias token を使うべき理由 ─ アクセシビリティ運用を強くする設計

0
Posted at

はじめに 🌟

以前、私は Fluent UI / Fluent 2 のアクセシビリティ全体像や、色と WCAG の関係を整理してきました。ですが、実務で意外と見落とされやすいのは 「色をどう選ぶか」ではなく、「色をどう指定するか」 です。

たとえば、#115ea3 のような raw 値(生の 16 進カラー)を直接書くのか、それとも colorBrandBackgroundcolorNeutralForeground1 のような alias token を使うのか。この違いは、単なる書き方の好みではありません。テーマ対応、レビューのしやすさ、状態の一貫性、そしてアクセシビリティの維持コストに大きく効いてきます 🎯

Fluent の公式でも、Design tokensglobal tokens が raw values を保持し、alias tokens が semantic meaning を与える と説明しています。また Color では、uniform color usage を保証する最も簡単な方法は Fluent の design token system を使うことであり、alias tokens によって hex code を探し回らずに適切な色を選びやすくなる と案内されています。

本記事では、前回の「色そのもの」の話から一歩進めて、なぜ app / product code では raw 値より alias token を優先した方がよいのかを、アクセシビリティの観点から整理します。ここを押さえておくと、デザインレビューも PR レビューもかなり強くなります 👍

今回のゴール 🎯

  • ✅ Fluent の global token と alias token の役割の違いを理解する
  • ✅ なぜ raw 値直書きがアクセシビリティ運用を壊しやすいのか整理する
  • ✅ alias token が特に効く WCAG の達成基準を把握する
  • ✅ PR / デザインレビューで確認すべき観点を持ち帰る

先に結論を言うと、alias token は魔法ではありませんが、アクセシビリティを「壊れにくくする」強い仕組みです。まずは Fluent のトークン構造から見ていきます。

背景: Fluent のトークンは 2 層で考える 🧱

Fluent の Design tokens では、トークンは大きく 2 層で説明されています。

  • global tokens: context-agnostic で raw values を保持する層
  • alias tokens: その値に semantic meaning を与える層

つまり、global token は「値そのもの」、alias token は「その値を何の目的で使うか」を表します。

たとえば grey[14]brand[80] のような値寄りの表現は global token 側の発想です。一方で colorNeutralForeground1colorStrokeFocus2 は、「本文テキスト」「フォーカスリング」のような役割に寄っています。

ここで重要なのは、global token が悪で alias token が善、という単純な話ではないことです。Fluent 自体や design system の内部では、global token を使って palette を定義したり、alias token を組み立てたりする必要があります。

ただし、app / product code で日常的に消費する側は、実務上 alias token を基本にした方が安全です。Fluent の一次情報も、その運用を強く支持しています。次の章で、その理由を見ていきます。

なぜ alias token がアクセシビリティに効くのか 🎨

1. 意味がコードに残る

#d13438 と書かれていても、それが本文のエラー文字なのか、装飾用の赤なのか、危険操作ボタンなのかは分かりません。レビュー時には「赤ですね」で止まりがちです。

一方で colorStatusDangerForeground1 なら、これは danger 系の foreground として使う意図なのだなと読み取れます。colorNeutralForeground1 なら、主要な本文色として使う意図が見えます。

この「意図が読める」ことは、アクセシビリティの観点でとても重要です。なぜなら、レビュー対象が 見た目の色 から 役割と状態 に変わるからです。ここが、単なるスタイル指定との大きな違いです。

2. light / dark / high-contrast で壊れにくい

Fluent の Design tokens は、token system 全体が light / dark / high-contrast / branded elements の OS theming を支え、さらに sufficient color contrast across the system を確保するための土台だと説明しています。

また Color でも、shared colors は dark mode で彩度や明度を調整し、視認性や目の負担に配慮するとされています。

ここで raw 値を直接書くと、この仕組みから外れやすくなります。

  • light では見えていた文字が dark で埋もれる
  • focus ring が high-contrast で弱くなる
  • selected / hover / pressed の差分がテーマごとに不揃いになる
  • ブランド変更時に一部だけ古い色が残る

alias token は、こうしたテーマ差分を役割単位で吸収しやすいのが強みです。もちろん最終確認は必要ですが、raw 値よりずっと壊れにくくなります。

3. 状態とフォーカスを一貫させやすい

Fluent の Color では、interaction states と focus states の考え方も整理されています。特に focus では、control 自体の色を変えるのではなく、container の太い stroke で明確に区別するという考え方が示されています。

この設計を app 側で raw 値の寄せ集めで再現すると、かなり高い確率で drift が起きます。

  • ある画面は 1px の薄い青
  • ある画面は 2px の灰色
  • ある画面は outline: none
  • ある画面は hover と focus が見分けにくい

alias token を使うと、focus は focus 用 token、stroke は stroke 用 token、status は status 用 token という整理がしやすくなります。結果として、キーボード利用者にとっての見え方が安定しやすくなります ⌨️

4. 監査・保守・ブランド変更に強い

raw 値は、短期的には速く見えます。ですが、数か月後に効いてくるのは保守コストです。

  • 同じ意味なのに画面ごとに微妙に違う色になる
  • どの色が本文用でどの色が装飾用か分からなくなる
  • 監査時に「この #8a8886 は何のための色ですか?」が大量発生する
  • ブランド刷新で置換漏れが出る

alias token は、意味を持った色指定を中央集権的に扱いやすくする仕組みです。だからこそ、監査で追いやすく、設計変更にも耐えやすいです。アクセシビリティは一度達成して終わりではなく、維持し続ける運用なので、ここはとても大きいです。

次は逆に、raw 値を直接使うと何がつらいのかを、もう少し露骨に見てみます。

raw 値を直接使うと何が起きるか ⚠️

raw 値直書きの問題は、「今たまたま見える」ことと「今後も正しく保てる」ことを混同しやすい点です。

観点 alias token raw 値
🎯 意味の明確さ 役割が名前に出る 色の意図が読めない
🌗 テーマ移植性 light / dark / high-contrast に追随しやすい モード切替で破綻しやすい
🔍 レビューしやすさ 「この token はこの用途で妥当か」を見られる 見た目レビューに寄りやすい
🧩 状態の一貫性 hover / pressed / focus を揃えやすい 画面ごとに drift しやすい
🛠️ 保守性 token 側の変更を反映しやすい 置換漏れ・残骸が出やすい
♿ アクセシビリティ監査 意味単位で追跡しやすい 監査対象が散らばる

特に危険なのは、一見アクセシブルそうに見える raw 値です。たとえば light mode では 4.5:1 を満たしていても、dark mode や branded theme では足りなくなることがあります。しかも raw 値は semantic intent を持たないので、「なぜその色なのか」が残りません。

alias token は自動 WCAG 達成ボタンではありません。
それでも raw 値より強いのは、意味・状態・テーマ差分をシステムに寄せられるからです。アクセシビリティを「人の記憶」ではなく「設計の仕組み」で支えやすくなります。

ここまでの話を、WCAG の達成基準と結び付けて整理してみます。

WCAG との関係を整理する 📋

Fluent の Accessibility では、次の数値が明示されています。

  • 標準テキスト: 4.5:1
  • 大きい文字: 3:1
  • interactive / non-text components: 隣接色に対して 3:1

このうち、数値として直接つながりやすいのは次の達成基準です。

さらに、フォーカス表示の一貫性という観点では次も強く関係します。

そして、色だけに依存しない設計という観点では次も外せません。

対応関係を表で置くと、次のようになります。

WCAG 達成基準 alias token が助けること それだけでは足りないこと
🔤 1.4.3 Contrast (Minimum) 本文・補助文・ブランド文字などを semantic に選びやすくし、テーマ変更時のコントラスト drift を減らす 実際の背景との組み合わせ確認は必要
🔲 1.4.11 Non-text Contrast 境界線、アイコン、選択状態、入力枠、トグルなどの見え方を役割ベースで揃えやすい 状態差そのものが十分に見えるかは画面で要確認
⌨️ 2.4.13 Focus Appearance focus 用 stroke / ring を専用 token で統一しやすく、 focused / unfocused の差を維持しやすい 面積要件と最終的な 3:1 は UI 全体で確認が必要
🌈 1.4.1 Use of Color semantic color の乱用を抑え、色の意味を設計レビューしやすくする 色だけで伝えないこと自体は別途必要。文言、アイコン、形、下線などを併用する必要がある

1.4.3 Contrast (Minimum) にどう効くか

WCAG 2.2 Understanding 1.4.3 は、通常テキスト 4.5:1、大きい文字 3:1 を求めています。Fluent 側も Accessibility で同じ数値を示しています。

alias token の利点は、「本文」「補助」「on brand」などの意味ごとに色を選べることです。raw 値だと「たまたま薄いグレーを置いた」になりやすいですが、alias token なら「主要本文なのに弱すぎる token を当てていないか」というレビューができます。

1.4.11 Non-text Contrast にどう効くか

WCAG 2.2 Understanding 1.4.11 は、意味のある UI コンポーネントや状態が 隣接色に対して 3:1 を満たすことを求めています。

これは、テキストよりもむしろ 境界線、チェック、選択状態、アイコン、フォーカス表示 で落としやすい基準です。alias token を使うと、たとえば neutral stroke、focus stroke、status foreground のように用途を分けやすくなり、「薄い灰色をなんとなく border に使う」を減らせます。

2.4.13 Focus Appearance にどう効くか

WCAG 2.2 Understanding 2.4.13 は、focus indicator の面積と、focused / unfocused 間の 少なくとも 3:1 の差を求めています。

Fluent の Color でも、focus ではコントロール本体の色ではなく、より太い stroke で区別する方向が示されています。ここで raw 値を画面ごとに直書きすると、focus 表示は真っ先に壊れます。

alias token を使うと、「focus 表示は focus 用 token に寄せる」というチームルールを作りやすくなります。これは 2.4.13 に対してかなり実務的な効き方です。ただし、面積要件まで含めて pass するかは実画面で別途確認が必要です。

1.4.1 Use of Color には「間接的に」効く

WCAG 2.2 Understanding 1.4.1 は、色を唯一の視覚的手段にしないことを求めています。Fluent の Color でも、low-visibility や color-blindness に配慮して十分な contrast を確保すること、可能なら personalize できること、色だけで伝えないことが明記されています。

ただし、ここは誤解しやすいです。alias token を使っただけでは 1.4.1 は満たせません。

エラーを colorStatusDangerForeground1 にしたとしても、テキスト説明やアイコンがなければ「赤いだけ」です。多くの場合は、文言・アイコン・形・下線のような追加の視覚的手段を併用した方が安全です。なお WCAG は、条件を満たす 3:1 以上の明度差も追加の視覚的区別として扱えると説明していますが、たとえば「有効は緑、無効は赤」のような設計をそれだけで済ませるのは危険です。

alias token が助けるのは、danger を decoration ではなく danger として扱うレビューがしやすくなることです。適合の最終責任は、あくまで情報設計側にあります。

次は、実装コードで bad / better を並べてみます。

コードで見る: raw 値より alias token がよい例 💻

以下は、あえて単純化した比較です。実際の API や token 名は使用ライブラリのバージョンで差があり得ますが、考え方は共通です。

raw 値を直接使う例

import { makeStyles } from '@fluentui/react-components';

const useStyles = makeStyles({
  card: {
    backgroundColor: '#ffffff',
    color: '#242424',
    border: '1px solid #d1d1d1',
  },
  errorText: {
    color: '#d13438',
  },
  primaryButton: {
    backgroundColor: '#115ea3',
    color: '#ffffff',
  },
  focusable: {
    outline: '2px solid #9c9c9c',
  },
});

この書き方の問題は、それぞれの色が何の役割なのかがコード上で分からないことです。light / dark / high-contrast に切り替わったときに、この組み合わせがそのまま成立する保証もありません。

alias token を使う例

import { makeStyles, tokens } from '@fluentui/react-components';

const useStyles = makeStyles({
  card: {
    backgroundColor: tokens.colorNeutralBackground1,
    color: tokens.colorNeutralForeground1,
    borderColor: tokens.colorNeutralStroke1,
    borderStyle: 'solid',
    borderWidth: '1px',
  },
  errorText: {
    color: tokens.colorStatusDangerForeground1,
  },
  primaryButton: {
    backgroundColor: tokens.colorBrandBackground,
    color: tokens.colorNeutralForegroundOnBrand,
  },
  focusable: {
    outlineColor: tokens.colorStrokeFocus2,
    outlineStyle: 'solid',
    outlineWidth: '2px',
  },
});

こちらは、「本文」「背景」「danger」「on brand」「focus」という役割が見えます。レビューでも「このコンポーネントの focus は focus token を使っているか」「danger が decoration に使われていないか」といった話ができます。

さらに大事な改善: 色だけで終わらせない

1.4.1 の観点では、色指定を alias token にしただけでは不十分です。たとえばエラーなら、こうしておきたいです。

<Field
  validationState="error"
  validationMessage="メールアドレスの形式が正しくありません"
/>

あるいはカスタム UI でも、エラー文言・アイコン・関連付けをセットで考えるべきです。色はその一部であって、全部ではありません。

app / product code では alias token を基本にする 🧭

ここで強調したいのは、「global token をどこでも使うな」ではないという点です。

design system の内部や token 定義の層では、global token は必要です。palette を持ち、そこから semantic な alias を組み立てるのが design token system だからです。

ただし、アプリケーションや業務画面、プロダクト個別機能のコードでは、通常は次の原則が扱いやすいです。

  • app / product code は alias token を基本にする
  • raw 値や palette 直参照は例外扱いにする
  • 例外が必要なら、理由をコメントや design review で明示する

この運用にしておくと、brand refresh や dark mode 対応、high-contrast 対応のときに「どこが semantic intent を外れているか」を見つけやすくなります。つまり、アクセシビリティを守るための変更がしやすくなるわけです。

PR / デザインレビューのチェックリスト ✅

最後に、私が実務で確認したいチェック項目を短くまとめます。

  • 色指定が raw 値ではなく alias token ベースになっている
  • 本文色・背景色・境界線・focus 表示が、それぞれ役割に合う token を使っている
  • error / success / warning が 装飾ではなく意味のある用途で使われている
  • エラーや状態変化が 色だけ で伝えられていない
  • 通常テキスト 4.5:1、大きい文字 3:1 を確認している
  • アイコン、境界線、選択状態、入力枠、focus 表示が 3:1 を下回っていない
  • focus indicator が見えるだけでなく、focused / unfocused の差が十分にある
  • light / dark / high-contrast の少なくとも主要画面を確認している
  • raw 値を使う例外があるなら、理由と所有者が明確になっている

このチェックリストがあるだけでも、「色を選ぶレビュー」から「アクセシビリティを保てる設計かどうかのレビュー」へ進みやすくなります。

まとめ 📝

Fluent UI / Fluent 2 における alias token の価値は、単に「見た目を統一できる」ことではありません。意味を持った色指定を通して、アクセシビリティを壊れにくくすることにあります。

Fluent の一次情報を並べると、次の流れが見えてきます。

  • Design tokens は、global token が raw values、alias token が semantic meaning を担う
  • Color は、uniform color usage を保証する最も簡単な方法として design token system を勧めている
  • 同じく Color は、十分な contrast、色の personalizability、色だけに依存しないことを挙げている
  • Accessibility は、標準テキスト 4.5:1、大きい文字 3:1、interactive / non-text 3:1 を明示している
  • WCAG では 1.4.31.4.112.4.131.4.1 がこの話と強くつながっている

つまり、alias token は WCAG 適合を自動保証するものではないが、WCAG を継続的に満たしやすい設計と運用を支える、ということです。

もしチームで Fluent を使っているなら、まず見直したいのは「色の正しさ」そのものより、色指定の入口が raw 値になっていないかです。そこを alias token に寄せるだけでも、テーマ対応・レビュー性・保守性・アクセシビリティの全部が少しずつ良くなります。私はここが、Fluent の design token system を採用する一番実務的な理由だと考えています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?