🫑tech.miyabitti
Markdownmdで開く

OKLCHで設計する、コントラスト比に基づいた実践グレースケール&同系色パレット

OKLCHの知覚均一な明度軸を活用し、WCAG 2.xコントラスト比とAPCA Lc値の具体的な数字をもとに、見やすいグレースケール・同系色パレットを設計する方法を解説します

目次

コントラスト比を数値で担保するパレット設計

UIのカラーパレットを組む際、背景色と文字色のコントラスト比は目視の感覚だけに頼らず、アクセシビリティ基準に沿った数値で担保しておくと後々のデザイン破綻を防げます。しかし、従来のRGBやHSLでは数値と人間の知覚が一致せず、色相を変えるたびにコントラスト比を再計算して微調整を繰り返すことになりがちでした。

人間の色知覚に最適化された色空間であるOKLCHを利用すると、知覚的に均一な明度(Lightness)軸を基準にして、再現性の高いグレースケールや同系色のカラーパレットを論理的に組み立てられます。

本稿では、OKLCHの明度軸をベースに、WCAG 2.xのコントラスト比やAPCAの基準を満たすグレースケールパレット、および同系色(ブルー系など)パレットの具体的な構築手順をまとめます。

OKLCHがパレット設計に適している理由

L・C・Hの3軸

OKLCHは、Björn Ottosson氏が2020年に発表したOKLab色空間の円筒座標表現で、人間の色知覚に基づいた色空間です。以下の3つの軸で色を表現します。

軸名前範囲説明
LLightness(明度)0〜10が黒、1が白。知覚的に均一なスケール
CChroma(彩度)0〜約0.40が無彩色(グレー)、大きいほど鮮やか
HHue(色相)0〜360色の種類(赤・青・緑など)

CSSでは oklch() 関数で記述します。

/* 明度55%、彩度0.15、色相250°(青) */
color: oklch(0.55 0.15 250);
/* グレースケール: Cを0にするだけ */
background: oklch(0.97 0 0);

なぜHSLではなくOKLCHなのか

HSLにも L(Lightness)パラメータがありますが、HSLの明度は人間の視覚特性を考慮していないため、知覚的には均一ではありません。例えば、hsl(60, 100%, 50%)(黄色)と hsl(240, 100%, 50%)(青)はどちらも L=50% ですが、黄色のほうが圧倒的に明るく見え、青は沈んで見えます。

一方、OKLCHの L は人間の明るさ知覚に合わせて設計されているため、L値が同一であれば、色相(H)が変わってもほぼ同一の明るさとして知覚されます。

以下のデモで、同じ明度指定(HSL L=50% と OKLCH L=0.65)における色相ごとの見え方と、彩度を落としたモノクロ階調の違いを確認できます。

⚠️ HSL(L = 50% で統一)

数値上の明度はすべて50%ですが、人間の目には黄色や緑が極端に明るく、青が沈んで見えます。

青 (H=240)
モノクロ化
hsl(240 100% 50%)
緑 (H=120)
モノクロ化
hsl(120 100% 50%)
黄 (H=60)
モノクロ化
hsl(60 100% 50%)
赤 (H=0)
モノクロ化
hsl(0 100% 50%)
✅ OKLCH(L = 0.65, C = 0.15 で統一)

知覚均一性:色相を変えても全体の明るさ感が均等に揃い、モノクロ化しても階調が一定です。

青 (H=250)
モノクロ化
oklch(0.65 0.15 250)
緑 (H=145)
モノクロ化
oklch(0.65 0.15 145)
黄 (H=95)
モノクロ化
oklch(0.65 0.15 95)
赤 (H=25)
モノクロ化
oklch(0.65 0.15 25)

モノクロ化した際、HSLは色相によって白から黒に近いグレーまでばらつきが生じるのに対し、OKLCHはすべての色相で均等な階調に揃っていることが分かります。この性質があるため、パレットの明度ステップを一度決めてしまえば、他の色相にもそのまま流用しやすくなります。

Note

OKLCHの設計背景や詳細な色空間の仕組みについては、考案者のBjörn Ottosson氏による解説 A perceptual color space for image processing や、Evil Martiansの OKLCH in CSS: why we moved from RGB and HSL で詳しく解説されています。

コントラスト比の計算と評価基準

WCAG 2.xの計算方法

WCAG 2.xでは、2色間のコントラスト比を以下の式で定義しています(Success Criterion 1.4.3 Contrast (Minimum))。

コントラスト比 = (L1 + 0.05) / (L2 + 0.05)

ここで L1 は明るい方の色の相対輝度、L2 は暗い方の相対輝度です。相対輝度はsRGB値からガンマ補正を解除(リニア化)した上で、以下の係数で算出されます。

L = 0.2126 × R + 0.7152 × G + 0.0722 × B

結果は 1:1(同一色)から 21:1(純粋な白と黒)の範囲を取ります。

Note

WCAG 2.xのコントラスト比計算はsRGB色空間に基づいています。OKLCHで設計したパレットも、最終的にはsRGBに変換してコントラスト比を検証する必要があります。計算式の詳細は WCAG 2.2 – Relative Luminance の定義 を参照してください。

WCAG 2.xの閾値

基準コントラスト比対象
AA(通常テキスト)4.5:1 以上本文テキスト(18pt未満)
AA(大きなテキスト)3:1 以上見出し(18pt以上 or 14pt太字以上)
AAA(通常テキスト)7:1 以上高コントラスト要件
UIコンポーネント3:1 以上アイコン、ボーダー等(SC 1.4.11)

WCAG 2.xの課題

WCAG 2.xの計算式は広く普及している一方で、人間の実際の視覚特性との乖離がいくつか指摘されています。

  • 極性(ポラリティ)が考慮されない: 白背景に暗い文字を置く場合と、黒背景に明るい文字を置く場合では、同じ計算値でも読みやすさの体感に差が出ます。
  • フォントサイズやウェイトへの追従が粗い: 基準が「通常テキスト(4.5:1)」と「大きなテキスト(3:1)」の2区分しかなく、細いフォントや極太フォントの違いが反映されません。
  • ダークモードでの過剰判定: 暗い背景に中間色のテキストを配した際、実際には問題なく読めるコントラストであっても計算上は不合格判定になりやすい傾向があります。

こうした知覚上の課題を改善するために考案されたのが、次期アルゴリズム候補として開発されているAPCAです。

APCA(次世代のコントラスト評価)

視覚特性に即したLc値の算出

APCA(Advanced Perceptual Contrast Algorithm)は、人間の空間周波数特性やコントラスト感度関数を考慮したコントラスト評価手法です。Andrew Somers氏(Myndex)が開発を進めており(GitHubリポジトリ)、WCAG 2.xの単純比率に代わってLc値(Lightness Contrast)という指標を用います。

APCAには以下のような特徴があります。

  • 明暗の極性(ライトモード/ダークモード)に応じて計算式を切り替える
  • テキストのフォントサイズやウェイト(太さ)と連動して必要なLc値を算出する
  • OKLCHと同様に、人間の明るさ知覚に基づいた非線形変換を行う

Lc値の推奨閾値

APCA Readability Criterion で示されている主な推奨値は以下の通りです。

用途最小Lc値フォント目安
本文テキスト(推奨)Lc 9016px / 400
本文テキスト(最小)Lc 7518px / 400
非本文テキストLc 60キャプション、ラベル
見出しLc 4536px / 400 や 24px / 700
プレースホルダーLc 30補助的テキスト
非テキスト要素Lc 15装飾的ボーダー等

Important

2026年時点で、APCAはWCAG 3.0の正式仕様として採択されていません。初期ドラフトに含まれていたものの、ワーキンググループでの合意形成に至らず見送られています(Adrian Roselli – “Using APCA as a WCAG 3 Failure”)。したがって法的なアクセシビリティ対応が求められるプロジェクトでは、引き続きWCAG 2.x(4.5:1 / 3:1)の数値を優先しつつ、視覚的な微調整の指標としてAPCAを参照するのが安全です。

OKLCHで設計するグレースケールパレット

OKLCHの L 軸を使って、コントラスト比の検証に基づいた実用的なグレースケールパレットを設計します。

パレット定義

グレースケールでは C = 0(無彩色)なので、L の値だけでパレット全体が決まります。以下は、Tailwind CSS v4のカラーパレットを参考にした11段階のパレットです。

グレースケールパレット(C = 0)

50oklch(0.97 0 0)
100oklch(0.93 0 0)
200oklch(0.87 0 0)
300oklch(0.78 0 0)
400oklch(0.68 0 0)
500oklch(0.55 0 0)
600oklch(0.45 0 0)
700oklch(0.37 0 0)
800oklch(0.27 0 0)
900oklch(0.21 0 0)
950oklch(0.14 0 0)

コントラスト比の検証

このパレットから代表的な背景色×文字色の組み合わせを取り、WCAG 2.xの基準で検証した結果が以下です。

ライトモード

組み合わせコントラスト比AA通常AA大AAA
背景50 × テキスト90016.24:1✅✅✅
背景50 × テキスト80013.82:1✅✅✅
背景50 × テキスト7009.56:1✅✅✅
背景50 × テキスト6006.82:1✅✅❌
背景50 × テキスト5004.45:1❌✅❌
背景50 × テキスト4002.64:1❌❌❌
カード100 × テキスト90014.42:1✅✅✅
カード100 × テキスト7008.49:1✅✅✅
背景200 × テキスト90011.96:1✅✅✅

ダークモード

組み合わせコントラスト比AA通常AA大AAA
背景950 × テキスト5018.25:1✅✅✅
背景950 × テキスト10016.20:1✅✅✅
背景950 × テキスト20013.43:1✅✅✅
背景950 × テキスト3009.95:1✅✅✅
背景950 × テキスト4006.91:1✅✅❌
カード900 × テキスト5016.24:1✅✅✅
カード900 × テキスト10014.42:1✅✅✅
カード800 × テキスト10012.26:1✅✅✅

実際に見て比較する

上記の数値を実際のUI要素としてレンダリングし、可読性を比較してみます。

テキストのコントラスト比較

❌ コントラスト不足 — 2.64:1

色彩はデザインの基盤です。適切なコントラスト比を確保することで、すべてのユーザーにとって読みやすいUIを実現できます。

背景: Gray-50 (L=0.97) / 文字: Gray-400 (L=0.68)

✅ AA準拠 — 16.24:1

色彩はデザインの基盤です。適切なコントラスト比を確保することで、すべてのユーザーにとって読みやすいUIを実現できます。

背景: Gray-50 (L=0.97) / 文字: Gray-900 (L=0.21)

❌ コントラスト不足 — 4.45:1 — AA未達

Gray-500(L=0.55)はAA通常をわずかに下回る4.45:1。1ステップ暗い600(L=0.45)にするだけで6.82:1まで改善されます。

背景: Gray-50 (L=0.97) / 文字: Gray-500 (L=0.55)

✅ AA準拠 — 6.82:1 — AA達成

Gray-500(L=0.55)はAA通常をわずかに下回る4.45:1。1ステップ暗い600(L=0.45)にするだけで6.82:1まで改善されます。

背景: Gray-50 (L=0.97) / 文字: Gray-600 (L=0.45)

ダークモードの比較

❌ コントラスト不足 — 2.82:1

色彩はデザインの基盤です。適切なコントラスト比を確保することで、すべてのユーザーにとって読みやすいUIを実現できます。

背景: Gray-950 (L=0.14) / 文字: Gray-600 (L=0.45)

✅ AA準拠 — 16.20:1

色彩はデザインの基盤です。適切なコントラスト比を確保することで、すべてのユーザーにとって読みやすいUIを実現できます。

背景: Gray-950 (L=0.14) / 文字: Gray-100 (L=0.93)

カード型UIでの比較

❌ Gray-300 × Gray-200 — 読めない

記事タイトル

この記事の説明文です。コントラスト比が不十分だと、テキストが背景に溶け込んでしまいます。

#CSS2026年8月21日
✅ Gray-900 × Gray-600 — はっきり読める

記事タイトル

この記事の説明文です。十分なコントラスト比が確保されていると、テキストがはっきり読めます。

#CSS2026年8月21日

数値検証から得られる設計基準

上記のコントラスト比テーブルから、実務で使えるいくつかの基準が見えてきます。

  • 本文テキストには L の差を 0.70 以上確保する
    背景50(L=0.97)に対してテキスト900(L=0.21)を置くと、L差は0.76となりコントラスト比は16.24:1に達します。これにより余裕をもってAAA基準(7:1)をクリアできます。
  • 中間色の500番(L=0.55)はAA基準の境界線
    背景50(L=0.97)と500番の組み合わせは4.45:1となり、わずかに通常テキストのAA基準(4.5:1)を下回ります。本文テキストとして使用する場合は、1ステップ暗い600番(L=0.45以下)を指定するのが安全です。
  • 400番以下はテキスト用途を避ける
    背景50に対する400番(L=0.68)は2.64:1にとどまり、大テキスト基準(3:1)も満たせません。ボーダーや無効状態、装飾的アイコンなどの用途に限定します。
  • ダークモードでは400番が補助テキストに適する
    背景950(L=0.14)に対する400番(L=0.68)はコントラスト比6.91:1となり、AA基準をクリアします。ダークテーマ時のサブテキストや補足ラベルとして使いやすい明るさです。

CSS Custom Propertiesでの実装

:root {
/* グレースケールパレット */
--gray-50: oklch(0.97 0 0);
--gray-100: oklch(0.93 0 0);
--gray-200: oklch(0.87 0 0);
--gray-300: oklch(0.78 0 0);
--gray-400: oklch(0.68 0 0);
--gray-500: oklch(0.55 0 0);
--gray-600: oklch(0.45 0 0);
--gray-700: oklch(0.37 0 0);
--gray-800: oklch(0.27 0 0);
--gray-900: oklch(0.21 0 0);
--gray-950: oklch(0.14 0 0);
/* セマンティックトークン(ライトモード) */
--color-bg: var(--gray-50);
--color-bg-card: var(--gray-100);
--color-border: var(--gray-200);
--color-text-muted: var(--gray-500);
--color-text: var(--gray-900);
--color-text-heading: var(--gray-800);
}
@media (prefers-color-scheme: dark) {
:root {
--color-bg: var(--gray-950);
--color-bg-card: var(--gray-900);
--color-border: var(--gray-800);
--color-text-muted: var(--gray-400);
--color-text: var(--gray-100);
--color-text-heading: var(--gray-50);
}
}

トークンを定義する際は、プリミティブトークン(--gray-XXX)とセマンティックトークン(--color-text など)を階層分離しておくと保守しやすくなります。ダークモードへの切り替え時も、セマンティックトークンの参照先を入れ替えるだけで、コントラスト比を維持したまま配色を反転できます。

同系色パレットの設計法

グレースケール(C=0)で確立した明度設計は、色相(H)と彩度(C)を加えたブランドカラーやアクセントカラーのパレットにもそのまま適用できます。

原則:Lの差でコントラストを確保する

同系色パレットにおいても、コントラスト比を支配するのは明度(L)の差です。色相(H)を固定し、彩度(C)を持たせたままグレースケールと同じ L 値のステップを踏襲すれば、同様のコントラスト比を再現できます。

OKLCHではL値の差が知覚的な明るさの差と直結しているため、色相を切り替えても、同じLステップを保つ限り、同等のコントラスト比を維持できます。

彩度カーブの設計

彩度(C)を全ステップで一定にすると、明度の高い色(50番台)と低い色(900番台)が不自然に鮮やかになります。人間の目は、極端に明るい色や暗い色の彩度を知覚しにくいためです。

推奨される彩度カーブは山型で、中間の明度で最大になり、両端に向かって小さくなるのが自然です。Huetoneなどのツールを使うと視覚的に確認しながら設計できます。

C
0.15 | ●
0.12 | ● ●
0.09 | ● ●
0.06 | ● ●
0.04 | ● ●
0.02 |● ●
0.01 ● ●
┗━━━━━━━━━━━━━━━━━━━━━━━
50 100 200 300 400 500 600 700 800 900 950

ブルー系パレットの具体例

色相 H = 250(青)でパレットを組んだ場合の具体例です。

ブルー系パレット(H = 250)

50oklch(0.97 0.01 250)
100oklch(0.93 0.02 250)
200oklch(0.87 0.04 250)
300oklch(0.78 0.08 250)
400oklch(0.68 0.12 250)
500oklch(0.55 0.15 250)
600oklch(0.45 0.14 250)
700oklch(0.37 0.12 250)
800oklch(0.27 0.09 250)
900oklch(0.21 0.06 250)
950oklch(0.14 0.04 250)

同系色でもコントラスト比はほぼ同じ

このブルー系パレットのコントラスト比を検証すると、グレースケールとほぼ同じ数値が得られます。

組み合わせコントラスト比AA通常AA大
Blue-50 × Blue-90016.26:1✅✅
Blue-50 × Blue-7009.49:1✅✅
Blue-950 × Blue-10016.23:1✅✅
Blue-950 × Blue-20013.48:1✅✅

グレースケールの同等ペア(Gray-50 × Gray-900 = 16.24:1)と比較すると、L値を揃えている限り、色相や彩度を変えてもコントラスト比はほぼ変わりません。知覚均一な色空間ならではの大きな利点です。

ブルー系での実際のレンダリング

❌ コントラスト不足 — 2.64:1

同系色でも、L値の差を十分に確保すれば読みやすいテキストを実現できます。色相を統一したブランドカラーパレットでも、アクセシビリティは両立可能です。

背景: Blue-50 (L=0.97) / 文字: Blue-400 (L=0.68)

✅ AA準拠 — 16.26:1

同系色でも、L値の差を十分に確保すれば読みやすいテキストを実現できます。色相を統一したブランドカラーパレットでも、アクセシビリティは両立可能です。

背景: Blue-50 (L=0.97) / 文字: Blue-900 (L=0.21)

同系色パレット作成時のポイント

  1. L値の階調ステップをグレースケールと共通化する: グレースケール側でコントラスト比を検証済みのL値を流用することで、カラー展開時の計算工数を削減できます。
  2. 彩度は中明度をピークとする山型カーブにする: 極端に明るい階調(50〜100)や暗い階調(900〜950)で彩度を高めすぎると不自然な色味になりやすいため、400〜600の中間域を最大値に設定します。
  3. sRGB色域(ガモット)のクリッピングに注意する: OKLCHはP3などの広色域も指定できますが、一般的なsRGBディスプレイ環境では域外の色が近似値に丸め込まれます(クランプ)。高彩度なイエローやシアンを扱う際は、sRGB範囲内に収まっているかをカラーピッカー等で確認しておくと安心です。
  4. 最終検証はsRGB変換値で行う: 現行のWCAG 2.x基準はsRGBの相対輝度に基づいているため、最終的な合否判定は変換後のsRGB値で行います。

実践テクニック

color-mix() による動的調整

CSS color-mix() を使えば、パレット外の中間色を動的に生成できます。

/* gray-100とgray-200の中間色 */
border-color: color-mix(in oklch, var(--gray-100), var(--gray-200));
/* 白を40%混ぜて明るくする */
background: color-mix(in oklch, var(--blue-500), white 40%);

in oklch を指定することで、OKLCHの知覚均一な空間で補間が行われます。in srgb と比較すると、中間色が「泥っぽく」ならない利点があります。

相対カラー構文

CSS相対カラー構文(Relative Color Syntax)を使えば、既存の色の L だけを変更するといった操作も可能です。

/* ベースカラーの明度だけを0.2に変更 */
--dark-variant: oklch(from var(--brand-color) 0.2 c h);
/* 明度を20%下げる */
--darker: oklch(from var(--brand-color) calc(l - 0.2) c h);

ダークモードの設計パターン

グレースケールパレットの場合、ダークモードではLの対応を反転させるだけで切り替えが可能です。

ライトモード用途ダークモード
50 (L=0.97)背景950 (L=0.14)
100 (L=0.93)カード背景900 (L=0.21)
200 (L=0.87)ボーダー800 (L=0.27)
900 (L=0.21)テキスト100 (L=0.93)

ダークモード設計では単純な反転にとどめず、コントラスト比の実測値を基準に微調整します。強いコントラストによる眩惑感を和らげるため、本文テキストには純白(L=1.0)を避け、50番(L=0.97)や100番(L=0.93)を当てるのが実用的です。

フォールバック

OKLCHは主要なモダンブラウザでサポートされていますが、古いブラウザ向けにフォールバックを提供する場合は @supports を活用します。

:root {
/* フォールバック(sRGB) */
--gray-900: #181818;
/* OKLCH対応ブラウザ向け */
@supports (color: oklch(0 0 0)) {
--gray-900: oklch(0.21 0 0);
}
}

まとめ

OKLCHの知覚均質な明度軸(L)を利用することで、カラーパレット設計におけるコントラスト比の確保を「勘と微調整の繰り返し」から「再現可能なステップ」へと移行できます。

  • グレースケールで検証したL値の階調をそのまま他の色相へ適用できるため、ブランドカラーや機能色(Success/Warning/Error)への展開が容易になります。
  • 本文テキストと背景色にはL値の差を0.70以上確保することで、WCAG 2.x AA基準を確実に満たし、AAA基準も達成できます。
  • 公式ガイドライン準拠にはWCAG 2.xを用いつつ、実際の視覚的なバランス調整にはAPCAのLc値を併用するのが効果的です。

デザインシステムやトークン設計の基盤としてOKLCHを導入する際の基準値としてご活用ください。

参考リンク