はじめに
前回の記事ではレスポンシブ対応の実践的なコツをHTML・CSS両方の観点からまとめました。今回はCSS変数(カスタムプロパティ)を使ったテーマカラーの管理方法とダークモード対応の実装を解説します。CSS変数はサイト全体の色・フォント・余白を一箇所で管理できる仕組みで、デザインの変更をファイル全体に一括反映できます。ダークモード対応の実装まで合わせて解説します。
1. CSS変数(カスタムプロパティ)とは
1.1 CSS変数の概要
**CSS変数(カスタムプロパティ)**とはCSS内に独自の変数を定義して再利用できる仕組みです。-- から始まる名前で定義して var() 関数で使います。
/* 変数の定義 */
:root {
--color-primary: #1A5FC0;
}
/* 変数の使用 */
.btn {
background-color: var(--color-primary);
}
1.2 Sassの変数との違い
CSSにはもともとSass(CSSプリプロセッサ)の変数($color: #1A5FC0)がよく使われてきました。CSS変数との主な違いは以下の通りです。
| 比較項目 | CSS変数 | Sass変数 |
|---|---|---|
| 動的な変更 | JavaScriptやメディアクエリで実行時に変更できる | ビルド時に固定される |
| スコープ | 要素ごとにスコープを持てる(カスケード) | グローバルまたはブロックスコープのみ |
| ブラウザサポート | 現代ブラウザで完全対応 | ビルドツールが必要 |
| DevToolsでの確認 | 開発者ツールでリアルタイムに値を確認できる | コンパイル後のCSSしか確認できない |
💡 新しいプロジェクトではCSS変数を優先的に使いましょう: ビルドツールが不要で学習コストも低く、JavaScriptとの連携もシンプルです。
2. テーマカラーをCSS変数で管理する
2.1 カラーシステムの設計
CSS変数でカラーシステムを設計するとき、「原始値」と「セマンティックな名前」の2層構造にすると変更に強いシステムになります。
:root {
/* ===== 第1層:原始値(プリミティブ) ===== */
/* 実際の色の値を定義する */
/* ブルー系 */
--blue-100: #dbeafe;
--blue-300: #93c5fd;
--blue-500: #3b82f6;
--blue-600: #1A5FC0;
--blue-800: #0d3a7a;
--blue-900: #1e3a5f;
/* グレー系 */
--gray-50: #f9fafb;
--gray-100: #f3f4f6;
--gray-200: #e5e7eb;
--gray-400: #9ca3af;
--gray-600: #4b5563;
--gray-800: #1f2937;
--gray-900: #111827;
/* アクセント系 */
--green-500: #22c55e;
--red-500: #ef4444;
--yellow-500: #eab308;
/* ===== 第2層:セマンティック(意味のある名前) ===== */
/* 用途に応じた名前で参照する */
/* 背景色 */
--color-bg: var(--gray-50);
--color-bg-sub: var(--gray-100);
--color-bg-muted: var(--gray-200);
/* テキスト色 */
--color-text: var(--gray-900);
--color-text-sub: var(--gray-600);
--color-text-muted: var(--gray-400);
--color-text-inverse: #ffffff;
/* ブランドカラー */
--color-primary: var(--blue-600);
--color-primary-hover: var(--blue-800);
--color-primary-light: var(--blue-100);
/* ステータスカラー */
--color-success: var(--green-500);
--color-error: var(--red-500);
--color-warning: var(--yellow-500);
/* ボーダー */
--color-border: var(--gray-200);
--color-border-hover: var(--gray-400);
/* シャドウ */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 16px 40px rgba(0, 0, 0, 0.12);
}
💡 セマンティックな名前を使うとダークモード対応が簡単になります: --color-bg という名前なら、ダークモードのときに :root の値を書き換えるだけで全体に反映されます。--gray-50 のような具体的な色名を直接使うと、ダークモードで個別に上書きする必要が生じて管理が煩雑になります。
2.2 フォントと余白もCSS変数で管理する
:root {
/* ===== タイポグラフィ ===== */
--font-sans: 'Noto Sans JP', 'Inter', sans-serif;
--font-mono: 'Fira Code', 'Courier New', monospace;
--text-xs: 0.75rem; /* 12px */
--text-sm: 0.875rem; /* 14px */
--text-base: 1rem; /* 16px */
--text-lg: 1.125rem; /* 18px */
--text-xl: 1.25rem; /* 20px */
--text-2xl: 1.5rem; /* 24px */
--text-3xl: 1.875rem; /* 30px */
--text-4xl: 2.25rem; /* 36px */
--text-5xl: 3rem; /* 48px */
--leading-tight: 1.25;
--leading-normal: 1.6;
--leading-loose: 1.9;
--font-normal: 400;
--font-medium: 500;
--font-bold: 700;
/* ===== 余白(スペーシング) ===== */
--space-1: 0.25rem; /* 4px */
--space-2: 0.5rem; /* 8px */
--space-3: 0.75rem; /* 12px */
--space-4: 1rem; /* 16px */
--space-6: 1.5rem; /* 24px */
--space-8: 2rem; /* 32px */
--space-10: 2.5rem; /* 40px */
--space-12: 3rem; /* 48px */
--space-16: 4rem; /* 64px */
--space-20: 5rem; /* 80px */
--space-24: 6rem; /* 96px */
/* ===== 角丸 ===== */
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-xl: 16px;
--radius-full: 9999px;
/* ===== z-index ===== */
--z-dropdown: 20;
--z-header: 30;
--z-modal: 100;
--z-toast: 200;
/* ===== トランジション ===== */
--transition-fast: 0.15s ease;
--transition-base: 0.25s ease;
--transition-slow: 0.4s ease;
}
2.3 実際の使い方
/* CSS変数を使ったコンポーネントの記述 */
body {
font-family: var(--font-sans);
font-size: var(--text-base);
line-height: var(--leading-normal);
color: var(--color-text);
background-color: var(--color-bg);
}
/* ボタン */
.btn-primary {
display: inline-flex;
align-items: center;
gap: var(--space-2);
padding: var(--space-3) var(--space-8);
background-color: var(--color-primary);
color: var(--color-text-inverse);
font-weight: var(--font-medium);
font-size: var(--text-base);
border-radius: var(--radius-md);
border: none;
cursor: pointer;
transition: background-color var(--transition-base),
transform var(--transition-fast),
box-shadow var(--transition-base);
}
.btn-primary:hover {
background-color: var(--color-primary-hover);
box-shadow: var(--shadow-md);
transform: translateY(-1px);
}
/* カード */
.card {
background-color: var(--color-bg);
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
padding: var(--space-6);
box-shadow: var(--shadow-sm);
transition: box-shadow var(--transition-base),
border-color var(--transition-base);
}
.card:hover {
box-shadow: var(--shadow-md);
border-color: var(--color-border-hover);
}
3. ダークモードを実装する
3.1 prefers-color-schemeメディアクエリ
OSの設定(ダークモード・ライトモード)を CSS のメディアクエリで検出できます。
/* ライトモード(デフォルト) */
:root {
--color-bg: #ffffff;
--color-bg-sub: #f9fafb;
--color-text: #111827;
--color-text-sub: #4b5563;
--color-primary: #1A5FC0;
--color-border: #e5e7eb;
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.1);
}
/* ダークモード */
@media (prefers-color-scheme: dark) {
:root {
--color-bg: #0f172a;
--color-bg-sub: #1e293b;
--color-text: #f1f5f9;
--color-text-sub: #94a3b8;
--color-primary: #60a5fa;
--color-border: #334155;
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4);
}
}
この設定だけでボタン・カード・テキストなどCSS変数を使っているすべての要素が自動的にダークモードに切り替わります。
3.2 手動でダークモードを切り替える
OSの設定に依存するだけでなく、ボタンで手動切り替えもできるようにするとユーザー体験が上がります。
HTML:
<!-- ダークモード切り替えボタン -->
<button class="theme-toggle" id="theme-toggle" aria-label="ダークモードを切り替える">
<span class="theme-icon theme-icon--light" aria-hidden="true">☀️</span>
<span class="theme-icon theme-icon--dark" aria-hidden="true">🌙</span>
</button>
CSS:
/* data-theme属性でテーマを切り替える */
/* ライトモード(デフォルト) */
:root,
[data-theme="light"] {
--color-bg: #ffffff;
--color-bg-sub: #f9fafb;
--color-text: #111827;
--color-text-sub: #4b5563;
--color-primary: #1A5FC0;
--color-border: #e5e7eb;
}
/* ダークモード */
[data-theme="dark"] {
--color-bg: #0f172a;
--color-bg-sub: #1e293b;
--color-text: #f1f5f9;
--color-text-sub: #94a3b8;
--color-primary: #60a5fa;
--color-border: #334155;
}
/* OSの設定がダークモードのときはデフォルトでダークにする */
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--color-bg: #0f172a;
--color-bg-sub: #1e293b;
--color-text: #f1f5f9;
--color-text-sub: #94a3b8;
--color-primary: #60a5fa;
--color-border: #334155;
}
}
/* ボタンのアイコン切り替え */
[data-theme="dark"] .theme-icon--light { display: none; }
[data-theme="dark"] .theme-icon--dark { display: inline; }
.theme-icon--dark { display: none; }
.theme-icon--light { display: inline; }
JavaScript:
const toggle = document.getElementById( 'theme-toggle' );
const root = document.documentElement;
const STORAGE_KEY = 'theme';
// 保存済みのテーマを読み込む
const savedTheme = localStorage.getItem( STORAGE_KEY );
if ( savedTheme ) {
root.setAttribute( 'data-theme', savedTheme );
}
// ボタンでテーマを切り替える
toggle.addEventListener( 'click', () => {
const current = root.getAttribute( 'data-theme' );
const next = current === 'dark' ? 'light' : 'dark';
root.setAttribute( 'data-theme', next );
localStorage.setItem( STORAGE_KEY, next );
// aria-labelも更新する
toggle.setAttribute(
'aria-label',
next === 'dark' ? 'ライトモードに切り替える' : 'ダークモードに切り替える'
);
} );
3.3 テーマ切り替え時のちらつきを防ぐ
ページ読み込み時に一瞬ライトモードが表示されてからダークモードに切り替わる「ちらつき(FOUC)」を防ぎます。
<!-- <head>の中・CSSより前に記述する -->
<script>
(function() {
const saved = localStorage.getItem( 'theme' );
const prefer = window.matchMedia( '(prefers-color-scheme: dark)' ).matches
? 'dark'
: 'light';
const theme = saved || prefer;
document.documentElement.setAttribute( 'data-theme', theme );
})();
</script>
⚠️ このスクリプトはCSSより前に読み込む必要があります: <head> の中・<link rel="stylesheet"> より前に配置することで、スタイルが適用される前にテーマが設定されます。
4. CSS変数をJavaScriptから操作する
4.1 JavaScriptでCSS変数を取得・変更する
// CSS変数の値を取得する
const root = document.documentElement;
const primaryColor = getComputedStyle( root )
.getPropertyValue( '--color-primary' )
.trim();
console.log( primaryColor ); // "#1A5FC0"
// CSS変数の値を変更する
root.style.setProperty( '--color-primary', '#8E44AD' );
// 特定の要素のCSS変数を変更する
const card = document.querySelector( '.card' );
card.style.setProperty( '--card-bg', '#f0f4ff' );
4.2 コンポーネントのカラーをCSS変数で渡す
<!-- style属性でCSS変数を渡す -->
<div class="progress-bar" style="--progress: 75%;">
<div class="progress-fill"></div>
</div>
<div class="avatar" style="--avatar-color: #8E44AD;">
山田
</div>
/* CSS変数をコンポーネントのスタイルに使う */
.progress-bar {
height: 8px;
background-color: var(--color-border);
border-radius: var(--radius-full);
overflow: hidden;
}
.progress-fill {
height: 100%;
width: var(--progress, 0%); /* 第2引数はフォールバック値 */
background-color: var(--color-primary);
border-radius: var(--radius-full);
transition: width var(--transition-slow);
}
.avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background-color: var(--avatar-color, var(--color-primary));
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: var(--text-sm);
font-weight: var(--font-bold);
}
💡 var() の第2引数はフォールバック値です: var(--progress, 0%) のように書くと変数が未定義の場合に 0% が使われます。コンポーネントのデフォルト値として活用しましょう。
5. ダークモードで注意すべきポイント
5.1 画像の扱い
/* ダークモードで画像の輝度を少し下げる */
@media (prefers-color-scheme: dark) {
img:not([src*=".svg"]) {
filter: brightness(0.9);
}
}
/* または data-theme を使う場合 */
[data-theme="dark"] img:not([src*=".svg"]) {
filter: brightness(0.85);
}
5.2 ボーダーとシャドウの調整
/* ダークモードではシャドウが見えにくいのでボーダーで代用する */
:root {
--shadow-card: 0 4px 12px rgba(0, 0, 0, 0.1);
--border-card: 1px solid transparent;
}
[data-theme="dark"] {
--shadow-card: none;
--border-card: 1px solid var(--color-border);
}
.card {
box-shadow: var(--shadow-card);
border: var(--border-card);
}
5.3 コントラスト比の確認
ダークモードでもWCAGの基準(テキストと背景が4.5:1以上)を満たしているか確認します。
| 組み合わせ | コントラスト比 | 評価 |
|---|---|---|
#f1f5f9 テキスト / #0f172a 背景 | 約15:1 | ✅ 非常に良好 |
#94a3b8 サブテキスト / #0f172a 背景 | 約5:1 | ✅ AA基準クリア |
#60a5fa リンク / #0f172a 背景 | 約7:1 | ✅ 非常に良好 |
6. 実装チェックリスト
✓ 原始値とセマンティックな名前の2層構造でCSS変数を設計しているか
✓ 色だけでなくフォント・余白・角丸・z-indexもCSS変数で管理しているか
✓ prefers-color-scheme: dark でOSのダークモード設定に対応しているか
✓ data-theme 属性を使った手動切り替えが実装されているか
✓ ユーザーのテーマ設定を localStorage に保存しているか
✓ ページ読み込み時のちらつき防止スクリプトを <head> の先頭に入れているか
✓ ダークモードでも画像が適切な明るさで表示されているか
✓ ダークモードのコントラスト比が4.5:1以上か確認したか
✓ var() のフォールバック値を適切に設定しているか
✓ Chrome DevToolsでライト・ダーク両方の表示を確認したか
まとめ
今回はCSS変数を使ったテーマカラーの管理方法とダークモード対応の実装を解説しました。ポイントをまとめると:
- CSS変数は原始値(
--blue-600)とセマンティックな名前(--color-primary)の2層構造にすると変更に強い - 色だけでなくフォント・余白・角丸・z-index・トランジションもCSS変数で一元管理する
prefers-color-scheme: darkだけでOSの設定に自動対応できる- 手動切り替えには
data-theme属性とlocalStorageの組み合わせが使いやすい - ページ読み込み時のちらつき防止スクリプトは
<head>の最初に配置する - ダークモードでは画像の輝度を少し下げ・シャドウをボーダーに置き換えると自然に見える
- コンポーネントに
style属性でCSS変数を渡すと再利用性の高い実装ができる
次の記事では、PHPを使った「お問い合わせフォーム(送信処理)」の仕組みについて解説します。お楽しみに!
コメント