はじめに

CSSカスタムプロパティ(CSS変数)は、モダンなCSSにおいて欠かすことのできない機能です。従来のCSSでは、同じ色やサイズの値を複数箇所に記述する必要がありましたが、カスタムプロパティを使うことで値を一元管理でき、保守性と効率性が大幅に向上します。

本記事では、CSSカスタムプロパティの基本構文から実践的なテーマ切り替えの実装まで、体系的に解説します。

この記事で学べる内容は以下のとおりです。

  • カスタムプロパティの基本構文とvar()関数の使い方
  • スコープと継承の仕組み
  • フォールバック値の設定方法
  • JavaScriptからカスタムプロパティを操作する方法
  • ダークモード対応のテーマ切り替え実装

CSSカスタムプロパティとは

CSSカスタムプロパティは、CSS内で再利用可能な値を定義するための仕組みです。変数名は必ず2つのハイフン(--)で始まり、var()関数を使って参照します。

なぜカスタムプロパティを使うのか

カスタムプロパティを使用する主なメリットは以下のとおりです。

メリット 説明
一元管理 色やサイズなどの値を1箇所で定義し、変更時の影響範囲を限定できる
可読性向上 #3498dbよりも--primary-colorの方が意図が明確
動的変更 JavaScriptやメディアクエリで値を動的に変更可能
テーマ対応 ダークモードなどのテーマ切り替えが容易

基本構文とvar()関数

カスタムプロパティの宣言

カスタムプロパティは、通常のCSSプロパティと同様にルールセット内で宣言します。

1
2
3
4
5
6
/* 要素セレクタ内での宣言 */
.container {
  --main-color: #3498db;
  --spacing: 16px;
  --font-family: 'Helvetica Neue', sans-serif;
}

カスタムプロパティ名は大文字と小文字を区別します。--main-color--Main-Colorは別のプロパティとして扱われるため、命名規則を統一することが重要です。

var()関数による参照

宣言したカスタムプロパティは、var()関数を使って参照します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
.container {
  --primary-color: #3498db;
  --secondary-color: #2ecc71;
  --spacing-md: 16px;
}

.button {
  background-color: var(--primary-color);
  color: white;
  padding: var(--spacing-md);
  border: 2px solid var(--secondary-color);
}

.card {
  border: 1px solid var(--primary-color);
  padding: var(--spacing-md);
}

値として使用できるもの

カスタムプロパティには、あらゆるCSS値を格納できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
:root {
  /* 色 */
  --color-primary: #3498db;
  --color-rgba: rgba(52, 152, 219, 0.5);
  
  /* 長さ */
  --spacing-sm: 8px;
  --spacing-md: 16px;
  --spacing-lg: 24px;
  
  /* フォント */
  --font-size-base: 16px;
  --font-family-main: 'Helvetica Neue', Arial, sans-serif;
  
  /* 複合値 */
  --box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  --transition: all 0.3s ease-in-out;
  
  /* 数値(単位なし) */
  --z-index-modal: 1000;
  --aspect-ratio: 16 / 9;
}

.card {
  font-family: var(--font-family-main);
  font-size: var(--font-size-base);
  padding: var(--spacing-md);
  box-shadow: var(--box-shadow);
  transition: var(--transition);
}

:rootとグローバルスコープ

:root疑似クラスの活用

グローバルに使用するカスタムプロパティは、:root疑似クラスに定義するのが一般的です。:rootはHTMLドキュメントのルート要素(<html>)を指し、最も広いスコープを持ちます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
:root {
  /* カラーパレット */
  --color-primary: #3498db;
  --color-secondary: #2ecc71;
  --color-accent: #e74c3c;
  --color-text: #333333;
  --color-background: #ffffff;
  
  /* タイポグラフィ */
  --font-size-xs: 12px;
  --font-size-sm: 14px;
  --font-size-md: 16px;
  --font-size-lg: 20px;
  --font-size-xl: 24px;
  
  /* スペーシング */
  --spacing-xs: 4px;
  --spacing-sm: 8px;
  --spacing-md: 16px;
  --spacing-lg: 24px;
  --spacing-xl: 32px;
}

デザイントークンとしての活用

カスタムプロパティをデザイントークンとして体系的に管理することで、デザインシステムの構築が容易になります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
:root {
  /* プリミティブトークン(基本値) */
  --blue-500: #3498db;
  --blue-600: #2980b9;
  --green-500: #2ecc71;
  --gray-100: #f8f9fa;
  --gray-900: #212529;
  
  /* セマンティックトークン(意味を持つ値) */
  --color-brand: var(--blue-500);
  --color-brand-hover: var(--blue-600);
  --color-success: var(--green-500);
  --color-text-primary: var(--gray-900);
  --color-bg-primary: var(--gray-100);
}

スコープと継承の仕組み

カスタムプロパティのスコープ

カスタムプロパティは、宣言された要素とその子孫要素でのみ有効です。これにより、コンポーネント単位でのスタイル管理が可能になります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* グローバルスコープ */
:root {
  --text-color: #333;
}

/* コンポーネントスコープ */
.card {
  --card-padding: 20px;
  --card-bg: #fff;
  
  padding: var(--card-padding);
  background-color: var(--card-bg);
  color: var(--text-color); /* :rootから継承 */
}

/* .card-headerは.cardの子要素として使用 */
.card-header {
  padding: var(--card-padding); /* 親の.cardから継承 */
}

/* .cardの外では使用できない */
.other-element {
  /* var(--card-padding) は未定義となる */
}

継承の動作

カスタムプロパティは、デフォルトで親要素から子要素へ継承されます。

1
2
3
4
5
<div class="parent">
  <div class="child">
    <div class="grandchild">コンテンツ</div>
  </div>
</div>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
.parent {
  --theme-color: blue;
}

.child {
  /* 親から--theme-colorを継承 */
  background-color: var(--theme-color); /* blue */
}

.grandchild {
  /* 祖先から--theme-colorを継承 */
  border: 2px solid var(--theme-color); /* blue */
}

スコープのオーバーライド

子要素で同名のカスタムプロパティを再定義すると、その要素以下のスコープで値が上書きされます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
:root {
  --brand-color: blue;
}

.section-primary {
  /* このセクション内では青を使用 */
  background-color: var(--brand-color);
}

.section-secondary {
  /* このセクション内では緑に上書き */
  --brand-color: green;
  background-color: var(--brand-color);
}

.section-secondary .button {
  /* 親の.section-secondaryから緑を継承 */
  background-color: var(--brand-color); /* green */
}

この仕組みを利用して、コンポーネントごとに異なるテーマカラーを適用できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
:root {
  --component-bg: white;
  --component-text: #333;
}

.card {
  background-color: var(--component-bg);
  color: var(--component-text);
}

.card.card--dark {
  --component-bg: #2c3e50;
  --component-text: #ecf0f1;
}

.card.card--primary {
  --component-bg: #3498db;
  --component-text: white;
}

フォールバック値の設定

var()関数のフォールバック

var()関数の第2引数にフォールバック値を指定できます。カスタムプロパティが未定義の場合や無効な値の場合に使用されます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
.element {
  /* --undefined-varが未定義の場合、#333が使用される */
  color: var(--undefined-var, #333);
  
  /* 複数の値を含むフォールバック */
  font-family: var(--custom-font, 'Helvetica Neue', Arial, sans-serif);
  
  /* 計算式を含むフォールバック */
  padding: var(--custom-padding, calc(1rem + 10px));
}

ネストしたフォールバック

フォールバック値として別のカスタムプロパティを指定することも可能です。

1
2
3
4
5
.element {
  /* --primary-colorが未定義なら--fallback-colorを参照 */
  /* --fallback-colorも未定義なら#000を使用 */
  color: var(--primary-color, var(--fallback-color, #000));
}

ただし、ネストが深くなるとパフォーマンスに影響する可能性があるため、2段階程度に留めることを推奨します。

フォールバック値の使いどころ

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
/* コンポーネントのカスタマイズを許容する設計 */
.button {
  /* 外部から--button-bgを設定可能、デフォルトは--color-primary */
  background-color: var(--button-bg, var(--color-primary, #3498db));
  color: var(--button-text, white);
  padding: var(--button-padding, 12px 24px);
  border-radius: var(--button-radius, 4px);
}

/* 使用時にカスタマイズ */
.hero .button {
  --button-bg: #e74c3c;
  --button-padding: 16px 32px;
}

@propertyによる型定義

@propertyルールの基本

@propertyルールを使うと、カスタムプロパティに型制約、初期値、継承の有無を明示的に指定できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@property --rotation {
  syntax: "<angle>";
  initial-value: 0deg;
  inherits: false;
}

@property --primary-color {
  syntax: "<color>";
  initial-value: #3498db;
  inherits: true;
}

@property --opacity-value {
  syntax: "<number>";
  initial-value: 1;
  inherits: false;
}

@propertyのメリット

機能 説明
型安全性 無効な値が設定された場合、initial-valueにフォールバック
アニメーション対応 通常のカスタムプロパティでは不可能な値のアニメーションが可能
継承制御 inherits: falseで親からの継承を明示的に無効化

アニメーションでの活用

@propertyを使うと、カスタムプロパティの値をスムーズにアニメーションさせることができます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
@property --gradient-angle {
  syntax: "<angle>";
  initial-value: 0deg;
  inherits: false;
}

.animated-gradient {
  background: linear-gradient(
    var(--gradient-angle),
    #3498db,
    #9b59b6
  );
  animation: rotate-gradient 3s linear infinite;
}

@keyframes rotate-gradient {
  to {
    --gradient-angle: 360deg;
  }
}

JavaScriptとの連携

カスタムプロパティの取得

JavaScriptからカスタムプロパティの値を取得するには、getComputedStyle()を使用します。

1
2
3
4
5
6
// 要素に適用されているカスタムプロパティを取得
const element = document.querySelector('.element');
const styles = getComputedStyle(element);
const primaryColor = styles.getPropertyValue('--primary-color').trim();

console.log(primaryColor); // "#3498db"

カスタムプロパティの設定

setProperty()メソッドでカスタムプロパティの値を動的に変更できます。

1
2
3
4
5
6
// 特定の要素にカスタムプロパティを設定
const element = document.querySelector('.element');
element.style.setProperty('--custom-color', '#e74c3c');

// ルート要素(グローバル)に設定
document.documentElement.style.setProperty('--primary-color', '#2ecc71');

インタラクティブな実装例

マウス位置に応じて背景色を変化させる例です。

1
2
3
<div class="interactive-bg">
  マウスを動かしてみてください
</div>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
.interactive-bg {
  --mouse-x: 50%;
  --mouse-y: 50%;
  
  width: 100%;
  height: 300px;
  background: radial-gradient(
    circle at var(--mouse-x) var(--mouse-y),
    #3498db,
    #2c3e50
  );
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  font-size: 1.5rem;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const element = document.querySelector('.interactive-bg');

element.addEventListener('mousemove', (e) => {
  const rect = element.getBoundingClientRect();
  const x = ((e.clientX - rect.left) / rect.width) * 100;
  const y = ((e.clientY - rect.top) / rect.height) * 100;
  
  element.style.setProperty('--mouse-x', `${x}%`);
  element.style.setProperty('--mouse-y', `${y}%`);
});

スライダーによる値の変更

1
2
3
4
<div class="color-demo">
  <input type="range" id="hue-slider" min="0" max="360" value="200">
  <div class="color-box">カスタムカラー</div>
</div>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
.color-demo {
  --hue: 200;
}

.color-box {
  background-color: hsl(var(--hue), 70%, 50%);
  color: white;
  padding: 2rem;
  text-align: center;
  font-size: 1.25rem;
  border-radius: 8px;
  transition: background-color 0.1s ease;
}
1
2
3
4
5
6
const slider = document.getElementById('hue-slider');
const demo = document.querySelector('.color-demo');

slider.addEventListener('input', (e) => {
  demo.style.setProperty('--hue', e.target.value);
});

テーマ切り替えの実装

ダークモード対応

カスタムプロパティを活用することで、ダークモードの実装が非常にシンプルになります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
:root {
  /* ライトモードのデフォルト値 */
  --color-bg: #ffffff;
  --color-bg-secondary: #f8f9fa;
  --color-text: #212529;
  --color-text-secondary: #6c757d;
  --color-border: #dee2e6;
  --color-primary: #3498db;
  --shadow-color: rgba(0, 0, 0, 0.1);
}

/* ダークモード */
[data-theme="dark"] {
  --color-bg: #1a1a2e;
  --color-bg-secondary: #16213e;
  --color-text: #eaeaea;
  --color-text-secondary: #b0b0b0;
  --color-border: #3a3a5c;
  --color-primary: #5dade2;
  --shadow-color: rgba(0, 0, 0, 0.3);
}

システム設定に連動させる

prefers-color-schemeメディアクエリを使用すると、OSのダークモード設定に自動で連動できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
:root {
  --color-bg: #ffffff;
  --color-text: #212529;
  --color-primary: #3498db;
}

@media (prefers-color-scheme: dark) {
  :root {
    --color-bg: #1a1a2e;
    --color-text: #eaeaea;
    --color-primary: #5dade2;
  }
}

テーマ切り替えボタンの実装

手動でテーマを切り替えるボタンの完全な実装例です。

1
2
3
4
<button id="theme-toggle" aria-label="テーマを切り替え">
  <span class="theme-icon light-icon">☀️</span>
  <span class="theme-icon dark-icon">🌙</span>
</button>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
:root {
  --color-bg: #ffffff;
  --color-bg-secondary: #f8f9fa;
  --color-text: #212529;
  --color-border: #dee2e6;
  --color-primary: #3498db;
}

[data-theme="dark"] {
  --color-bg: #1a1a2e;
  --color-bg-secondary: #16213e;
  --color-text: #eaeaea;
  --color-border: #3a3a5c;
  --color-primary: #5dade2;
}

body {
  background-color: var(--color-bg);
  color: var(--color-text);
  transition: background-color 0.3s ease, color 0.3s ease;
}

#theme-toggle {
  background: var(--color-bg-secondary);
  border: 1px solid var(--color-border);
  padding: 8px 16px;
  border-radius: 8px;
  cursor: pointer;
  font-size: 1.25rem;
}

.dark-icon {
  display: none;
}

[data-theme="dark"] .light-icon {
  display: none;
}

[data-theme="dark"] .dark-icon {
  display: inline;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const themeToggle = document.getElementById('theme-toggle');
const htmlElement = document.documentElement;

// ローカルストレージから保存されたテーマを取得
const savedTheme = localStorage.getItem('theme');
const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;

// 初期テーマを設定
if (savedTheme) {
  htmlElement.setAttribute('data-theme', savedTheme);
} else if (systemPrefersDark) {
  htmlElement.setAttribute('data-theme', 'dark');
}

// テーマ切り替え
themeToggle.addEventListener('click', () => {
  const currentTheme = htmlElement.getAttribute('data-theme');
  const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
  
  htmlElement.setAttribute('data-theme', newTheme);
  localStorage.setItem('theme', newTheme);
});

// システム設定の変更を監視
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
  if (!localStorage.getItem('theme')) {
    htmlElement.setAttribute('data-theme', e.matches ? 'dark' : 'light');
  }
});

テーマシステムの全体像

flowchart TD
    A[ページ読み込み] --> B{ローカルストレージに<br>テーマ設定あり?}
    B -->|あり| C[保存されたテーマを適用]
    B -->|なし| D{システム設定は<br>ダークモード?}
    D -->|はい| E[ダークテーマを適用]
    D -->|いいえ| F[ライトテーマを適用]
    G[テーマ切り替えボタン] --> H[data-theme属性を変更]
    H --> I[ローカルストレージに保存]
    I --> J[CSSカスタムプロパティが<br>自動的に切り替わる]

実践的なユースケース

レスポンシブスペーシング

画面サイズに応じてスペーシングを調整する例です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
:root {
  --spacing-unit: 8px;
  --spacing-xs: calc(var(--spacing-unit) * 0.5);
  --spacing-sm: var(--spacing-unit);
  --spacing-md: calc(var(--spacing-unit) * 2);
  --spacing-lg: calc(var(--spacing-unit) * 3);
  --spacing-xl: calc(var(--spacing-unit) * 4);
}

@media (min-width: 768px) {
  :root {
    --spacing-unit: 12px;
  }
}

@media (min-width: 1200px) {
  :root {
    --spacing-unit: 16px;
  }
}

.container {
  padding: var(--spacing-lg);
  gap: var(--spacing-md);
}

コンポーネントのバリエーション

同じコンポーネントで異なるスタイルバリエーションを効率的に実装できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
.alert {
  --alert-bg: #d1ecf1;
  --alert-border: #bee5eb;
  --alert-text: #0c5460;
  
  background-color: var(--alert-bg);
  border: 1px solid var(--alert-border);
  color: var(--alert-text);
  padding: 1rem;
  border-radius: 4px;
}

.alert--success {
  --alert-bg: #d4edda;
  --alert-border: #c3e6cb;
  --alert-text: #155724;
}

.alert--warning {
  --alert-bg: #fff3cd;
  --alert-border: #ffeeba;
  --alert-text: #856404;
}

.alert--danger {
  --alert-bg: #f8d7da;
  --alert-border: #f5c6cb;
  --alert-text: #721c24;
}

タイポグラフィスケール

一貫性のあるタイポグラフィシステムを構築できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
:root {
  --font-size-base: 16px;
  --type-scale: 1.25;
  
  --font-size-xs: calc(var(--font-size-base) / var(--type-scale) / var(--type-scale));
  --font-size-sm: calc(var(--font-size-base) / var(--type-scale));
  --font-size-md: var(--font-size-base);
  --font-size-lg: calc(var(--font-size-base) * var(--type-scale));
  --font-size-xl: calc(var(--font-size-base) * var(--type-scale) * var(--type-scale));
  --font-size-2xl: calc(var(--font-size-base) * var(--type-scale) * var(--type-scale) * var(--type-scale));
}

h1 { font-size: var(--font-size-2xl); }
h2 { font-size: var(--font-size-xl); }
h3 { font-size: var(--font-size-lg); }
p { font-size: var(--font-size-md); }
small { font-size: var(--font-size-sm); }

ブラウザ対応状況

CSSカスタムプロパティは、すべてのモダンブラウザで十分にサポートされています。

ブラウザ サポートバージョン
Chrome 49以降
Firefox 31以降
Safari 9.1以降
Edge 15以降
Opera 36以降

@propertyルールについては、Chrome 85以降、Edge 85以降、Safari 15.4以降でサポートされています。古いブラウザへの対応が必要な場合は、フォールバック値を活用してください。

1
2
3
4
5
6
.element {
  /* フォールバック:カスタムプロパティ非対応ブラウザ用 */
  color: #3498db;
  /* カスタムプロパティ対応ブラウザ用 */
  color: var(--primary-color, #3498db);
}

まとめ

CSSカスタムプロパティは、モダンなCSS開発において不可欠な機能です。本記事で解説した内容を振り返ります。

  • 基本構文: --で始まる命名規則とvar()関数による参照
  • スコープと継承: 宣言された要素とその子孫に限定されるスコープ、親から子への継承
  • フォールバック値: var()の第2引数で未定義時のデフォルト値を指定
  • @propertyルール: 型定義、初期値、継承制御による高度な管理
  • JavaScript連携: getPropertyValue()setProperty()による動的な値の取得・設定
  • テーマ切り替え: ダークモード対応やカスタムテーマの効率的な実装

カスタムプロパティを活用することで、保守性が高く、柔軟なスタイル管理が実現できます。デザインシステムの構築やテーマ対応など、実践的な場面で積極的に活用していきましょう。

参考リンク