light-dark()でカラースキームに応じて画像を切り替える
light-dark()に色だけでなく画像を渡せるようになりました。グラデーションやurl()をライトとダークで切り替えられ、prefers-color-schemeのメディアクエリと違って要素ごとのcolor-schemeに追従します。Baseline 2026で揃った、light-dark()に画像を渡す書き方と、色を渡す場合との違いを整理します。
- Chrome150
- Edge150
- Firefox150
- Safari27
はじめに
light-dark()は、ライトとダークの2つの色を渡すと、要素に適用されているcolor-schemeに応じてどちらかを返す関数です。Baseline 2024で揃い、prefers-color-schemeのメディアクエリで同じ宣言を2回書かずに済むことから、テーマ対応の定番になりました。
ただし渡せるのは色だけだったので、背景のグラデーションやアイコンの画像をテーマごとに変えたくなると、そこだけメディアクエリに戻ることになっていました。CSS Color Level 5でlight-dark()が<image>も受け取れるように拡張され、Chrome 150とFirefox 150、Safari 27で揃ってBaseline 2026に加わりました。
画像を渡す
引数に画像を2つ渡すと、color-schemeがライトなら1つ目、ダークなら2つ目が使われます。渡せるのはurl()やグラデーションといった<image>型の値です。
:root {
color-scheme: light dark;
}
.hero {
background-image: light-dark(
linear-gradient(135deg, oklch(92% 0.06 250), oklch(68% 0.16 250)),
linear-gradient(135deg, oklch(25% 0.04 250), oklch(15% 0.02 250))
);
}
.logo {
content: light-dark(url('/logo-light.svg'), url('/logo-dark.svg'));
}:rootのcolor-scheme: light darkを書き忘れると、OSがダークでもライト側の画像が出たままになります。light-dark()が働くのは、color-schemeがlight darkのように両方を許可している範囲だけだからです。この条件は色を渡す場合と変わりません。
片方だけ画像を出したいときはnoneを渡します。ダークテーマでは背景の装飾を消したいなら、light-dark(url('/pattern.svg'), none)と書けます。noneは自然なサイズを持たない透明な画像として扱われるので、background-sizeなどの指定が残っていても崩れません。
枠のcolor-schemeを切り替えると、background-imageに渡した2つのグラデーションが画像ごと入れ替わります。
枠のcolor-scheme
枠のbackground-imageには、light-dark()に直線グラデーションと放射状グラデーションを渡しています。ライトは斜めの直線、ダークは中心が明るい放射状で、light darkではOSの設定に従います。
グラデーションの色をlight-dark()で書く場合との違い
グラデーションについては、これまでもグラデーションの色ごとにlight-dark()を書く手が使えました。色の数と角度がライトとダークで同じなら、この書き方で足ります。
.hero {
background-image: linear-gradient(
135deg,
light-dark(oklch(92% 0.06 250), oklch(35% 0.08 250)),
light-dark(oklch(68% 0.16 250), oklch(15% 0.02 250))
);
}差が出るのはグラデーションの形そのものを変えるときです。ライトは2色の直線グラデーション、ダークは3色の放射状にしたいとすると、色ごとのlight-dark()では関数の種類も色の数も揃えられません。これまではメディアクエリで宣言を丸ごと上書きするしかありませんでした。
.hero {
background-image: linear-gradient(135deg, oklch(92% 0.06 250), oklch(68% 0.16 250));
}
@media (prefers-color-scheme: dark) {
同じセレクタをもう一度書き、色だけでなく宣言全体を差し替える
.hero {
background-image: radial-gradient(
circle at 50% 50%,
oklch(65% 0.15 250),
oklch(35% 0.08 250),
oklch(15% 0.02 250) 60%
);
}
}light-dark()に画像を渡せば、この2つを1つの宣言に並べるだけです。
.hero {
background-image: light-dark(
linear-gradient(135deg, oklch(92% 0.06 250), oklch(68% 0.16 250)),
radial-gradient(
circle at 50% 50%,
oklch(65% 0.15 250),
oklch(35% 0.08 250),
oklch(15% 0.02 250) 60%
)
);
}url()で画像そのものを差し替えるときも同じで、ライト用とダーク用のファイルを1つの宣言に並べられます。
ただし、これはメディアクエリ版と同じ結果になる置き換えではありません。prefers-color-schemeのメディアクエリが見ているのはOSやブラウザの設定で、light-dark()が見ているのは要素に適用されているcolor-schemeの使用値です。ユーザー設定がライトのまま、サイト側のトグルでhtml要素のcolor-schemeをdarkに書き換えると、メディアクエリ版の放射状グラデーションは出ません。light-dark()版は色と同じ基準で切り替わるので、色は暗くなったのに背景画像だけライトのまま残る、という食い違いが起きません。
ページの一部だけ配色を固定するときも同じです。ページ全体はダークテーマのまま、埋め込みプレビューの中だけライトで見せたいとします。同じ.panelをページ本体とプレビューの両方で使っていると、メディアクエリ版ではダーク側の分岐からプレビューの中を除外するセレクタが要ります。
.panel {
background-image: url('/paper.png');
}
@media (prefers-color-scheme: dark) {
.panel:not(.preview *) {
プレビューの中の .panel はライト用の画像のままにしたいので、ダーク側から除外する
background-image: url('/slate.png');
}
}light-dark()版では、プレビューの要素にcolor-scheme: lightを書くだけです。その中のlight-dark()は色も画像も常にライト側を返すので、.panelのセレクタを変える必要がありません。
:root {
color-scheme: light dark;
}
.panel {
background-image: light-dark(url('/paper.png'), url('/slate.png'));
}
/* 埋め込みプレビューはテーマに関係なくライトで見せる */
.preview {
color-scheme: light;
}おわりに
light-dark()が<image>を受け取れるようになり、テーマごとに変えたい背景やアイコンをメディアクエリなしで書けるようになりました。色のときと同じ仕組みで動くので、color-schemeを上書きした領域にもそのまま追従します。グラデーションの色だけを変えるなら色ごとのlight-dark()で足り、形や画像そのものを変えるときに画像を渡す書き方が効きます。