1. ホーム
  2. CSS

【CSS】color-mix() の使い方|2つの色を混ぜて中間色や派生色を作る方法

Share

ボタンのホバー色を少しだけ暗くしたい、テーマカラーから淡い背景色を作りたい——こうした「元の色から派生した色」は、これまで Sass の darken() のような関数や、デザインツールで作った色をひとつずつ CSS に書き写して用意していました。color-mix() を使えば、それを CSS だけで、しかもカスタムプロパティと組み合わせて動的に作れます。この記事では color-mix() の書き方、混ぜる色空間の指定、割合の決まりごとを、動かせるデモ付きで解説します。

color-mix() は2つの色を混ぜた色を返す関数

color-mix() は、2つの色を指定した割合で混ぜ合わせ、その結果を1つの色として返す CSS の関数です。絵の具を混ぜるのと同じ感覚で、background や color など色を書ける場所ならどこでも使えます。

style.css
/* 赤と青を半分ずつ混ぜる */
.box {
  background: color-mix(in srgb, red, blue);
}

書式は次のとおりです。

書式
color-mix( in <色空間>, <色1> <割合>, <色2> <割合> )

ポイントは3つあります。先頭の in <色空間> は省略できません。どの空間で混ぜるかによって結果の色が変わるためです。次に、色は2つまでで、3色を一度には混ぜられません(入れ子にすれば可能です)。そして割合は省略でき、両方省略すると 50% ずつになります。

割合の決まりごと

割合の書き方は何通りかあり、それぞれ結果が決まっています。実際に並べて見てみましょう。

<div class="swatches">
  <div class="swatch s0"><span>red 100%</span></div>
  <div class="swatch s25"><span>red 75%</span></div>
  <div class="swatch s50"><span>50% / 50%</span></div>
  <div class="swatch s75"><span>blue 75%</span></div>
  <div class="swatch s100"><span>blue 100%</span></div>
</div>
.swatches {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.swatch {
  flex: 1 1 100px;
  height: 90px;
  display: grid;
  place-items: center;
  border-radius: 8px;
  color: #fff;
  font-size: 12px;
  font-family: sans-serif;
}

/* 第2引数の割合を変えると、混ざり具合が変わる */
.s0   { background: color-mix(in srgb, red 100%, blue); }
.s25  { background: color-mix(in srgb, red 75%, blue); }
.s50  { background: color-mix(in srgb, red, blue); }      /* 省略すると 50% ずつ */
.s75  { background: color-mix(in srgb, red 25%, blue); }
.s100 { background: color-mix(in srgb, red 0%, blue); }
Preview

デモの CSS を見ると、片方にだけ割合を書けば、もう片方は自動で残りの割合になることがわかります。color-mix(in srgb, red 75%, blue) は「赤 75%・青 25%」です。両方とも省略した color-mix(in srgb, red, blue) は 50% ずつになります。

両方に割合を書いて合計が 100% にならない場合は、次のように処理されます。

書き方結果
red, blue50% ずつ
red 70%, blue赤 70%・青 30%
red 30%, blue 30%割合は 50% ずつに正規化され、さらに不透明度が 60% になる
red 80%, blue 40%合計 120% を 100% に正規化し、赤 67%・青 33%
red 0%, blue 0%無効な指定(エラーになる)

ややこしいのは3行目です。合計が 100% に満たないときは、足りないぶんだけ透明になります。合計 60% なら不透明度 60%、という具合です。合計が 100% を超えるときは単純に比率へ均されるだけで、透明度は変わりません。意図せず薄くなってしまうことがあるので、割合は片方にだけ書くのが安全です。

色空間で結果が変わる

in の後ろに書く色空間は、どういう座標系の上で2色を直線的につなぐかの指定です。同じ2色でも、空間が違えば中間の色が変わります。黄色と青を7段階で混ぜたものを、srgb と oklab で並べてみます。

<p class="label">in srgb</p>
<div class="row srgb">
  <div></div><div></div><div></div><div></div><div></div><div></div><div></div>
</div>
<p class="label">in oklab</p>
<div class="row oklab">
  <div></div><div></div><div></div><div></div><div></div><div></div><div></div>
</div>
.label {
  font-family: sans-serif;
  font-size: 13px;
  margin: 12px 0 4px;
}

.row {
  display: flex;
}

.row div {
  flex: 1;
  height: 56px;
}

/* sRGB で混ぜると、中間が少しくすんで見える */
.srgb div:nth-child(1) { background: color-mix(in srgb, yellow 100%, blue); }
.srgb div:nth-child(2) { background: color-mix(in srgb, yellow 83%, blue); }
.srgb div:nth-child(3) { background: color-mix(in srgb, yellow 67%, blue); }
.srgb div:nth-child(4) { background: color-mix(in srgb, yellow 50%, blue); }
.srgb div:nth-child(5) { background: color-mix(in srgb, yellow 33%, blue); }
.srgb div:nth-child(6) { background: color-mix(in srgb, yellow 17%, blue); }
.srgb div:nth-child(7) { background: color-mix(in srgb, yellow 0%, blue); }

/* oklab は見た目の明るさが揃いやすい */
.oklab div:nth-child(1) { background: color-mix(in oklab, yellow 100%, blue); }
.oklab div:nth-child(2) { background: color-mix(in oklab, yellow 83%, blue); }
.oklab div:nth-child(3) { background: color-mix(in oklab, yellow 67%, blue); }
.oklab div:nth-child(4) { background: color-mix(in oklab, yellow 50%, blue); }
.oklab div:nth-child(5) { background: color-mix(in oklab, yellow 33%, blue); }
.oklab div:nth-child(6) { background: color-mix(in oklab, yellow 17%, blue); }
.oklab div:nth-child(7) { background: color-mix(in oklab, yellow 0%, blue); }
Preview

srgb のほうは、中間あたりが灰色っぽくくすんで見えるはずです。これは、赤・緑・青の数値をそのまま平均しているだけで、人間が感じる「明るさ」を考慮していないためです。一方 oklab は、人の見た目の感覚に近くなるよう設計された色空間なので、明るさの落ち込みが起こりにくく、段階の変化も均等に見えます。

指定できる色空間は大きく2種類に分かれます。

種類指定できる値特徴
直交座標系srgb / srgb-linear / lab / oklab / xyz / xyz-d50 / xyz-d65各成分をそのまま補間する
極座標系hsl / hwb / lch / oklch色相(hue)を角度として補間する

使い分けの目安はシンプルです。白や黒を混ぜて明るさを調整するだけなら srgb で十分です。元の色に近い結果になり、これまでの感覚と合います。色相が大きく離れた2色を混ぜる、グラデーションの中間色を作るといった場面では oklab や oklch を選ぶと、くすみのないきれいな中間色になります。

極座標系では色相の回り方も指定できる

hsl や oklch のような極座標系の空間では、色相が円環になっています。0度の赤から 240度の青へ向かうとき、時計回りに 240度進む道と、反時計回りに 120度戻る道の2通りがあるわけです。この経路は色空間名のあとに書いて指定できます。

style.css
/* 既定は shorter hue(短いほうを回る) */
.a { background: color-mix(in hsl, red, blue); }

/* 長いほうを回る。緑や黄色を通った中間色になる */
.b { background: color-mix(in hsl longer hue, red, blue); }

/* 常に色相が増える向きに回る */
.c { background: color-mix(in hsl increasing hue, red, blue); }

/* 常に色相が減る向きに回る */
.d { background: color-mix(in hsl decreasing hue, red, blue); }

とはいえ、日常的なUIの色作りで longer hue が必要になることはほとんどありません。既定の shorter hue のままで問題ない、と覚えておけば十分です。

カスタムプロパティと組み合わせるのが本領

color-mix() がいちばん効くのは、カスタムプロパティに入れた1色から、派生色をすべて作る使い方です。ホバー色も淡い背景色も枠線の色も、変数を1つ差し替えるだけでまとめて変わります。

<div class="buttons">
  <button class="btn">保存する</button>
  <button class="btn btn--danger">削除する</button>
</div>
<div class="alert">
  <strong>注意</strong>
  <p>背景も枠線も、--accent 1色から作っています。</p>
</div>
.buttons {
  display: flex;
  gap: 12px;
  margin-bottom: 20px;
}

.btn {
  --accent: #2f6fed;

  padding: 10px 20px;
  border: none;
  border-radius: 6px;
  background: var(--accent);
  color: #fff;
  font-size: 15px;
  cursor: pointer;
  transition: background 0.2s;
}

/* ホバーは黒を 15% 混ぜて暗く */
.btn:hover {
  background: color-mix(in srgb, var(--accent) 85%, black);
}

/* 押したときはさらに暗く */
.btn:active {
  background: color-mix(in srgb, var(--accent) 70%, black);
}

.btn--danger {
  --accent: #d9453d;
}

.alert {
  --accent: #d9453d;

  padding: 14px 18px;
  border-radius: 8px;
  /* 白を 88% 混ぜた、ごく淡い背景 */
  background: color-mix(in srgb, var(--accent) 12%, white);
  border: 1px solid color-mix(in srgb, var(--accent) 40%, white);
  color: color-mix(in srgb, var(--accent) 75%, black);
  font-family: sans-serif;
}

.alert p {
  margin: 6px 0 0;
  font-size: 14px;
}
Preview

ボタンの --accent を #d9453d に変えているのは .btn--danger の1行だけです。ホバー時と押下時の色は color-mix() が自動で計算するので、色のバリエーションを増やすたびに3色ずつ定義する必要がなくなります。

よく使うパターンをまとめておきます。

目的書き方
暗くするcolor-mix(in srgb, var(--c) 85%, black)
明るくするcolor-mix(in srgb, var(--c) 85%, white)
淡い背景色を作るcolor-mix(in srgb, var(--c) 12%, white)
半透明にするcolor-mix(in srgb, var(--c), transparent 60%)
彩度を落とすcolor-mix(in srgb, var(--c) 70%, gray)

4行目の transparent との組み合わせは覚えておくと便利です。rgba() に書き換えるには元の色を3つの数値に分解する必要がありますが、color-mix() なら変数のまま透明度を足せます。ここでの transparent 60% は「60% を透明にする」、つまり不透明度 40% という意味になります。

currentColor と組み合わせる

currentColor を混ぜると、その要素の文字色に連動した色を作れます。文字色が何であれ、それより薄い枠線を引きたいときなどに有効です。

style.css
.quote {
  /* 文字色を 30% だけ残した、淡い枠線 */
  border-left: 4px solid color-mix(in srgb, currentColor 30%, transparent);
  padding-left: 16px;
}

思ったとおりの色にならないとき

色が薄くなってしまう

両方に割合を書いていて、合計が 100% を下回っていないか確認してください。color-mix(in srgb, red 40%, blue 40%) は合計 80% なので、結果の不透明度が 80% になります。背景が透けて見えるのはこのためです。割合は片方だけに書けば、この問題は起きません。

スタイルが丸ごと無視される

いちばん多い原因は in <色空間> の書き忘れです。color-mix(red, blue) は不正な値なので、その宣言ごと無効になります。次に多いのが、色名やカスタムプロパティのつづり間違いです。var(--accent) が未定義だと color-mix() 全体が無効になるので、開発者ツールで宣言に取り消し線が付いていないか確かめてください。

なお @supports で対応状況を見て書き分けることもできます。

style.css
.btn:hover {
  /* 対応していないブラウザー向けのフォールバック */
  background: #24509f;
}

@supports (color: color-mix(in srgb, red, blue)) {
  .btn:hover {
    background: color-mix(in srgb, var(--accent) 85%, black);
  }
}

もっとも、同じプロパティを2回書けば、後から書いた不正な値は無視されて前の値が残ります。上の例のように単純な上書きで済む場面では、@supports を使わず2行並べるだけでもフォールバックとして機能します。

トランジションで色が変化しない

color-mix() の結果は普通の色として扱われるので、transition: background 0.2s は問題なく効きます。効かない場合は、color-mix() の中で使っているカスタムプロパティ側が変化しているケースが考えられます。カスタムプロパティは既定ではアニメーションしないため、--accent 自体を変化させたいなら @property で型を宣言する必要があります。

まとめ

color-mix() は、2つの色を指定した割合で混ぜて1つの色を返す関数です。in srgb のような色空間の指定は必須で、割合は片方だけ書けば残りは自動計算されます。両方に書いて合計が 100% を下回ると結果が半透明になる、という点だけ気をつけてください。

色空間は、白や黒を混ぜる明暗の調整なら srgb、色相の離れた2色を混ぜるなら oklab や oklch が向いています。極座標系では longer hue などで色相の回り方も選べますが、通常は既定のままで構いません。

そして真価が出るのはカスタムプロパティとの組み合わせです。--accent という1色から、ホバー色・淡い背景・枠線の色をすべて導き出せば、テーマの色を変える作業が変数1行の書き換えで済みます。Sass の darken() のためにビルド環境を用意していたなら、まずはホバー色から color-mix() に置き換えてみてください。

参考ページ