ダークモードトークンの仕組み

ダークモードトークンとは、ライトテーマと同じセマンティック名に結び付けられた二つ目の値の集合です。モードを切り替えると --color-surface のような名前が別の色を指すようになり、どのモードが有効かをコンポーネント側が知る必要はありません。

反転ではうまくいかない理由

直感的なやり方、つまり明度を反転し白と黒を入れ替えるやり方は、はじめのうちは言葉にしにくい形で「何かおかしい」ダークテーマを生みます。

具体的な理由をいくつか挙げます。

純黒はきつすぎます。 よくできたダークテーマの多くは #000000 ではなく、しばしばわずかに色味を帯びた非常に暗いグレーを使います。純黒の上に最大コントラストの白文字を置くとハレーションが起き、文字がにじんで見えます。

彩度の高い色は暗い背景でより明るく読まれます。 白の上で心地よく収まる青が、暗い背景では振動して見えることがあります。ダークモード用の変形は通常、明るくするだけでなく彩度も落とします。

エレベーションが反転します。 ライトテーマでは、持ち上がった面が影を落とします。ダークテーマでは影がほとんど見えないため、持ち上がった面は逆に*明るく*なることで示されます。片方のモードで機能する影のトークンは、もう片方では情報を運びません。

コントラスト比は反転を生き延びません。 ライトで AA を通る組み合わせが、ダークでは落ちることがあります。すべての組み合わせは再計算ではなく再確認が必要です。

成立させる構造

ダークモードは色の問題である前に、命名の問題です。セマンティック層がすでにある場合にだけ、きれいに片付きます。

:root {
  --color-surface: var(--grey-50);
  --color-text-primary: var(--grey-900);
}

[data-theme="dark"] {
  --color-surface: var(--grey-900);
  --color-text-primary: var(--grey-50);
}

コンポーネントは --color-surface を参照します。モードがあることを知ることはありません。後から三つ目のテーマを足すのも同じ形のブロックを増やすだけで、全コンポーネントの変更にはなりません。

コンポーネントがプリミティブを直接参照していると、指し替える対象がなく、ダークモードはすべてのファイルの条件分岐になります。

両方を同時に生成する

半年後に後付けするダークテーマは、全コンポーネントの棚卸しです。ライトテーマと同時に、同じスケールから作れば、追加コストはありません。

これは、片方しか出さない予定でも最初にモードを決めておくべき最も強い理由です。構造的なコストは命名にあり、命名はコンポーネントができる前なら安く、できた後なら高くつきます。

Flash DSM との関係

生成される体系はライトとダークを同時に作り、同じセマンティック名に結び付け、片方から後付けで導くのではなく組み合わせごとにコントラストを確認します。