CSS変数で配色と余白を一括管理する:基本構文から実務的な設計まで
CSS変数を使うと、サイト内で繰り返す色や余白を一か所に定義し、まとめて変更できます。たとえばブランドカラーを変更するとき、ボタン、リンク、見出しを一つずつ探して直す必要がありません。
基本は、--から始まる名前で値を定義し、使う場所でvar()を指定するだけです。
:root {
--color-primary: #2563eb;
--space-md: 16px;
}
.button {
padding: var(--space-md);
background-color: var(--color-primary);
}
この記事では、CSS変数の基本構文から、配色・余白の管理、上書き、フォールバック、JavaScriptとの連携、よくある失敗までを順に解説します。
- 対象読者:CSSの基本的なセレクターとプロパティを理解している人
- 対象環境:標準的なHTMLとCSSを使うWebページ
- 確認時点:2026年7月29日
- できること:共通値の一括変更、部品ごとの上書き、テーマ切り替え
CSS変数とは何か
CSS変数は、CSS内で再利用する値に名前を付ける仕組みです。正式には「カスタムプロパティ」と呼ばれます。
通常のCSSでは、同じ色を複数の場所へ直接書きます。
.site-header {
background-color: #2563eb;
}
.button {
background-color: #2563eb;
}
.text-link {
color: #2563eb;
}
この状態で色を変えるには、該当箇所をすべて探さなければなりません。CSS変数に置き換えると、変更箇所を集約できます。
:root {
--color-primary: #2563eb;
}
.site-header {
background-color: var(--color-primary);
}
.button {
background-color: var(--color-primary);
}
.text-link {
color: var(--color-primary);
}
--color-primaryを変更すれば、3か所の表示が一度に変わります。色だけでなく、余白、文字サイズ、角丸、影、アニメーション時間なども管理できます。
ここがポイント: CSS変数の価値は、短く書くことより「同じ意味の値を一か所から管理できること」にあります。
基本の書き方
CSS変数は「定義」と「参照」の2段階で使います。
1. --名前: 値で定義する
変数名は半角ハイフン2個から始めます。
:root {
--color-text: #1f2937;
--color-background: #ffffff;
--space-sm: 8px;
--space-md: 16px;
--radius-md: 8px;
}
--color-textや--space-mdは任意の名前です。ただし、大文字と小文字は区別されます。
:root {
--main-color: blue;
--Main-Color: red;
}
この2つは別のカスタムプロパティとして扱われます。チーム開発では、小文字とハイフンに統一すると混乱を減らせます。
2. var()で値を参照する
定義した値は、通常のCSSプロパティの値として参照します。
body {
color: var(--color-text);
background-color: var(--color-background);
}
.card {
padding: var(--space-md);
border-radius: var(--radius-md);
}
var()を書かず、変数名だけを指定しても動きません。
/* NG */
.card {
padding: --space-md;
}
/* OK */
.card {
padding: var(--space-md);
}
3. ページ全体で使う値は:rootに置く
:rootは文書のルート要素を選ぶ疑似クラスです。HTML文書では通常html要素に当たり、ここで定義したカスタムプロパティは子孫へ継承されます。
:root {
--color-primary: #2563eb;
}
ページ全体で使う値には便利ですが、すべての値を:rootへ集める必要はありません。特定の部品だけで使う値は、その部品のセレクターに置いたほうが関係を追いやすくなります。
.alert {
--alert-color: #b91c1c;
--alert-background: #fef2f2;
color: var(--alert-color);
background-color: var(--alert-background);
}
色と余白をまとめて管理する実装例
実務では、色や余白を単独の値としてではなく、用途ごとのまとまりとして定義すると管理しやすくなります。
HTMLの入力例
<article class="card">
<h2 class="card__title">更新のお知らせ</h2>
<p class="card__text">新しい管理画面を公開しました。</p>
<a class="button" href="#details">詳細を見る</a>
</article>
CSSの実装例
:root {
/* 色 */
--color-primary: #2563eb;
--color-primary-hover: #1d4ed8;
--color-text: #1f2937;
--color-text-muted: #6b7280;
--color-surface: #ffffff;
--color-border: #d1d5db;
/* 余白 */
--space-xs: 4px;
--space-sm: 8px;
--space-md: 16px;
--space-lg: 24px;
--space-xl: 32px;
/* その他の共通値 */
--radius-md: 8px;
--shadow-card: 0 4px 12px rgb(0 0 0 / 10%);
}
body {
margin: 0;
color: var(--color-text);
background-color: #f3f4f6;
}
.card {
max-width: 480px;
margin: var(--space-xl) auto;
padding: var(--space-lg);
background-color: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
box-shadow: var(--shadow-card);
}
.card__title {
margin: 0 0 var(--space-sm);
}
.card__text {
margin: 0 0 var(--space-lg);
color: var(--color-text-muted);
}
.button {
display: inline-block;
padding: var(--space-sm) var(--space-md);
color: #ffffff;
background-color: var(--color-primary);
border-radius: var(--radius-md);
text-decoration: none;
}
.button:hover {
background-color: var(--color-primary-hover);
}
表示結果と変更箇所
この例では、白いカード内に見出し、説明文、青いボタンが表示されます。余白は4px、8px、16px、24px、32pxの段階にそろいます。
ボタンを緑へ変更したい場合は、次の2行だけを差し替えます。
:root {
--color-primary: #15803d;
--color-primary-hover: #166534;
}
カードやボタンのCSSを探し回らずに済むため、変更漏れを防ぎやすくなります。
変数名は「見た目」より「役割」で付ける
共通値には、その値が何に使われるか分かる名前を付けます。 現在の色名だけで命名すると、デザイン変更後に名前と実際の値が食い違うからです。
変更に弱い名前
:root {
--blue: #2563eb;
--gray: #6b7280;
}
ブランドカラーが緑へ変わると、--blueへ緑色を入れる不自然な状態になります。変数名を変更すれば、参照箇所も直さなければなりません。
役割を表す名前
:root {
--color-primary: #2563eb;
--color-text-muted: #6b7280;
--color-danger: #dc2626;
}
この形なら、実際の色が変わっても役割は変わりません。
命名は次のように整理できます。
--color-primary:主要な操作やブランドを示す色--color-text:本文の基本色--color-surface:カードなどの面に使う色--space-md:標準的な余白--radius-md:標準的な角丸--duration-fast:短いアニメーション時間
ただし、部品の内部だけで使う値には--button-backgroundのような具体的な名前も有効です。全体用と部品用を分けると、変更の影響範囲が読みやすくなります。
適用範囲と上書きを使いこなす
CSS変数は、定義した要素とその子孫で利用できます。この性質を使えば、同じ部品を保ったまま一部分だけ見た目を変えられます。
部品ごとに値を上書きする
.button {
--button-bg: #2563eb;
--button-bg-hover: #1d4ed8;
background-color: var(--button-bg);
}
.button:hover {
background-color: var(--button-bg-hover);
}
.button--danger {
--button-bg: #dc2626;
--button-bg-hover: #b91c1c;
}
HTMLではクラスを追加するだけです。
<button class="button">保存する</button>
<button class="button button--danger">削除する</button>
.button--dangerの内側では変数の値が赤系に置き換わります。ボタン本体のプロパティを重複して書く必要はありません。
セクション単位でテーマを変える
:root {
--color-text: #1f2937;
--color-surface: #ffffff;
}
.dark-section {
--color-text: #f9fafb;
--color-surface: #111827;
}
.panel {
color: var(--color-text);
background-color: var(--color-surface);
}
<section>
<div class="panel">明るいパネル</div>
</section>
<section class="dark-section">
<div class="panel">暗いパネル</div>
</section>
.dark-section内の.panelだけが暗い配色になります。CSS変数は単なる文字列置換ではなく、CSSのカスケードと継承に従って値が決まる仕組みです。
フォールバック値を指定する
未定義の変数に備えるなら、var()の第2引数へ代替値を書きます。
.notice {
color: var(--notice-color, #b91c1c);
}
--notice-colorが利用できる場合はその値を使い、未定義または無効なら#b91c1cを使います。
複数段階のフォールバック
別の変数を代替候補にする場合は、var()を入れ子にします。
.notice {
color: var(--notice-color, var(--color-danger, #b91c1c));
}
優先順位は次の通りです。
--notice-color--color-danger#b91c1c
次の書き方は、複数候補の指定にはなりません。
/* --color-dangerを第2候補として参照できない */
.notice {
color: var(--notice-color, --color-danger, #b91c1c);
}
最初のカンマより後ろは、まとめて一つのフォールバック値として解釈されます。別のカスタムプロパティを参照するなら、必ずvar(--color-danger)と書きます。
なお、フォールバックは「CSS変数に対応していないブラウザ向けの代替」ではありません。var()自体を解釈できる環境で、参照先が未定義または無効な場合に使われます。
calc()と組み合わせて余白を計算する
基準となる余白を定義し、倍数でサイズを作ることもできます。
:root {
--space-unit: 4px;
}
.card {
padding: calc(var(--space-unit) * 4);
margin-bottom: calc(var(--space-unit) * 6);
}
この結果、paddingは16px、margin-bottomは24pxになります。基準値を5pxへ変更すれば、それぞれ20pxと30pxへ連動します。
一方、値と単位を単純につなげる書き方はできません。
:root {
--space-number: 16;
}
/* NG:var()の後ろにpxを連結できない */
.card {
padding: var(--space-number)px;
}
/* OK */
.card {
padding: calc(var(--space-number) * 1px);
}
単位込みの値を保存できるなら、最初から--space-md: 16pxと定義するほうが簡潔です。単位なしの数値は、複数の単位へ変換したい場合に限って使うとよいでしょう。
ダークモードへ応用する
CSS変数を使うと、部品のCSSを変えずに配色一式を切り替えられます。
:root {
--color-text: #1f2937;
--color-background: #ffffff;
--color-surface: #f3f4f6;
--color-border: #d1d5db;
}
@media (prefers-color-scheme: dark) {
:root {
--color-text: #f9fafb;
--color-background: #111827;
--color-surface: #1f2937;
--color-border: #4b5563;
}
}
body {
color: var(--color-text);
background-color: var(--color-background);
}
.card {
background-color: var(--color-surface);
border: 1px solid var(--color-border);
}
メディアクエリ内では値だけを上書きしています。bodyや.cardの宣言を丸ごと複製しないため、通常表示とダークモードの差分が明確です。
配色を決める際は、変数化だけで満足せず、文字と背景のコントラスト、フォーカス表示、リンクが色以外でも判別できるかを確認してください。
JavaScriptから値を変更する
利用者がテーマを選ぶ画面では、JavaScriptからカスタムプロパティを書き換えられます。
<button id="theme-button" type="button">緑のテーマに変更</button>
const button = document.querySelector('#theme-button');
button.addEventListener('click', () => {
document.documentElement.style.setProperty(
'--color-primary',
'#15803d'
);
});
document.documentElementはHTML文書のhtml要素です。ボタンを押すと、その要素へ次のインラインスタイルが設定された状態になります。
<html style="--color-primary: #15803d;">
現在の値を読み取る場合は、算出済みスタイルを取得します。
const styles = getComputedStyle(document.documentElement);
const primaryColor = styles
.getPropertyValue('--color-primary')
.trim();
console.log(primaryColor);
CSSだけで実現できる状態変化までJavaScriptへ移す必要はありません。ホバーやメディアクエリはCSSに任せ、利用者の選択を保存するテーマ機能などでJavaScriptを使うと責務を分けやすくなります。
よくある失敗と直し方
CSS変数が反映されないときは、名前、適用範囲、値の形式を順に確認します。
変数名のつづりや大文字・小文字が違う
:root {
--color-primary: #2563eb;
}
.button {
/* NG:定義名と大文字・小文字が異なる */
background-color: var(--Color-Primary);
}
開発者ツールで対象要素のスタイルを開き、var()の参照先と定義名を見比べます。命名規則を小文字に統一すると防ぎやすいミスです。
定義した要素の外側から参照している
.card {
--card-space: 16px;
}
.page-footer {
padding: var(--card-space);
}
.page-footerが.cardの子孫でなければ、--card-spaceは届きません。両方で使う値なら、共通の祖先または:rootへ定義を移します。
値の形式が使用先に合っていない
カスタムプロパティにはさまざまな値を保存できますが、参照先のCSSプロパティがその値を受け入れるとは限りません。
:root {
--card-space: red;
}
.card {
padding: var(--card-space);
}
redはpaddingの値として無効です。カスタムプロパティの宣言自体が書けても、代入後のプロパティが有効になるとは限らない点に注意してください。
循環参照を作っている
:root {
--space-a: var(--space-b);
--space-b: var(--space-a);
}
互いを参照する値は解決できません。変数同士の依存を一方向にします。
:root {
--space-base: 8px;
--space-md: calc(var(--space-base) * 2);
}
!importantを増やして解決しようとしている
期待した値にならない原因は、変数そのものより、定義位置やセレクターの詳細度、読み込み順にある場合があります。まず開発者ツールで、どの宣言が採用され、どの宣言が上書きされたかを確認してください。
!importantを追加すると一時的に直っても、その後の上書きが難しくなります。テーマや部品の境界に合わせて定義位置を整理するほうが保守しやすくなります。
@propertyはいつ必要か
通常の色・余白管理なら、--名前とvar()だけで十分です。@propertyは、値の型、継承の有無、初期値を明示したい場合に使います。
@property --accent-color {
syntax: "<color>";
inherits: true;
initial-value: #2563eb;
}
:root {
--accent-color: #15803d;
}
.button {
background-color: var(--accent-color);
}
この登録では、--accent-colorが色として扱われ、初期値は#2563ebになります。
@propertyが役立つのは、次のような場面です。
- 無効な値を型の指定によって制御したい
- カスタムプロパティを継承させたくない
- カスタムプロパティを使ったアニメーションを型に沿って補間したい
- 再利用する部品の既定値を明確にしたい
初心者が共通色や余白を整理する段階では、最初から導入する必要はありません。基本のカスケードと継承を理解した後に検討すると、設定の意味を判断しやすくなります。
Sass変数との違いと使い分け
CSS変数とSassなどのプリプロセッサー変数は、値を再利用できる点は似ていますが、値が決まる時点が異なります。
CSS変数
- ブラウザ上にカスタムプロパティとして残る
- カスケードや継承を利用できる
- メディアクエリやクラスで値を上書きできる
- JavaScriptから変更できる
Sass変数
- CSSを生成するビルド時に値が展開される
- 演算、ループ、関数などSassの機能と組み合わせられる
- 出力されたCSSにはSass変数が残らない
- ブラウザ上でSass変数を変更することはできない
配色テーマや部品ごとの上書きにはCSS変数が向いています。CSS生成時の計算や繰り返し処理にはSassが便利です。両者は競合するものではなく、SassでCSS変数の定義を生成する使い方もできます。
実務で管理しやすい設計
変数を増やすだけでは、CSSが自動的に読みやすくなるわけではありません。変更理由が同じ値をまとめ、影響範囲が分かる場所へ置くことが重要です。
最初は変更頻度の高い値に絞る
まず変数化しやすいのは、繰り返し登場し、まとめて変更する可能性がある値です。
- ブランドカラーと状態色
- 本文色、補助文字色、背景色、境界線色
- 余白の段階
- 角丸
- 影
- アニメーション時間
一度しか使わず、他の値と連動しない数値まで機械的に変数化すると、定義と使用箇所を往復する手間が増えます。
基礎値と用途別の値を分ける
規模が大きくなったら、色そのものと用途を分ける方法があります。
:root {
/* 基礎値 */
--blue-600: #2563eb;
--blue-700: #1d4ed8;
--red-600: #dc2626;
--gray-900: #111827;
/* 用途別 */
--color-primary: var(--blue-600);
--color-primary-hover: var(--blue-700);
--color-danger: var(--red-600);
--color-text: var(--gray-900);
}
部品は--blue-600ではなく、原則として--color-primaryなどの用途別変数を参照します。ブランドカラーを青から別の色へ変えるときも、部品側の意味を保ったまま値を差し替えられます。
導入前のチェックリスト
- 同じ値を複数箇所で使っているか
- その値を一括変更する場面があるか
- 名前から用途を推測できるか
- 全体用と部品内だけの値を分けているか
- 未定義時にフォールバックが必要か
- 開発者ツールで上書き元を確認できるか
- 色を変えた後も文字や操作部品を識別できるか
最初の一歩として、既存CSSから主要色を1色、余白を3段階だけ選び、:rootへ移してみてください。その後、実際に一括変更する値だけを追加します。変数の数ではなく、次のデザイン変更で直す場所が明確になったかを判断基準にすると、管理の仕組みが形だけになりません。
