カラーシステム: RGB, HSL, OKLCH
2026年のデザインシステムでOKLCHがHSLを置き換える理由。
デザインシステムがHSLからOKLCHへ移行しつつありますが、その理由は流行ではありません。HSLには測定可能な欠陥があります。明度軸が人間の目に嘘をつくのです。OKLCHは実際の知覚実験から導かれたモデルでこれを修正します。メカニズムを理解すれば、移行がいつ割に合うのか、鋭いエッジがどこにあるのかが分かります。
RGBとHex: ハードウェア座標
color: #ff6633;
color: rgb(255 102 51);
sRGBの三つ組は発光体の強度を記述し、チャンネルあたり8ビットが目の最も敏感な領域に使われるようガンマエンコードされています。保存・交換フォーマットとしては問題ありません。デザイン操作の空間としては役立たずです。「同じ色相で少し暗く」を得るために調整できるチャンネルが存在しません。RGBチャンネルを少しずつ動かして作った派生パレットは、色相と彩度が同時に漂流します。
HSL: 知覚的ではなく幾何学的な変換
color: hsl(15 100% 60%);
HSLはRGBの立方体を円柱に再配置します。色相は角度、彩度は半径、明度は高さ。計算が安く推論しやすいため、20年間CSSの主力でした。欠陥: HSLの明度はRGB値に対する数式であって、知覚された明るさの尺度ではありません。hsl(60 100% 50%)(黄)と hsl(240 100% 50%)(青)はL=50%を共有しますが、どんな人間の観察者にも黄色の方が劇的に明るく見えます。パレットでは結果が複利で効いてきます。HSLで生成した「均一な」明度ランプは目に見えて不均一なステップを持ち、L固定での色相回転は知覚的な明るさを変えます。HSL生成のカテゴリカラーがチャートで混沌として見える理由です。
OKLCH: 知覚座標
OKLCHは、Björn Ottossonが2020年に発表しCSS Color Level 4に採用されたOKLabの円柱形です。現代の色知覚データに数値的にフィッティングされており、CIELABの青の色相歪みとHSLの明度の嘘の両方を補正します。
color: oklch(0.7 0.15 45);
- L — 知覚明度、0から1。同じLの2色は本当に同じ明るさに見えます。
- C — クロマ、0(グレー)から上へ。実務では表示可能な最高彩度の色で0.37付近が上限。HSLの彩度と違い、色域に相対的でない絶対値です。
- H — 度単位の色相角、知覚的に配置。
日々の作業を変える性質: 軸が独立しています。Lを変えても色相は漂流しません。LとCを固定してHを回転させれば、得られるすべての色が同じ視覚的重みを持ちます。
--brand-100: oklch(0.95 0.04 250);
--brand-300: oklch(0.80 0.10 250);
--brand-500: oklch(0.65 0.15 250);
--brand-700: oklch(0.45 0.12 250);
--brand-900: oklch(0.28 0.08 250);
これが1つの色相番号から生まれた完全なティント・シェードスケールで、ステップは均等に見えます。HSLには決して実現できなかったことです。CSS Color 5の相対色構文は派生を宣言的にします。oklch(from var(--brand-500) calc(l + 0.1) c h) は「ブランドカラーを1段明るく」です。
色域: 新しいエッジケース
OKLCHは画面が表示できるかどうかに関係なく色を記述します。高クロマの値はsRGBを超え、一部はDisplay P3すら超えます。ブラウザは最も近い表示可能色へのガマットマッピングを適用しますが、それに頼るということは、実際に見たことのない色を出荷するということです。実用的なガードレール:
- 体系的なパレットは中程度のクロマに保つ(テキストに隣接する色はおよそ0.15以下)ことで、全ステップがsRGBで生き残るように。
- 高クロマのP3色はアクセント用に取っておき、
@media (color-gamut: p3)の後ろに配置。 - 数学を信用せず、各トークンをコンバーターで確認。
ブラウザサポートは2023年以降、エバーグリーンブラウザで普遍的です。レガシーターゲットにはOKLCHで記述しPostCSSにRGBフォールバックを出力させましょう。通常の宣言の後に @supports (color: oklch(0 0 0)) のオーバーライドを置くパターンはツーリングなしでも機能します。
避けるべき2つの間違い
- 等しいΔLはWCAG準拠のコントラストを意味しません。 WCAG 2.xのコントラスト比は異なる輝度式を使うため、OKLCH由来の見た目の良いペアでもチェッカーに落ち得ますし、その逆もあります(APCAが修正を目指す欠陥)。コントラストは常に別途測定を。
- 間違った空間での補間。 グラデーションと
color-mix()は一部の文脈でsRGBがデフォルトのため、補色の間にグレーのデッドゾーンが生まれます。color-mix(in oklch, var(--a), var(--b))は知覚的に補間します。
既存パレットの移行
レガシーパレットの変換は機械的なステップから始まります。各hexトークンをOKLCHに翻訳し、数値を眺めましょう。手作業でチューニングされたパレットの多くは、即座に非一貫性を露呈します。色相をまたいだbrand-500トークンが同じL値を共有していることは稀で、なぜ一部が常に「重く」見えたのかの説明になります。実用的なパス: リリース済みUIの既存hex値はそのままにし、ステップごとにLとCを調和させた新トークンをOKLCHで定義し、画面単位でコンポーネントを切り替えていきます。ダークモードでは単純なL反転を我慢しましょう。高クロマ色は暗い背景で滲んで振動するため、ダークテーマには反転した明度だけでなく引き下げたクロマも必要です。ライトモードの oklch(0.55 0.15 250) には、公式だけで計算した値ではなく、手で調整した oklch(0.75 0.10 250) のようなダーク変種が対になります。
sdk.is/color-picker のカラーピッカーで同じ色をhex、RGB、HSL、OKLCHで見比べましょう。クロマ値への直感を養う最速の方法は、スライダーをドラッグしながら数値を眺めることです。