CSS

CSSのoklch()とcolor-mix()|1色から8用途へ広げる配色デモ

oklch()の基準色から、color-mix()でホバー・淡い背景・文字・暗い背景の色を生成。相対色構文、機能別の代替色、色相を動かすデモとコントラスト確認まで解説します。

この記事の目次
  1. oklchのL・C・hは、HSLの数字をそのまま移さない
  2. 8用途の色を、基準色から作るCSS
  3. HTMLとスライダーをつなぎ、実際の部品に色を付ける
  4. 同じLでも、文字とのコントラストは測る
  5. 色域とブラウザ対応は、別々に確認する
  6. 今回の確認結果と、採用前の一手

ブランド色を変えたのに、ホバー色や淡い背景だけ前の色が残る。そんなときは、基準色をoklch()で一つ決め、color-mix()で用途ごとの色を作れます。明度だけを変えたい場所には、相対色構文のoklch(from ...)も使えます。

ただし、色を派生させる式は文字が読みやすいことまで保証しません。ここでは、色相を動かすと8用途の色が更新されるデモを作り、明るい背景・暗い背景・ボタンに使い、見え方を確認します。

oklchのL・C・hは、HSLの数字をそのまま移さない

oklch(52% 0.12 250)は、知覚的な明るさを表すL、色の鮮やかさに関係するC、色相角hを並べた指定です。Lの52%は0.52とも書けます。Cの0.12を「彩度12%」と読み替えないでください。CはHSLの彩度とは異なる成分です。

HSLとOKLCHでは、同じ色相角でも同じ色になるとは限りません。hsl(250 ...)の250だけを移してブランド色を再現する方法にはなりません。正確に色を引き継ぐ場合は、元の色を変換した値から調整します。構文と成分の対応はMDNのoklch()リファレンスで確認できます。

したいこと 選び方
既存のHSL配色を小さく修正する そのままHSLで管理してもよい。記法変更自体を目的にしない
明るさ・鮮やかさ・色相を分けて調整する OKLCHの成分で試し、実表示を確認する
白や黒と混ぜて用途別の色を作る color-mixで混色空間と割合を指定する
元の色の明度だけを加算する 相対色構文でLを操作する。色域も確認する

HSLの基礎はhsl()の設定方法、HEXやRGBとの違いはCSSの色指定で確認できます。

スポンサーリンク

8用途の色を、基準色から作るCSS

次のCSSをstyle.cssとして保存します。最初に固定の代替色を置き、対応する機能ごとに上書きします。基準色の色相を表す--hueだけを後のスライダーから変更します。

:root {
  --hue: 250;
  --brand: #306ca5;
  --hover: #245280;
  --soft: #eaf0f7;
  --border: #9eb7d0;
  --ink: #172538;
  --light: #83aee5;
  --dark-surface: #141b25;
  --dark-accent: #91b6dc;
}
@supports (color: oklch(52% 0.12 250)) {
  :root { --brand: oklch(52% 0.12 var(--hue)); }
}
@supports (color: color-mix(in oklch, blue, white)) {
  :root {
    --hover: color-mix(in oklch, var(--brand) 80%, black);
    --soft: color-mix(in oklch, var(--brand) 12%, white);
    --border: color-mix(in oklch, var(--brand) 45%, white);
    --ink: color-mix(in oklch, var(--brand) 35%, black);
    --light: color-mix(in oklch, var(--brand) 60%, white);
    --dark-surface: color-mix(in oklch, var(--brand) 10%, black);
    --dark-accent: color-mix(in oklch, var(--brand) 55%, white);
  }
}
@supports (color: oklch(from blue calc(l + 0.2) c h)) {
  :root { --light: oklch(from var(--brand) calc(l + 0.2) c h); }
}
* { box-sizing: border-box; }
body { margin: 2rem auto; padding: 0 1rem; max-width: 56rem; font: 18px/1.7 sans-serif; background: white; color: #172538; }
input { width: min(100%, 24rem); display: block; }
.palette { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem; padding: 0; list-style: none; }
.swatch { display: block; height: 4rem; border: 1px solid #777; background: var(--sample); }
code { font-size: .85em; overflow-wrap: anywhere; }
.card { margin-block: 1rem; padding: 1.25rem; border: 1px solid var(--border); background: var(--soft); color: var(--ink); }
button { font: inherit; border: 0; padding: .7rem 1.2rem; color: white; background: var(--brand); cursor: pointer; }
button:hover { background: var(--hover); }
button:focus-visible { outline: 3px solid #111; outline-offset: 4px; }
.dark { background: var(--dark-surface); color: var(--dark-accent); }
@media (min-width: 48rem) { .palette { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.swatch-brand { --sample: var(--brand); }
.swatch-hover { --sample: var(--hover); }
.swatch-soft { --sample: var(--soft); }
.swatch-border { --sample: var(--border); }
.swatch-ink { --sample: var(--ink); }
.swatch-light { --sample: var(--light); }
.swatch-dark-surface { --sample: var(--dark-surface); }
.swatch-dark-accent { --sample: var(--dark-accent); }

color-mix(in oklch, var(--brand) 80%, black)はOKLCH空間で基準色80%と黒20%を混ぜます。この例では割合を片方だけ指定しているため、もう片方は残りです。淡い背景の12%も透明度ではなく、白との混合割合です。不透明な2色をこの割合で混ぜるので、下地が透ける薄さとは違います。

in oklchは混色の仕方を決める指定です。sRGBなど別の空間で混ぜれば途中の色も変わります。色相同士を混ぜる場合には色相を回る方向も関係します。color-mix()の公式リファレンスを見ながら、欲しい見た目に合う空間を選びます。

--lightだけは、相対色に対応する環境でcalc(l + 0.2)へ上書きしています。元のLが0.52なら0.72になり、Cとhは構文上そのまま引き継ぎます。白を混ぜる場合はCも変わり得るため、単に明度を足す場合と同じ結果とは限りません。

HTMLとスライダーをつなぎ、実際の部品に色を付ける

同じフォルダにindex.htmlとmain.jsを保存し、HTMLをブラウザで開きます。色見本のラベルは色の付いた面の外に置いています。文字の読みやすさは、その下のカードで確かめます。

<!doctype html>
<html lang="ja"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>OKLCHから8用途の色を作る</title><link rel="stylesheet" href="style.css"></head>
<body>
<h1>1色から8用途へ</h1>
<label for="hue">基準色の色相 <output id="hue-value">250</output>度</label>
<input id="hue" type="range" min="0" max="360" value="250">
<p id="support">色相を動かし、派生色とカードの見え方を確認します。</p>
<ul class="palette">
<li><span class="swatch swatch-brand"></span><code>brand</code> 基準色</li>
<li><span class="swatch swatch-hover"></span><code>hover</code> ホバー</li>
<li><span class="swatch swatch-soft"></span><code>soft</code> 淡い背景</li>
<li><span class="swatch swatch-border"></span><code>border</code> 装飾境界</li>
<li><span class="swatch swatch-ink"></span><code>ink</code> 濃い文字</li>
<li><span class="swatch swatch-light"></span><code>light</code> 明度を加算</li>
<li><span class="swatch swatch-dark-surface"></span><code>dark-surface</code> 暗い背景</li>
<li><span class="swatch swatch-dark-accent"></span><code>dark-accent</code> 暗い背景上の文字</li>
</ul>
<section class="card"><h2>明るい背景</h2><p>背景と文字を組み合わせて確認します。</p><button type="button">見本のボタン</button></section>
<section class="card dark"><h2>暗い背景</h2><p>明度を反転せず、別の役割として色を作ります。</p></section>
<p>色生成はコントラスト合格の保証ではありません。採用する組み合わせを測定してください。</p>
<script src="main.js"></script>
</body></html>
const input = document.querySelector('#hue');
const output = document.querySelector('#hue-value');
input.addEventListener('input', () => {
  document.documentElement.style.setProperty('--hue', input.value);
  output.value = input.value;
});
if (!CSS.supports('color', 'oklch(52% 0.12 250)')) {
  input.disabled = true;
  document.querySelector('#support').textContent = 'この環境では固定の代替色を表示します。';
}

動かすのは基準色の色相だけです。基準色を含む8つの見本、カード背景、文字、ボタンが追従します。ここでのborderは装飾用です。入力欄の輪郭や選択状態を示すために使うなら、文字の読みやすさとは別に、輪郭や状態の違いを見分けられるか確かめます。

暗いカードは、自動ダークモードの完成品ではなく配色の見本です。prefers-color-schemeへ接続する前に、背景、文字、リンク、フォーカス、画像を含め、各部品にどの色を使うか決めます。明度を単純に反転するだけでは済みません。変数の管理方法はCSS変数の使い方も参考になります。

同じLでも、文字とのコントラストは測る

WCAG 2.2のコントラスト最低限の解説では、通常の文字と背景は原則4.5:1以上、大きな文字は3:1以上です。このデモの通常文字は4.5:1を基準に確認します。値を丸めて4.5に見せるのではなく、計算値で比較します。

開発者ツールやコントラスト測定ツールで、次の組み合わせを実際の状態ごとに調べます。CSSの変数名が似ているかどうかでは判定できません。

状態 確認する組み合わせ
通常ボタン 白文字とbrand
ホバー 白文字とhover
明るいカード inkの文字とsoftの背景
暗いカード dark-accentの文字とdark-surfaceの背景
キーボード操作 フォーカス輪郭が周囲と区別できるか

制作中にLを55%とした設定では、色相180のボタンと白文字が約4.45:1になりました。そこで掲載コードはLを52%へ下げています。この経験からも、OKLCHを使ったという理由だけでコントラスト確認を省略できないと分かります。

透明度を足す場合は、実際の下地と合成した結果を測ります。色だけで成功・失敗や選択状態を伝えず、文字、アイコン、形の違いも併用してください。

色域とブラウザ対応は、別々に確認する

OKLCHで書ける色が、どの画面でも同じ鮮やかさで出るとは限りません。Cを保ったままLやhを変えると、表示先の色域を外れる場合があります。ブラウザが表示可能な色へ調整する結果、狙った色との差が出ることがあります。相対色で成分を保った指定も、画面上の見え方まで固定するものではありません。

また、通常のoklch()、color-mix()、相対色構文は同じ対応状況だと決めつけず、別々に確認します。掲載CSSの@supportsはそのための分岐です。未対応の構文を含む値でもカスタムプロパティには保存できる場合があるため、var(--brand, blue)の代替値だけでその構文に対応しないブラウザで代替色を表示できるとは限りません。

固定の代替色は、上の式で派生させた色を完全に再現したものではありません。読みやすさを確保するため、別の配色を用意しています。必要なブランド色との差が許容できるか、代替色だけの状態でも確認します。

今回の確認結果と、採用前の一手

2026年9月12日にChrome 152で、390px・1440pxの表示、8色の更新、ホバー色の変化、機能分岐を外した固定色の表示を確認しました。色相0・90・180・250・360の5点で通常ボタンと明暗カードの文字を測り、掲載設定では最小約5.00:1でした。

測定は不透明な計算済み色をCanvasのsRGBへ変換した近似値です。全色相の連続範囲、広色域ディスプレイ、全ブラウザでの合格を示すものではありません。自分のブランド色に変えたら、通常時に加えてホバー・フォーカス時の見え方も実際の画面で確かめ、使う8色を役割名で保存してください。

スポンサーリンク