CSS light-dark()関数でテーマ切り替えを簡単に実装する


ダークモードは、現代のWebアプリケーションには欠かせない機能になっています。しかし、従来の実装方法では、CSS変数を使ったり、JavaScriptでクラスを切り替えたりと、複雑な実装が必要でした。

CSS仕様に新しく追加されたlight-dark()関数を使えば、これらの実装がずっとシンプルになります。この記事では、light-dark()関数の使い方から実践的な実装パターンまで、詳しく解説します。

light-dark()関数とは?

light-dark()は、CSS Color Module Level 5で追加された関数で、カラースキームに応じて自動的に色を切り替えることができます。

基本的な構文

/* light-dark(ライトモードの色, ダークモードの色) */
color: light-dark(black, white);
background-color: light-dark(white, #1a1a1a);

この関数は、color-schemeプロパティの値に応じて、自動的に適切な色を選択します。

従来の実装方法との比較

従来の方法1: CSS変数とメディアクエリ

:root {
  --text-color: black;
  --bg-color: white;
}

@media (prefers-color-scheme: dark) {
  :root {
    --text-color: white;
    --bg-color: #1a1a1a;
  }
}

body {
  color: var(--text-color);
  background-color: var(--bg-color);
}

問題点

  • CSS変数を定義する必要がある
  • メディアクエリを書く必要がある
  • コード量が多い

従来の方法2: data属性とJavaScript

[data-theme="light"] {
  --text-color: black;
  --bg-color: white;
}

[data-theme="dark"] {
  --text-color: white;
  --bg-color: #1a1a1a;
}

body {
  color: var(--text-color);
  background-color: var(--bg-color);
}
// JavaScriptでテーマを切り替え
document.documentElement.dataset.theme = "dark";

問題点

  • JavaScriptが必須
  • 初期レンダリング時にフラッシュが発生する可能性
  • 管理するコードが増える

light-dark()関数を使った方法

:root {
  color-scheme: light dark;
}

body {
  color: light-dark(black, white);
  background-color: light-dark(white, #1a1a1a);
}

メリット

  • シンプルで直感的
  • CSS変数が不要
  • ブラウザが自動的に切り替え
  • コード量が少ない

基本的な使い方

color-schemeプロパティの設定

light-dark()関数を使うには、まずcolor-schemeプロパティを設定する必要があります。

:root {
  color-scheme: light dark;
}

このプロパティは、以下の値を取ります。

  • light: ライトモードのみ
  • dark: ダークモードのみ
  • light dark: 両方サポート(システムの設定に従う)
  • dark light: 両方サポート(ダークモードを優先)

light-dark()関数の使用

.button {
  /* テキスト色 */
  color: light-dark(#333, #fff);

  /* 背景色 */
  background-color: light-dark(#f0f0f0, #2a2a2a);

  /* ボーダー色 */
  border-color: light-dark(#ccc, #444);

  /* シャドウ */
  box-shadow: light-dark(
    0 2px 4px rgba(0, 0, 0, 0.1),
    0 2px 4px rgba(255, 255, 255, 0.1)
  );
}

システムの設定に従う

デフォルトでは、ブラウザのカラースキームの設定に従います。

:root {
  color-scheme: light dark;
}

/* システムがライトモードなら白背景、ダークモードなら黒背景 */
body {
  background-color: light-dark(white, black);
}

ユーザーは、OSの設定やブラウザの設定で、ライトモード/ダークモードを切り替えられます。

手動でテーマを切り替える

システムの設定に加えて、ユーザーが手動でテーマを切り替えられるようにするには、color-schemeプロパティを動的に変更します。

JavaScriptでの切り替え

// ライトモードに切り替え
document.documentElement.style.colorScheme = "light";

// ダークモードに切り替え
document.documentElement.style.colorScheme = "dark";

// システムの設定に従う
document.documentElement.style.colorScheme = "light dark";

トグルボタンの実装

<button id="theme-toggle">
  <span class="light-icon">☀️</span>
  <span class="dark-icon">🌙</span>
</button>
:root {
  color-scheme: light dark;
}

.dark-icon {
  display: none;
}

:root[style*="color-scheme: dark"] .light-icon {
  display: none;
}

:root[style*="color-scheme: dark"] .dark-icon {
  display: inline;
}
const toggle = document.getElementById("theme-toggle");
const root = document.documentElement;

// 初期値を取得
const savedTheme = localStorage.getItem("theme");
if (savedTheme) {
  root.style.colorScheme = savedTheme;
}

toggle.addEventListener("click", () => {
  const current = root.style.colorScheme;

  if (current === "dark") {
    root.style.colorScheme = "light";
    localStorage.setItem("theme", "light");
  } else {
    root.style.colorScheme = "dark";
    localStorage.setItem("theme", "dark");
  }
});

実践的な例

ナビゲーションバー

:root {
  color-scheme: light dark;
}

.navbar {
  background-color: light-dark(#ffffff, #1a1a1a);
  border-bottom: 1px solid light-dark(#e0e0e0, #333);
  box-shadow: light-dark(
    0 2px 8px rgba(0, 0, 0, 0.1),
    0 2px 8px rgba(0, 0, 0, 0.5)
  );
}

.navbar-link {
  color: light-dark(#333, #fff);
  transition: color 0.2s;
}

.navbar-link:hover {
  color: light-dark(#0066cc, #66b3ff);
}

.navbar-link.active {
  color: light-dark(#0066cc, #66b3ff);
  background-color: light-dark(#f0f0f0, #2a2a2a);
}

カード

.card {
  background-color: light-dark(#fff, #2a2a2a);
  border: 1px solid light-dark(#e0e0e0, #444);
  border-radius: 8px;
  box-shadow: light-dark(
    0 4px 6px rgba(0, 0, 0, 0.1),
    0 4px 6px rgba(0, 0, 0, 0.3)
  );
  padding: 1.5rem;
}

.card-title {
  color: light-dark(#1a1a1a, #fff);
  font-size: 1.5rem;
  margin-bottom: 0.5rem;
}

.card-text {
  color: light-dark(#666, #aaa);
  line-height: 1.6;
}

ボタン

.button {
  padding: 0.75rem 1.5rem;
  border-radius: 6px;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.2s;
}

.button-primary {
  color: white;
  background-color: light-dark(#0066cc, #66b3ff);
  border: none;
}

.button-primary:hover {
  background-color: light-dark(#0052a3, #4da6ff);
}

.button-secondary {
  color: light-dark(#333, #fff);
  background-color: light-dark(#f0f0f0, #333);
  border: 1px solid light-dark(#ccc, #555);
}

.button-secondary:hover {
  background-color: light-dark(#e0e0e0, #444);
}

.button-outline {
  color: light-dark(#0066cc, #66b3ff);
  background-color: transparent;
  border: 2px solid light-dark(#0066cc, #66b3ff);
}

.button-outline:hover {
  color: white;
  background-color: light-dark(#0066cc, #66b3ff);
}

フォーム

.form-input {
  width: 100%;
  padding: 0.75rem;
  border: 1px solid light-dark(#ccc, #555);
  border-radius: 6px;
  background-color: light-dark(#fff, #2a2a2a);
  color: light-dark(#333, #fff);
  font-size: 1rem;
}

.form-input:focus {
  outline: none;
  border-color: light-dark(#0066cc, #66b3ff);
  box-shadow: 0 0 0 3px light-dark(
    rgba(0, 102, 204, 0.1),
    rgba(102, 179, 255, 0.1)
  );
}

.form-input::placeholder {
  color: light-dark(#999, #666);
}

.form-label {
  display: block;
  margin-bottom: 0.5rem;
  color: light-dark(#333, #fff);
  font-weight: 600;
}

.form-error {
  margin-top: 0.25rem;
  color: light-dark(#dc3545, #ff6b7a);
  font-size: 0.875rem;
}

コードブロック

.code-block {
  padding: 1rem;
  border-radius: 8px;
  background-color: light-dark(#f5f5f5, #1e1e1e);
  border: 1px solid light-dark(#e0e0e0, #333);
  overflow-x: auto;
}

.code-block code {
  color: light-dark(#333, #d4d4d4);
  font-family: 'Fira Code', monospace;
  font-size: 0.9rem;
}

.code-inline {
  padding: 0.2rem 0.4rem;
  border-radius: 4px;
  background-color: light-dark(#f0f0f0, #2a2a2a);
  color: light-dark(#d73a49, #ff6b7a);
  font-family: 'Fira Code', monospace;
  font-size: 0.9em;
}

画像の切り替え

light-dark()関数は色だけでなく、画像の切り替えにも使えます。

背景画像

.hero {
  background-image: light-dark(
    url('/images/hero-light.jpg'),
    url('/images/hero-dark.jpg')
  );
  background-size: cover;
  background-position: center;
}

ロゴの切り替え

.logo {
  content: light-dark(
    url('/images/logo-dark.svg'),
    url('/images/logo-light.svg')
  );
  width: 150px;
  height: 50px;
}

HTMLでの画像切り替え

HTMLの<picture>要素を使う方法もあります。

<picture>
  <source srcset="/images/logo-dark.svg" media="(prefers-color-scheme: light)">
  <source srcset="/images/logo-light.svg" media="(prefers-color-scheme: dark)">
  <img src="/images/logo-dark.svg" alt="Logo">
</picture>

CSS変数との組み合わせ

light-dark()関数とCSS変数を組み合わせることで、より柔軟なテーマシステムを構築できます。

:root {
  color-scheme: light dark;

  /* プライマリカラー */
  --color-primary: light-dark(#0066cc, #66b3ff);
  --color-primary-hover: light-dark(#0052a3, #4da6ff);

  /* テキストカラー */
  --color-text: light-dark(#333, #fff);
  --color-text-secondary: light-dark(#666, #aaa);

  /* 背景色 */
  --color-bg: light-dark(#fff, #1a1a1a);
  --color-bg-secondary: light-dark(#f5f5f5, #2a2a2a);

  /* ボーダー */
  --color-border: light-dark(#e0e0e0, #444);

  /* シャドウ */
  --shadow-sm: light-dark(
    0 1px 2px rgba(0, 0, 0, 0.05),
    0 1px 2px rgba(0, 0, 0, 0.3)
  );
  --shadow-md: light-dark(
    0 4px 6px rgba(0, 0, 0, 0.1),
    0 4px 6px rgba(0, 0, 0, 0.3)
  );
  --shadow-lg: light-dark(
    0 10px 15px rgba(0, 0, 0, 0.1),
    0 10px 15px rgba(0, 0, 0, 0.4)
  );
}

/* 使用例 */
.card {
  background-color: var(--color-bg);
  border: 1px solid var(--color-border);
  box-shadow: var(--shadow-md);
}

.button-primary {
  background-color: var(--color-primary);
  color: white;
}

.button-primary:hover {
  background-color: var(--color-primary-hover);
}

アニメーションとトランジション

テーマ切り替え時にスムーズなアニメーションを追加できます。

:root {
  color-scheme: light dark;
}

body {
  background-color: light-dark(white, #1a1a1a);
  color: light-dark(black, white);
  transition: background-color 0.3s ease, color 0.3s ease;
}

.card {
  background-color: light-dark(white, #2a2a2a);
  border-color: light-dark(#e0e0e0, #444);
  transition: background-color 0.3s ease, border-color 0.3s ease;
}

.button {
  background-color: light-dark(#0066cc, #66b3ff);
  transition: background-color 0.3s ease;
}

ブラウザサポート

light-dark()関数は比較的新しい機能のため、ブラウザサポートを確認する必要があります。

サポート状況(2025年2月時点)

  • Chrome/Edge: 123+
  • Firefox: 120+
  • Safari: 17.5+

フォールバック

古いブラウザのために、フォールバックを用意します。

:root {
  color-scheme: light dark;
}

body {
  /* フォールバック */
  background-color: white;
  color: black;

  /* light-dark()をサポートしているブラウザで上書き */
  background-color: light-dark(white, #1a1a1a);
  color: light-dark(black, white);
}

@media (prefers-color-scheme: dark) {
  body {
    /* light-dark()をサポートしていないブラウザ用 */
    background-color: #1a1a1a;
    color: white;
  }
}

CSS Feature Queryを使う

@supports (color: light-dark(white, black)) {
  /* light-dark()をサポートしているブラウザ用 */
  body {
    background-color: light-dark(white, #1a1a1a);
  }
}

@supports not (color: light-dark(white, black)) {
  /* サポートしていないブラウザ用 */
  body {
    background-color: white;
  }

  @media (prefers-color-scheme: dark) {
    body {
      background-color: #1a1a1a;
    }
  }
}

まとめ

light-dark()関数を使えば、ダークモードの実装がずっとシンプルになります。

メリット

  • コード量が大幅に削減
  • CSS変数の定義が不要
  • メディアクエリの記述が不要
  • ブラウザが自動的に切り替え
  • 直感的で読みやすい

使い所

  • 新規プロジェクト
  • モダンブラウザをターゲットとしたアプリ
  • シンプルなテーマ切り替え

注意点

  • ブラウザサポートを確認
  • 必要に応じてフォールバックを用意
  • 古いブラウザをサポートする場合は従来の方法と併用

light-dark()関数は、CSSの新しい標準として、これからのWebデザインを大きく変えていくでしょう。ぜひ、あなたのプロジェクトでも試してみてください。