CSS変数とは

CSSで変数を使って値を再利用・動的変更できる機能です。

従来の問題

/* 同じ色を何度も書く */
.button { background: #2196f3; }
.link { color: #2196f3; }
.border { border-color: #2196f3; }

/* 変更時に全て修正が必要 */

CSS変数を使うと

/* 変数定義 */
:root {
  --primary-color: #2196f3;
}

/* 使用 */
.button { background: var(--primary-color); }
.link { color: var(--primary-color); }
.border { border-color: var(--primary-color); }

/* 変更は1箇所だけ! */

1. 基本的な使い方

変数の定義

/* グローバル変数(推奨) */
:root {
  --main-color: #2a2a2a;
  --accent-color: #ffeb3b;
  --spacing: 1rem;
  --border-width: 3px;
}

/* 特定要素内でのみ有効 */
.component {
  --local-color: #4caf50;
}

変数の使用

/* var(変数名, デフォルト値) */
.element {
  color: var(--main-color);
  background: var(--accent-color);
  padding: var(--spacing);
  border: var(--border-width) solid var(--main-color);
}

/* デフォルト値の指定 */
.element {
  color: var(--undefined-color, #000);
}

実装例

<style>
:root {
  --card-bg: white;
  --card-border: #2a2a2a;
  --card-shadow: rgba(0,0,0,0.1);
  --card-padding: 2rem;
}

.card {
  background: var(--card-bg);
  border: 3px solid var(--card-border);
  box-shadow: 5px 5px 0px var(--card-shadow);
  padding: var(--card-padding);
}
</style>

<div class="card">
  CSS変数を使ったカード
</div>

実装結果

CSS変数を使ったカード

2. スコープとカスケード

グローバルスコープ

:root {
  --global-color: #2196f3;
}

/* どこでも使える */
.anywhere {
  color: var(--global-color);
}

ローカルスコープ

.parent {
  --local-color: #4caf50;
}

.parent .child {
  /* 親で定義した変数を使える */
  color: var(--local-color);
}

.other {
  /* ここでは使えない */
  color: var(--local-color); /* 未定義 */
}

上書き

:root {
  --size: 16px;
}

.large {
  --size: 24px; /* 上書き */
}

.element {
  font-size: var(--size); /* 16px */
}

.large .element {
  font-size: var(--size); /* 24px */
}

3. カラーシステムの構築

基本カラーパレット

:root {
  /* ブランドカラー */
  --color-primary: #2196f3;
  --color-secondary: #4caf50;
  --color-accent: #ffeb3b;
  
  /* セマンティックカラー */
  --color-success: #4caf50;
  --color-warning: #ff9800;
  --color-error: #f44336;
  --color-info: #2196f3;
  
  /* ニュートラルカラー */
  --color-text: #2a2a2a;
  --color-text-light: #666;
  --color-border: #ccc;
  --color-bg: #fafafa;
  --color-bg-alt: #f5f5f5;
}

実装例

Primary
Success
Warning
Error

バリエーション

:root {
  --color-primary: #2196f3;
  --color-primary-light: #64b5f6;
  --color-primary-dark: #1976d2;
  
  /* または計算で生成 */
  --color-primary-alpha: rgba(33, 150, 243, 0.5);
}

4. スペーシングシステム

:root {
  /* 基準値 */
  --spacing-unit: 8px;
  
  /* 倍数システム */
  --spacing-xs: calc(var(--spacing-unit) * 0.5);  /* 4px */
  --spacing-sm: var(--spacing-unit);              /* 8px */
  --spacing-md: calc(var(--spacing-unit) * 2);    /* 16px */
  --spacing-lg: calc(var(--spacing-unit) * 3);    /* 24px */
  --spacing-xl: calc(var(--spacing-unit) * 4);    /* 32px */
  --spacing-2xl: calc(var(--spacing-unit) * 6);   /* 48px */
}

/* 使用例 */
.card {
  padding: var(--spacing-md);
  margin-bottom: var(--spacing-lg);
  gap: var(--spacing-sm);
}

実装例

XS (4px)
SM (8px)
MD (16px)
LG (24px)
XL (32px)

5. タイポグラフィシステム

:root {
  /* フォントファミリー */
  --font-sans: 'Helvetica Neue', Arial, sans-serif;
  --font-serif: Georgia, serif;
  --font-mono: 'Courier New', monospace;
  
  /* フォントサイズ */
  --font-size-xs: 0.75rem;   /* 12px */
  --font-size-sm: 0.875rem;  /* 14px */
  --font-size-base: 1rem;    /* 16px */
  --font-size-lg: 1.125rem;  /* 18px */
  --font-size-xl: 1.25rem;   /* 20px */
  --font-size-2xl: 1.5rem;   /* 24px */
  --font-size-3xl: 2rem;     /* 32px */
  
  /* フォントウェイト */
  --font-weight-normal: 400;
  --font-weight-medium: 500;
  --font-weight-bold: 700;
  
  /* 行間 */
  --line-height-tight: 1.25;
  --line-height-normal: 1.5;
  --line-height-relaxed: 1.75;
}

/* 使用例 */
h1 {
  font-family: var(--font-sans);
  font-size: var(--font-size-3xl);
  font-weight: var(--font-weight-bold);
  line-height: var(--line-height-tight);
}

6. ダークモード実装

基本パターン

/* ライトモード(デフォルト) */
:root {
  --bg-primary: #ffffff;
  --bg-secondary: #f5f5f5;
  --text-primary: #2a2a2a;
  --text-secondary: #666;
  --border-color: #ccc;
}

/* ダークモード */
[data-theme="dark"] {
  --bg-primary: #1a1a1a;
  --bg-secondary: #2a2a2a;
  --text-primary: #ffffff;
  --text-secondary: #ccc;
  --border-color: #444;
}

/* 使用 */
body {
  background: var(--bg-primary);
  color: var(--text-primary);
}

.card {
  background: var(--bg-secondary);
  border: 2px solid var(--border-color);
}

JavaScriptで切り替え

<button id="themeToggle">テーマ切替</button>

<script>
const toggle = document.getElementById('themeToggle');
const html = document.documentElement;

toggle.addEventListener('click', () => {
  const current = html.getAttribute('data-theme');
  const next = current === 'dark' ? 'light' : 'dark';
  html.setAttribute('data-theme', next);
  
  // ローカルストレージに保存
  localStorage.setItem('theme', next);
});

// ページ読み込み時に復元
const saved = localStorage.getItem('theme');
if (saved) {
  html.setAttribute('data-theme', saved);
}
</script>

実装例(動作するボタン)

テーマ切替デモ

システム設定に従う

/* ユーザーのOS設定を尊重 */
@media (prefers-color-scheme: dark) {
  :root {
    --bg-primary: #1a1a1a;
    --text-primary: #ffffff;
  }
}

7. レスポンシブな値

画面サイズで変化

:root {
  --container-padding: 1rem;
}

@media (min-width: 768px) {
  :root {
    --container-padding: 2rem;
  }
}

@media (min-width: 1024px) {
  :root {
    --container-padding: 3rem;
  }
}

.container {
  padding: var(--container-padding);
}

clampとの組み合わせ

:root {
  /* 画面幅に応じて自動調整 */
  --fluid-font: clamp(1rem, 2vw, 2rem);
  --fluid-spacing: clamp(1rem, 3vw, 3rem);
}

h1 {
  font-size: var(--fluid-font);
  margin-bottom: var(--fluid-spacing);
}

8. 計算とネスト

calc()との組み合わせ

:root {
  --base-size: 16px;
  --multiplier: 1.5;
}

.element {
  /* 計算 */
  font-size: calc(var(--base-size) * var(--multiplier));
  
  /* 複雑な計算 */
  width: calc(100% - var(--spacing-lg) * 2);
  
  /* ネスト */
  margin: calc(var(--spacing-md) + var(--spacing-sm));
}

グラデーション

:root {
  --gradient-start: #667eea;
  --gradient-end: #764ba2;
}

.gradient-bg {
  background: linear-gradient(
    135deg, 
    var(--gradient-start), 
    var(--gradient-end)
  );
}

9. アニメーションとの連携

アニメーション時間の管理

:root {
  --transition-fast: 0.15s;
  --transition-normal: 0.3s;
  --transition-slow: 0.5s;
  --easing: cubic-bezier(0.4, 0, 0.2, 1);
}

.button {
  transition: all var(--transition-normal) var(--easing);
}

.modal {
  animation-duration: var(--transition-slow);
}

JavaScriptから動的に変更

<div id="box" class="animated-box"></div>
<button onclick="changeSpeed()">速度変更</button>

<style>
.animated-box {
  width: 100px;
  height: 100px;
  background: #2196f3;
  transition: transform var(--animation-speed, 0.3s);
}

.animated-box:hover {
  transform: scale(1.2);
}
</style>

<script>
function changeSpeed() {
  const box = document.getElementById('box');
  box.style.setProperty('--animation-speed', '2s');
}
</script>

10. 完全なデザインシステム

:root {
  /* === カラー === */
  /* ブランドカラー */
  --color-primary: #2196f3;
  --color-primary-light: #64b5f6;
  --color-primary-dark: #1976d2;
  
  --color-secondary: #4caf50;
  --color-accent: #ffeb3b;
  
  /* セマンティックカラー */
  --color-success: #4caf50;
  --color-warning: #ff9800;
  --color-error: #f44336;
  --color-info: #2196f3;
  
  /* ニュートラル */
  --color-gray-50: #fafafa;
  --color-gray-100: #f5f5f5;
  --color-gray-200: #eeeeee;
  --color-gray-300: #e0e0e0;
  --color-gray-400: #bdbdbd;
  --color-gray-500: #9e9e9e;
  --color-gray-600: #757575;
  --color-gray-700: #616161;
  --color-gray-800: #424242;
  --color-gray-900: #212121;
  
  /* === スペーシング === */
  --spacing-xs: 0.25rem;   /* 4px */
  --spacing-sm: 0.5rem;    /* 8px */
  --spacing-md: 1rem;      /* 16px */
  --spacing-lg: 1.5rem;    /* 24px */
  --spacing-xl: 2rem;      /* 32px */
  --spacing-2xl: 3rem;     /* 48px */
  --spacing-3xl: 4rem;     /* 64px */
  
  /* === タイポグラフィ === */
  --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --font-mono: 'Courier New', monospace;
  
  --font-size-xs: 0.75rem;   /* 12px */
  --font-size-sm: 0.875rem;  /* 14px */
  --font-size-base: 1rem;    /* 16px */
  --font-size-lg: 1.125rem;  /* 18px */
  --font-size-xl: 1.25rem;   /* 20px */
  --font-size-2xl: 1.5rem;   /* 24px */
  --font-size-3xl: 2rem;     /* 32px */
  --font-size-4xl: 2.5rem;   /* 40px */
  
  --font-weight-normal: 400;
  --font-weight-medium: 500;
  --font-weight-bold: 700;
  
  --line-height-tight: 1.25;
  --line-height-normal: 1.5;
  --line-height-relaxed: 1.75;
  
  /* === ボーダー === */
  --border-width-thin: 1px;
  --border-width-normal: 2px;
  --border-width-thick: 3px;
  
  --border-radius-sm: 0.25rem;
  --border-radius-md: 0.5rem;
  --border-radius-lg: 1rem;
  --border-radius-full: 9999px;
  
  /* === シャドウ === */
  --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
  --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
  --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
  --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
  
  /* === アニメーション === */
  --transition-fast: 0.15s;
  --transition-normal: 0.3s;
  --transition-slow: 0.5s;
  
  --easing-linear: linear;
  --easing-ease: ease;
  --easing-ease-in: ease-in;
  --easing-ease-out: ease-out;
  --easing-ease-in-out: ease-in-out;
  
  /* === ブレークポイント === */
  --breakpoint-sm: 640px;
  --breakpoint-md: 768px;
  --breakpoint-lg: 1024px;
  --breakpoint-xl: 1280px;
  
  /* === z-index === */
  --z-index-dropdown: 1000;
  --z-index-sticky: 1020;
  --z-index-fixed: 1030;
  --z-index-modal: 1040;
  --z-index-popover: 1050;
  --z-index-tooltip: 1060;
}

使用例

.card {
  background: var(--color-gray-50);
  border: var(--border-width-normal) solid var(--color-gray-300);
  border-radius: var(--border-radius-md);
  padding: var(--spacing-lg);
  box-shadow: var(--shadow-md);
  transition: all var(--transition-normal) var(--easing-ease-out);
}

.card:hover {
  box-shadow: var(--shadow-lg);
  transform: translateY(-2px);
}

.card-title {
  font-family: var(--font-sans);
  font-size: var(--font-size-xl);
  font-weight: var(--font-weight-bold);
  line-height: var(--line-height-tight);
  margin-bottom: var(--spacing-md);
  color: var(--color-gray-900);
}

11. ブラウザ対応

フォールバック

/* フォールバック値 */
.element {
  color: #2196f3;                  /* フォールバック */
  color: var(--color-primary);     /* 対応ブラウザ */
}

/* @supportsで検出 */
@supports (--css: variables) {
  .element {
    color: var(--color-primary);
  }
}

対応状況

✅ Chrome 49+
✅ Firefox 31+
✅ Safari 9.1+
✅ Edge 15+
❌ IE 11(未対応)

まとめ

CSS変数でメンテナンスしやすいコードを書こう

メリット

  • ✅ 一箇所で値を管理
  • ✅ ダークモード簡単実装
  • ✅ JavaScriptから動的変更可能
  • ✅ レスポンシブ対応が簡単
  • ✅ デザインシステム構築に最適

ベストプラクティス

  • 意味のある変数名を使う(--color-primary ⭐ --blue ❌)
  • :rootにグローバル変数を定義
  • カテゴリごとにまとめる(カラー、スペーシング等)
  • フォールバック値を用意

学習のステップ

  1. 基本的な変数定義と使用
  2. カラーシステムの構築
  3. ダークモード実装
  4. 完全なデザインシステム構築