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にグローバル変数を定義
- カテゴリごとにまとめる(カラー、スペーシング等)
- フォールバック値を用意
学習のステップ
- 基本的な変数定義と使用
- カラーシステムの構築
- ダークモード実装
- 完全なデザインシステム構築