世界を動かす技術を、日本語で。

CSSでブラウザに対比色を選ばせる方法

概要

  • contrast-color()関数 により、CSSで自動的に最適なテキスト色(黒または白)を選択できる機能を紹介。
  • 背景色の変化に応じて、テキスト色を手動で管理する手間を大幅に削減可能。
  • 現在の実装では WCAG 2 アルゴリズムが使われており、必ずしも知覚的に最適なコントラストを保証しない。
  • APCA など新しいアルゴリズムの導入が検討されており、将来的な改善が期待される。
  • アクセシビリティ確保には色選びやフォントサイズ・太さの配慮、prefers-contrastメディアクエリの活用が重要。

contrast-color()関数による自動コントラスト色選択

  • CSSの contrast-color()関数 を使うことで、指定した背景色に対して 黒または白 のうちコントラストが高い方を自動選択することが可能。
  • 例:color: contrast-color(purple);と記述することで、purple背景に対し自動的に黒か白のテキスト色を割り当てる提案。
  • ボタンの背景色を変数--button-colorで管理し、テキスト色はcontrast-color(var(--button-color))で自動決定することができる確認。
  • ボタンのホバー時の色変化も、Relative Color Syntax(例:oklch)と組み合わせて一つの変数から複数の配色を管理することが可能。
  • こうした仕組みにより、 大規模プロジェクト でも色管理の手間を削減し、コントラスト問題による可読性低下を防ぐことができる提案。

contrast-color()の実装と制限

  • 現時点(Safari Technology Preview等)では WCAG 2 アルゴリズムに基づいてコントラスト判定を実施すること。
  • WCAG 2 は法的にも標準とされているが、知覚的なコントラストと数学的なコントラストが一致しない場合がある指摘。
  • 例えば、#317CFFの青背景では、WCAG 2では黒テキストが推奨されるが、実際には白テキストの方が見やすいと感じる場合がある説明。
  • APCA(Accessible Perceptual Contrast Algorithm) などの新アルゴリズムがWCAG 3で検討されており、より人間の知覚に近いコントラスト判定が可能となる見込み。
  • contrast-color()関数のアルゴリズムは将来的に切り替えが可能であり、CSS標準でも今後のアップデートが予定されている確認。

コントラストのアクセシビリティと実践的配慮

  • contrast-color()関数の利用だけでは、 十分なコントラスト やアクセシビリティを保証できない警告。
  • 中間色(ミッドトーン)では、黒・白どちらを選んでも十分なコントラストが得られない場合がある指摘。
  • フォントサイズやウェイト(太さ)によっても必要なコントラスト基準は変化するため、デザイン時に総合的に配慮することが重要。
  • prefers-contrastメディアクエリ を用いることで、ユーザーが高コントラストを求めている場合に代替配色を提供することができる提案。
  • APCAスコアはLc値(Lightness contrast)で示され、フォントサイズや太さに応じて適切な閾値が設けられているため、より柔軟なアクセシビリティ対応が可能。

実践例:ブランドカラーとアクセシビリティ両立

  • 例として、明るい緑(#2DAD4E)をブランドカラーにしたボタンデザインを考える場合、APCAスコアやprefers-contrastメディアクエリを活用して配色を調整することが推奨される提案。
  • フォントサイズ24px・ウェイト400以上なら白テキストでも十分なコントラストが得られるが、より小さいサイズや細いフォントでは色を変更することが必要。
  • prefers-contrast: moreが指定された場合には、ダークグリーンやライトグリーンなど、よりコントラストの高い色に切り替えることで、幅広いユーザーのアクセシビリティニーズに対応することができる確認。
  • 変数で色を管理することで、状況に応じた配色の切り替えが容易となり、contrast-color()の活用でテキスト色の自動化も実現できる提案。
  • アクセシビリティは単なる基準のクリアではなく、実際のユーザー体験を重視した柔軟な設計が求められることを強調。

参考:主要CSS記述例

:root {
  --button-color: purple;
  --hover-color: oklch(from var(--button-color) calc(l + .2) c h);
}
button {
  background-color: var(--button-color);
  color: contrast-color(var(--button-color));
  text-box: cap alphabetic; /* テキストの垂直中央揃え */
}
button:hover {
  background-color: var(--hover-color);
  color: contrast-color(var(--hover-color));
}
@media (prefers-contrast: more) {
  /* より高コントラストな配色に切り替え */
}

まとめ

  • contrast-color()関数は、 色管理の効率化アクセシビリティ向上 に大きく貢献する新機能。
  • ただし、現状のアルゴリズムや色選びだけに頼らず、デザイナーや開発者が 実際の可読性やユーザー体験 を重視して調整・確認することが不可欠。
  • 新しいコントラストアルゴリズムやCSS機能の進化を積極的に取り入れ、 柔軟かつ実用的なアクセシビリティ対応 を心がけることが重要。

Hackerたちの意見

大きなプロジェクトで大規模なチームが関わると、こういった細かい部分をしっかり管理するのが本当に難しくなるよね。突然、暗いボタンに読めない黒い文字があって、ユーザーがどうすればいいかわからなくなっちゃう。大きなプロジェクトが出る前に、誰かボタンをチェックしてくれないかな?それか、暗いボタンには絶対に黒い文字を使わないってルールを作って、全てのチームメンバーに伝えるべきだよね。知覚的コントラストと数学的コントラストについて読むのは面白いね。そんなこと知らなかったよ。これを自分のワークフローに取り入れてみるつもり。

APCAについて調べてみるといいよ。APCAアルゴリズムを使って知覚的コントラストの計算ができるから。

大きなプロジェクトが出荷される前にボタンを確認する人はいないの? もちろんできるけど、そうするとリリース前の回帰テストサイクルが数週間や数ヶ月になることがあるんだ。「大きなプロジェクト」には簡単に数千のボタン(それ以上も!)が含まれていて、その多くは特定の設定が有効になっているときや、複雑なワークフローの中で特定のオプションが選ばれたときにしか見えないんだよ。

lchを使えば、これに近いことができる方法があるよ: --text: lch(from var(--bg) calc((49.44 - l) * infinity) 0 0); 参照: https://til.jakelazaroff.com/css/swap-between-black-and-whit...

こんなコールバックスタイルのCSS関数は見たことないよ。パラメータを変更できるのが面白いね!これ以外にも例はあるのかな、それともlchだけの特別なもの?

こんな感じのワークアラウンドについて、lea verouの良い記事があるよ。https://lea.verou.me/blog/2024/contrast-color/

LCHは素晴らしいけど、OKLCHはさらにいいよ!https://evilmartians.com/chronicles/oklch-in-css-why-quit-rg... この話題に関してこの文章が私の視点を劇的に変えたと言えるよ。本当に素晴らしいツールだ。デザイナーの友達が全くOKLCHを知らなかったことに驚いたし、これは全く新しい問題を解決してくれる。

コントラストの色をブラウザのベンダーが決めるべきだとはまだ納得できないな。いつも正しいとも限らないし、予測もできないよね。これが全てのブラウザで決定的な標準になるのかな?この機能は、デザインフェーズでUXチームを助けるためのツールって感じがする。

参照: https://news.ycombinator.com/item?id=44015980、APCAのボタンの例についての混乱から不正確な部分を取り除くと、100%正しいってことが少しクリアになるよね。でも、一貫性はない。例えば、L* 50の背景があって、白か黒の前景がほぼ同じように機能する場合、そこでは美的原則が関わってくる。これを明確にして、100%信頼できるようにするためには、コントラストKと背景色Cが与えられた場合、暗い色と明るい色の両方が利用可能なら、Cを見て、L*が>= 60なら明るい方を選ぶ。そうすれば、100%正しくて一貫性がある。

これが全てのブラウザで決定的な標準になるのかな?記事には、標準が使用する計算を指定していると書いてあるよ。

ここで「選ぶ」って言葉は変だね。色を計算するアルゴリズムがあるんだ。

最低限、ライトテーマとダークテーマのために、擬似クラスのアクティブ、フォーカス、ホバー、リンク、訪問済み、それらの様々な組み合わせに対して良い色を知っておくといいよね。さらに、マテリアルUIは無効、前、後も追加してるし。

似たようなことについてのビデオチュートリアルを昔作ったことがあるよ - カラーバックグラウンドに対してテキストカラーを黒か白に選ぶってやつ。俺の解決策はすごく単純だった。色をグレースケールに変換して、黒と白を比較しただけ。楽しいプロジェクトだったけど、動画作りは得意じゃないんだ。https://youtu.be/tUJvE4xfTgo?si=vFlegFA_7lzijfSR(注意: 動画はポルトガル語です)

面白い!姉妹のコメントがそのためのカラースペースの式を教えてくれたよ。動画は大丈夫そうだね。ただ、ポルトガル語はわからないから、君が言ったことは判断できないけど、コードは良さそうだね!

ビルド時にできる良い代替案はあるかな?SASSやTailwindの上で動くやつ。これが広く使えるようになるまで時間がかかるだろうし、すべてのプラットフォームで同じ(または正しい)方法で実装されるか不安だな。

これの利点と欠点の良い概要だね。シンプルなサイトを作る人には、適切なコントラストを持つ簡単な方法だと思う。でも、WCAG準拠が必要なプロダクション規模のものを作る人には、これを避けて適切なセマンティックトークンレイヤーを活用した方がいいよ。セマンティックトークンは開発サイクルを加速させるし、前景レイヤーを黒や白に切り替えるだけよりも視覚的に良いコントラスト比を保証してくれる。セマンティックトークンレイヤーの良いところは、テーマを簡単に変更できること。つまり、少しの追加コストでライト/ダークテーマが手に入るんだ。もしブランドカラーがWCAG2で問題がある色の一つなら、別のWCAG2 / APCAアクセシブルテーマを作ることもできるよ。これでコンプライアンスを保ちながら、より良い視覚的コントラストオプションを提供できる。これは私の専門分野なんだ。Figmaで変数/トークンのストリームを運営していて、FigmaとAtlassianのダークモード実装にも関わったよ。トークンやテーマ、アクセシブルカラーについて質問があれば喜んで答えるよ。

セマンティックトークンって何を指してるの?このタイプの機能が、CSS in JS(相対色やコントラスト色のため)を使う大規模プロジェクトを引き起こしたんだ。こういうのが出てきて嬉しいし、数年後には広く利用できるのを楽しみにしてるよ。

同意するよ、実際に全く同意なんだけど、最後の2/3はただの意味のないおしゃべりに聞こえる。自分を賢く見せようとしてるだけじゃない?真実じゃないとは言わないけど、言葉の吐き出しみたいだね。この機能は好きだけど、企業のサイトやアプリでは、この機能に頼るのは良くないよ。結果がどうなるかコントロールできないからね。もしかしたら、WebKitが後でバグを修正したり、何かを変更して、望まない色に変わるかもしれないし。

システムカラーが本当にクールだった頃に、いくつかシステムカラースタイルを作ったんだ。見た目はすごく良かったけど、コントラストがどうなるかはわからなかった。その一つは[say] buttonFaceって呼ばれて、もう一つのbuttonTextは意味がなかった。誰かがgetComputedStyleを使ってコントラストを計算するJSを書いてくれたんだ。もし受け入れられない場合は、第二候補の色を使うか、テキストの周りに十分に暗くしたり明るくしたりするためにテキストシャドウにフォールバックしたりした。https://i.sstatic.net/18bQt.png 計算は忘れちゃったけど、考えてみると3つのRGB値の平均を取って比較すればいいんじゃないかな?それだと青の値が低くなって、白いテキストが優先されると思う。

ホットリンクはうまくいかなかった。https://www.freepik.com/free-vector/white-3d-editable-vector...

カラースキームの色を全部選ぶんだから、最初から対照的なボタンのテキスト色を選ぶ方が簡単じゃない?これは、個人が不一致な背景色を自由に選べるほど機能不全なチームを助けるための機能なの?でも同時に、対照的な前景色を選べないってどういうこと?本当に直すべきなのは、画像や他の多様な背景の上にテキストがあるとき(例えば、スクロールする背景の上に固定されたテキスト)で、常に見えるようにする必要があるときだよ。これじゃ全然役に立たない。だから、これが助けになるのは非常に疑わしい状況だけだし、新しい動詞を考え出さなきゃいけなかったり、機能が貧弱(黒か白しか選べない)で、最悪のコントラスト選択アルゴリズムを使ってる(最も視覚的なコントラストを持つ選択肢を選ばない)なんて、どういうことだよ!

そのツールを使ったことがないからって、簡単に否定するのはもったいないよ。たくさんのウェブサイトは、エンドユーザーが色を選べるようにしてるし、エンドユーザーが提供したアセットから自動的に色を導き出すこともある。アクセシビリティを気にする人たちは、ユーザーが非アクセシブルな体験を作らないように、通常は対照的な色を計算するんだ。こういう内蔵CSSツールがあれば、もっと多くのサイトが基本的なアクセシビリティを提供するようになることを願ってるし、より良い体験を作りたい人たちの妨げにはならないはず。npmのcontrast-colorパッケージみたいにもっとカスタマイズ可能だったらいいけど、ブログ記事では白と黒から始めた理由が説明されていて、後でアルゴリズムを変更する意図があるみたいだね。

そして彼らは最悪のコントラスト選択アルゴリズムを使った 彼らは具体的にWCAG 2のアルゴリズムに従っていると言っていて、WCAG 3がこの問題を修正するかもしれないとも言ってる。将来的に標準化されたときに、より良いアルゴリズムに簡単に調整できるとも言ってるよ。

この問題に対して、色のペアが設計上シンプルで予測可能なWCAG/ACPAコントラストを持つパレットを作成するためのツールに取り組んでるよ(デスクトップではもっと機能がある):https://www.inclusivecolors.com/ だから一つのアプローチとしては、グレード100(明るい)からグレード900(暗い)までの異なる色のスウォッチを作成することだね。明るさは、すべてのグレード700の色がグレード100の色と対照的になるように選ばれていて、すべてのグレード800の色がグレード200の色と対照的になるようにしてる。だから、赤-700と灰色-100、緑-800と黄色-200などは、チェックしなくても対照的だってわかるよ。コントラストメニューに行くと、APCAアルゴリズム(より正確であることを目指している)がWCAGと比べてどれだけ厳しいかも探ることができる。特に明るい背景に対して暗い色の場合、APCAはコントラストに関してずっと厳しいから、暗いテーマにはWCAGを使うべきじゃない。さらに、Examplesメニューに行ってTailwindやIBM Carbonのカラーパレットをチェックすると、手作りのパレットでスウォッチがどのようにグレードに沿って彩度や色相を非線形的に変化させるかを見ることができるよ。だから、白か黒が最も対照的かを自動的に選ぶのはもっと簡単だけど(記事が言ってるように)、より意図的でブランド化されたパレットの場合、単純な明るさのシフトだけで色を生成することはできないから、これはもっとオープンエンドだね。