ウェブエンジニア問題集
第10章

CSS変数(カスタムプロパティ)入門 — テーマ管理・ダークモード・コンポーネント設計の実践

7
この章の目次開く

**CSS変数(カスタムプロパティ)**を使うと、色・サイズ・余白などの値を一箇所で定義して使い回せます。「この青をサイト全体で少し暗くしたい」という変更が、値を1つ書き換えるだけで完了します。

学習者学習者

同じ色をあちこちにベタ書きしてて、変更のたびに全部直すのが大変…一箇所で管理できないの?

まさにその課題を解決するのがCSS変数です。Sassの変数($color)と似ていますが、CSS変数はブラウザが実行時に解決するため、JavaScriptからの操作やメディアクエリでの切り替えが可能です。

CSS変数でデザイントークンを設計しているイメージ


基本の書き方 — 定義と参照

変数は -- で始まる名前で定義し、var() で参照します。

/* :root に定義するとページ全体で使える */
:root {
  --color-primary: #2563eb;
  --color-text: #1a1a2e;
  --color-bg: #ffffff;
  --spacing-sm: 8px;
  --spacing-md: 16px;
  --spacing-lg: 24px;
  --radius-md: 8px;
}
 
.btn {
  background-color: var(--color-primary);
  padding: var(--spacing-sm) var(--spacing-md);
  border-radius: var(--radius-md);
}
 
.card {
  background-color: var(--color-bg);
  color: var(--color-text);
  padding: var(--spacing-lg);
  border-radius: var(--radius-md);
}
css

:root はHTMLの <html> 要素を指すセレクタで、ここに書いた変数はページ内のどこからでも参照できます。


デフォルト値(フォールバック)

変数が未定義のときのフォールバック値を指定できます。

.card {
  background-color: var(--card-bg, #ffffff);
  /* --card-bg が未定義なら #ffffff が使われる */
}
 
/* デフォルト値にさらに変数を入れることもできる */
.text {
  color: var(--text-color, var(--color-text, #333));
}
css

コンポーネント単位でCSS変数を使うとき、「指定されていなければデフォルト値を使う」というパターンが非常に便利です。


スコープ — 変数が有効な範囲

CSS変数はCSSのカスケードに従い、定義した要素とその子孫でのみ有効です。

:root {
  --btn-bg: #2563eb; /* ページ全体で有効 */
}
 
.btn-danger {
  --btn-bg: #dc2626; /* このクラスの中だけ上書き */
}
 
.btn {
  background-color: var(--btn-bg);
}
/* .btn-danger 内の .btn → 赤、それ以外の .btn → 青 */
css
先生先生

Sassの変数($color)はビルド時に解決されて固定値になるけど、CSS変数はブラウザが実行時に解決する。だからメディアクエリで値を切り替えたり、JavaScriptから動的に変えたりできるんだ。これが最大の違いだよ。


デザイントークンとしてのCSS変数

実務では、色・余白・角丸・フォントサイズなどのデザイン値をCSS変数で一元管理し、これをデザイントークンと呼びます。

:root {
  /* カラー */
  --color-primary: #2563eb;
  --color-primary-hover: #1d4ed8;
  --color-secondary: #64748b;
  --color-success: #22c55e;
  --color-danger: #ef4444;
  --color-text: #1a1a2e;
  --color-text-muted: #64748b;
  --color-bg: #ffffff;
  --color-surface: #f8fafc;
  --color-border: #e2e8f0;
 
  /* スペーシング */
  --spacing-xs: 4px;
  --spacing-sm: 8px;
  --spacing-md: 16px;
  --spacing-lg: 24px;
  --spacing-xl: 48px;
 
  /* 角丸 */
  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-lg: 16px;
  --radius-full: 9999px;
 
  /* フォントサイズ */
  --text-sm: 0.875rem;
  --text-base: 1rem;
  --text-lg: 1.125rem;
  --text-xl: 1.25rem;
}
css

ダークモード対応

CSS変数の最も実践的な使い方の一つがダークモード対応です。変数の値を切り替えるだけで、全コンポーネントのテーマが一気に変わります。

:root {
  --color-bg: #ffffff;
  --color-text: #1a1a2e;
  --color-surface: #f8fafc;
  --color-border: #e2e8f0;
}
 
@media (prefers-color-scheme: dark) {
  :root {
    --color-bg: #0f172a;
    --color-text: #f1f5f9;
    --color-surface: #1e293b;
    --color-border: #334155;
  }
}
 
body {
  background-color: var(--color-bg);
  color: var(--color-text);
}
 
.card {
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
}
css
学習者学習者

すごい…変数の値を変えるだけで全部切り替わるの?コンポーネント側は何も触らなくていいんだ。

そのとおりです。コンポーネント側は var(--color-bg) と書いておくだけで、ライトモードでもダークモードでも適切な色が適用されます。

クラスベースのダークモード切り替え

OSの設定ではなくユーザーの操作でテーマを切り替えたい場合は、クラスベースで制御します。

:root {
  --color-bg: #ffffff;
  --color-text: #1a1a2e;
}
 
:root.dark {
  --color-bg: #0f172a;
  --color-text: #f1f5f9;
}
css
// JavaScriptでトグル
document.documentElement.classList.toggle('dark');
js

コンポーネント設計でのCSS変数

CSS変数をコンポーネントの「外部インターフェース」として使うと、再利用性の高いコンポーネントが作れます。

/* ボタンコンポーネント:外からCSS変数でカスタマイズ可能 */
.btn {
  background-color: var(--btn-bg, #2563eb);
  color: var(--btn-color, #ffffff);
  padding: var(--btn-padding, 8px 16px);
  border-radius: var(--btn-radius, 8px);
}
 
/* 使う側で変数を上書き */
.hero .btn {
  --btn-bg: #dc2626;
  --btn-padding: 12px 32px;
}
 
.sidebar .btn {
  --btn-bg: #64748b;
  --btn-radius: 4px;
}
css
コンポーネント設計を確認するイメージ

コンポーネントの内部CSSには触れず、変数だけを上書きしてバリエーションを作れるのがポイントです。


JavaScriptとの連携

CSS変数はJavaScriptから読み書きできます。これにより、ユーザーの操作に応じたリアルタイムなスタイル変更が可能です。

読み取り

const styles = getComputedStyle(document.documentElement);
const primary = styles.getPropertyValue('--color-primary');
// " #2563eb"(先頭にスペースが入ることがある → trim()推奨)
js

書き込み

// テーマカラーを動的に変更
document.documentElement.style.setProperty('--color-primary', '#10b981');
 
// ユーザーがカラーピッカーで選んだ色をリアルタイムに反映
colorPicker.addEventListener('input', (e) => {
  document.documentElement.style.setProperty('--color-primary', e.target.value);
});
js
先生先生

JavaScriptで setProperty した値は、CSSのカスケードでインラインスタイルと同じ扱いになるよ。だからCSS側の :root { --color-primary: ... } より優先される。元に戻したいときは removeProperty で解除できる。


よくあるハマりどころ

変数名のタイプミスに気づかない

CSS変数は未定義でもエラーにならず、フォールバック値(var() の第2引数)が使われます。フォールバック値もなければプロパティが無効になるだけです。

/* タイプミス:--color-primry(aが抜けてる) */
.btn {
  background-color: var(--color-primry);
  /* 未定義 → 無効 → backgroundが透明になる */
}
css

DevToolsで値が空になっていたら、変数名のスペルを確認しましょう。

calc() の中での単位

/* ❌ 数値だけの変数をcalcでpxに変換しようとする */
:root {
  --spacing: 16;
}
.box {
  padding: calc(var(--spacing) * 1px);
} /* 動くがやめた方がいい */
 
/* ⭕ 最初から単位付きで定義する */
:root {
  --spacing: 16px;
}
.box {
  padding: var(--spacing);
}
css

ちゃんと使うためのポイント

  • デザイントークン(色・余白・角丸など)はCSS変数で一元管理する
  • 命名は --カテゴリ-用途--color-primary--spacing-md
  • :root にベーステーマを定義し、ダークモードは @media (prefers-color-scheme: dark) またはクラス(.dark)で変数を上書き
  • コンポーネントの「外部インターフェース」としてCSS変数を使うと再利用性が上がる
  • CSS変数はJavaScriptから読み書きできる → テーマ切り替えやリアルタイムカスタマイズに活用

次の章では、スタイルが当たらない・意図せず上書きされる原因となる**詳細度とカスケード**を学びます。

参考リンク

CSSクイズに挑戦するこの章で学んだCSSの知識を、4択クイズでアウトプットして定着させよう