本文へスキップ
スキルアップカレッジ

カスタムプロパティ——値を一元管理する

レッスン3:カスタムプロパティ——値を一元管理する

このレッスンで学ぶこと

  • CSS カスタムプロパティの書き方とスコープを理解する
  • 色・余白・文字サイズをカスタムプロパティでまとめる方法を身につける
  • 同じ値を 2 回書いたら変数にするという判断基準を身につける
  • テーマの切り替えという応用と、変数を作りすぎない判断を理解する

前回のレッスンでは、クラス名を見た目ではなく役割で付けるという発想を学びました。今回は、名前の次に CSS を壊れやすくする原因である「値の重複」を扱います。

同じ値が何度も書かれているという問題

CSS を書き進めていくと、同じ色や余白の値があちこちに登場することがよくあります。

.button {
  background: #2c6cd4;
  padding: 12px;
}

.card {
  border-color: #2c6cd4;
  padding: 12px;
}

.nav__link {
  color: #2c6cd4;
}

#2c6cd4 というブランドカラーの値が、3 つの場所に別々に書かれています。ここまではまだ問題が表面化しません。ところが、ブランドカラーを変更することになったとき、この値を使っているすべての場所を探して書き換える必要が出てきます。検索と置換で対応できることもありますが、同じ値がたまたま別の意味で使われている場所まで書き換えてしまう危険もあります。

⚠️ 注意 値の重複は、名前の重複よりも見つけにくい問題です。クラス名の重複は目で追えば気づけますが、#2c6cd4 という値がファイル全体の何か所に散らばっているかは、検索しない限りわかりません。

CSS カスタムプロパティの書き方

この問題を解決するのが、CSS カスタムプロパティです。CSS カスタムプロパティは、値に名前を付けて 1 か所で管理し、必要な場所から呼び出せる仕組みです。CSS 変数と呼ばれることもあります。

書き方は 2 段階です。まず -- から始まる名前で値を定義し、次に var() という関数でその値を呼び出します。

:root {
  --color-brand: #2c6cd4;
}

.button {
  background: var(--color-brand);
}

:root は、文書全体のもっとも外側を指すセレクタです。ここに定義したカスタムプロパティは、ページ全体のどこからでも参照できます。先ほどの例をカスタムプロパティで書き直すと、次のようになります。

:root {
  --color-brand: #2c6cd4;
  --spacing-base: 12px;
}

.button {
  background: var(--color-brand);
  padding: var(--spacing-base);
}

.card {
  border-color: var(--color-brand);
  padding: var(--spacing-base);
}

.nav__link {
  color: var(--color-brand);
}

これで、ブランドカラーを変更したいときは --color-brand の値を 1 か所書き換えるだけで、3 つの場所すべてに変更が反映されます。

中核メッセージ 4:同じ値を何度も書いた時点で、それは変数にすべきものです。

スコープ——どこまで値が届くか

カスタムプロパティには、通常の CSS の指定と同じように「スコープ」という考え方があります。スコープとは、その値が有効な範囲のことです。

:root に定義した値は文書全体に届きますが、特定のセレクタの中に定義すれば、その範囲だけに限定できます。

.card {
  --card-padding: 20px;
  padding: var(--card-padding);
}

.card--compact {
  --card-padding: 12px;
}
<div class="card">通常のカード</div>
<div class="card card--compact">余白が狭いカード</div>

--card-padding は .card の内部で定義されているため、.card の要素とその子要素からしか参照できません。.card--compact が同じ名前の値を上書きすると、そのカードだけ余白が変わります。ページ全体で 1 つの値だけを持つ :root の使い方と、特定の部品だけで完結させるローカルなスコープの使い方は、目的に応じて使い分けます。

💡 ポイント 迷ったときの目安は「サイト全体で 1 つに揃えたい値かどうか」です。ブランドカラーや基本の余白は :root に、特定の部品だけで完結する調整値は、その部品のセレクタの中に置くとスコープが整理されます。

色・余白・文字サイズをまとめる

カスタムプロパティは、色・余白・文字サイズのように、コード全体で繰り返し使う値をまとめるのに向いています。

:root {
  /* 色 */
  --color-brand: #2c6cd4;
  --color-text: #2c2c2c;
  --color-border: #dddddd;

  /* 余白 */
  --spacing-sm: 8px;
  --spacing-md: 16px;
  --spacing-lg: 32px;

  /* 文字サイズ */
  --font-size-body: 16px;
  --font-size-heading: 24px;
}

こうしてまとめておくと、コード全体でどんな値の種類がいくつ存在するかが一望できるようになります。値がバラバラに散らばっている状態と比べて、「いま使える色は何色あるか」「余白の刻みは何段階あるか」を把握しやすくなります。

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

.card__title {
  font-size: var(--font-size-heading);
}

値の名前も、役割で名付けるという考え方と同じです。--color-brand は「ブランドカラー」という役割を、--spacing-md は「中くらいの余白」という役割を語っています。値そのものではなく、値の意味を名前にすることが大切です。

同じ値を 2 回書いたら変数にする、という判断基準

すべての値を最初からカスタムプロパティにする必要はありません。判断基準はシンプルで、同じ値を 2 回書いた時点で変数にするという基準です。

/* 1回目:まだ変数にしなくてよい */
.button {
  padding: 12px 20px;
}

/* 2回目:同じ値が出てきたら変数にするタイミング */
.card {
  padding: 12px 20px;
}

2 か所目が出てきた時点で、次のように値を切り出します。

:root {
  --spacing-button: 12px 20px;
}

.button {
  padding: var(--spacing-button);
}

.card {
  padding: var(--spacing-button);
}

この基準を持っておくと、「そろそろ変数にすべきか」を毎回悩まずに判断できます。1 回しか使わない値まで先回りして変数化すると、かえって間接参照が増え、値を追うコストが上がってしまいます。

flowchart TD
  A[新しい値を書く] --> B{同じ値がほかにもあるか}
  B -->|初めて登場| C[そのまま直接書く]
  B -->|2回目以降| D[カスタムプロパティに切り出す]
  D --> E[var で呼び出す形に置き換える]

この図は、値を書くたびに行う判断の流れを表しています。1 回目は直接書いてよく、2 回目が出た時点で変数化を検討するという順番です。

テーマの切り替えという応用

カスタムプロパティのもう 1 つの利点は、値をまとめて差し替えられることです。この性質を使うと、ライトテーマとダークテーマの切り替えのような機能を、比較的簡潔に実現できます。

:root {
  --color-bg: #ffffff;
  --color-text: #2c2c2c;
}

[data-theme="dark"] {
  --color-bg: #1a1a1a;
  --color-text: #f0f0f0;
}

body {
  background: var(--color-bg);
  color: var(--color-text);
}
<body data-theme="dark">
  ...
</body>

<body> に data-theme="dark" という属性が付くと、--color-bg と --color-text の値がまとめて上書きされ、body に指定した background と color が自動的に切り替わります。個々の要素の CSS を書き換える必要はなく、値の定義側だけを差し替えれば済みます。テーマ切り替え以外にも、特定のセクションだけ配色を変えたいときなど、同じ考え方が応用できます。

📝 補足 テーマ切り替えを実際に画面上で動かすには、data-theme 属性を付け替える処理が必要になりますが、その処理自体は CSS の範囲を超えるため、本レッスンでは扱いません。ここで押さえておきたいのは、値をカスタムプロパティにまとめておけば、切り替えの土台が CSS 側で整うということです。

calc() と組み合わせて値を導き出す

カスタムプロパティは、calc() という計算のための関数と組み合わせることもできます。基準になる値を 1 つ決めておき、そこから倍数や割合で別の値を導き出す使い方です。

:root {
  --spacing-unit: 8px;
}

.card {
  padding: calc(var(--spacing-unit) * 2); /* 16px */
  margin-bottom: calc(var(--spacing-unit) * 3); /* 24px */
}

--spacing-unit という基準値を 1 つ変えるだけで、そこから計算されるすべての余白が連動して変わります。余白の刻みを個別の値として何個も定義する代わりに、基準値と倍率の組み合わせで表現できるため、値どうしの関係性がコードの中に残ります。

📖 もっと詳しく CSS のカスタムプロパティは、ブラウザ側で実行時に値を解決する仕組みです。あらかじめビルド時に値を文字列として置き換えるだけの仕組みとは異なり、data-theme の切り替えのように、ページを表示したあとから値を差し替えられる点が特徴です。この違いが、テーマ切り替えのような動的な用途に向いている理由です。

フォールバック値を指定する

var() には、2 つ目の引数としてフォールバック値(値が定義されていなかったときに使われる代わりの値)を指定できます。

.badge {
  background: var(--color-badge, #999999);
}

--color-badge が :root などで定義されていない場合、.badge の背景色は #999999 になります。定義されていれば、その値が優先されます。この仕組みは、部品を単体で使い回すときに役立ちます。テーマ側で --color-badge を用意していない環境で部品を使っても、フォールバック値のおかげで見た目が崩れずに済みます。

:root {
  --color-badge: #2c6cd4;
}

.badge {
  /* --color-badge が定義されているので #2c6cd4 が使われる */
  background: var(--color-badge, #999999);
}

💡 ポイント フォールバック値は「もしもの保険」です。すべてのカスタムプロパティに毎回フォールバック値を付ける必要はありませんが、ほかのプロジェクトへ持ち出す可能性がある部品や、テーマ側の定義漏れが心配な値には付けておくと安全です。

変数を作りすぎない判断

カスタムプロパティの便利さに気づくと、あらゆる値を変数にしたくなるかもしれません。ですが、変数を作りすぎることにも代償があります。

:root {
  --card-title-font-size: 18px;
  --card-title-line-height: 1.4;
  --card-title-letter-spacing: 0.02em;
  --card-title-margin-bottom: 8px;
  --card-title-color-hover: #1a4fa0;
}

1 つの部品のためだけに 5 個も 6 個も専用の変数を作ると、値を追いかけるための間接参照ばかりが増え、直接値を書いていたときより読み解きにくくなる場合があります。

変数にする価値が高いのは、複数の部品にまたがって使われる値です。逆に、1 つの部品にしか登場しない細かい調整値は、その部品の CSS の中に直接書いてしまってかまいません。

/* 複数の部品で使うので変数にする価値がある */
:root {
  --color-brand: #2c6cd4;
  --spacing-md: 16px;
}

/* この部品にしか登場しない調整値は直接書いてよい */
.card__title {
  letter-spacing: 0.02em;
}

🔰 初学者の方へ 「変数にすべきかどうか」で迷ったときは、「同じ値を 2 回書いたら変数にする」という基準に戻ってください。基準を持たずに感覚だけで判断すると、変数を作りすぎる方向にも、まったく作らない方向にも偏りやすくなります。判断基準を 1 つ持っておくだけで、チームで作業していても迷う場面がぐっと減ります。

実習:重複した値をカスタムプロパティに切り出す

次の CSS には、同じ値が複数の場所に重複して書かれています。カスタムプロパティに切り出して書き直してみましょう。

.header {
  background: #1c2833;
  padding: 24px;
}

.footer {
  background: #1c2833;
  padding: 24px;
}

.button {
  background: #2c6cd4;
  border-radius: 6px;
}

.link {
  color: #2c6cd4;
}

書き直しの手順は次のとおりです。

  1. コード全体を見渡し、同じ値が 2 回以上登場している箇所を洗い出す
  2. それぞれの値が何の役割を持つかを考え、役割にもとづいた変数名を付ける(例:--color-dark、--spacing-lg、--color-brand)
  3. :root に変数の定義をまとめる
  4. 元のコードの値を var() を使った呼び出しに置き換える
  5. 1 回しか登場しない値(border-radius: 6px など)まで変数化する必要があるかどうかを、本レッスンの判断基準に照らして検討する

まとめ

このレッスンでは、以下のことを学びました。

  • 同じ値が複数の場所に重複して書かれていると、変更のたびにすべての場所を探す必要が生まれること
  • CSS カスタムプロパティは --名前: 値 で定義し、var(--名前) で呼び出す仕組みであること
  • :root に定義すると文書全体に、特定のセレクタの中に定義するとその範囲だけにスコープが限定されること
  • 色・余白・文字サイズのような繰り返し使う値をカスタムプロパティにまとめておくと、コード全体の見通しがよくなること
  • 同じ値を 2 回書いた時点で変数にするという基準が、変数化のタイミングを判断する助けになること
  • カスタムプロパティの値を差し替えることで、テーマ切り替えのような応用ができること
  • 変数は作りすぎても読み解きにくくなるため、複数の部品にまたがる値に絞って変数化すること

次のレッスンでは、CSS Grid を使った 2 次元のレイアウトを学びます。値の次は配置の技術です。


確認クイズ

このレッスンの理解度をチェックしましょう。