Sass 変数と CSS カスタムプロパティと calc() の使い分け

CSS で値を管理する手段として、Sass 変数、CSS カスタムプロパティ(var())、calc() の 3 つがあります。どれも「数値に意味を持たせる」「繰り返しを避ける」という目的で使われますが、動作するタイミングと得意な場面がそれぞれ異なります。

動作タイミングの違い

3 つの仕組みは、値が確定するタイミングが根本的に違います。

Sass 変数

コンパイル時に値が展開され、出力される CSS には固定値しか残らない。ブラウザは Sass 変数の存在を知らない。

CSS カスタムプロパティ(var())

ブラウザが描画する時点で値が解決される。カスケードに従って要素ごとに異なる値を持てる。

calc()

ブラウザが描画する時点で計算が実行される。vh% のようなランタイム単位と固定値を組み合わせた計算ができる。

この違いが使い分けの基準になります。

calc() だけで済む場面

ランタイムで変わる単位と固定値の組み合わせで、その場限りの計算であれば calc() だけで十分です。

.sidebar {
  height: calc(100vh - 60px);
}

.container {
  width: calc(100% - 32px);
}

100vh100% はブラウザの描画時に決まる値なので、Sass のコンパイル時には計算できません。calc() を使えばブラウザが実行時に計算してくれます。

この 60px を他のどこからも参照しないなら、わざわざ変数に切り出す必要はありません。変数化はコードを追いやすくする手段であって、1 箇所でしか使わない値まで変数にするのは過剰設計です。

var() が必要な場面

同じ値を複数箇所で共有する場合や、条件によって値を切り替える場合には CSS カスタムプロパティが必要です。

:root {
  --header-height: 60px;
}

.dropdown {
  top: var(--header-height);
}

.main {
  padding-top: var(--header-height);
}

.sidebar {
  height: calc(100vh - var(--header-height));
}

--header-height が 3 箇所で参照されています。ヘッダーの高さが変わったとき、:root の 1 箇所を直せばすべてに反映されます。

カスタムプロパティはカスケードに従うので、特定のスコープで値を上書きすることもできます。

:root {
  --color-primary: #1a73e8;
}

.dark-theme {
  --color-primary: #8ab4f8;
}

.dark-theme の中では --color-primary が別の値に変わり、それ以外では元の値のままです。テーマ切り替えやレスポンシブでの値の変化をカスケードだけで表現できるのは、Sass 変数にはない特徴です。

Sass 変数が必要な場面

Sass 変数でなければならないのは、Sass の言語機能に値を渡す場合です。

/* メディアクエリの条件 */
$bp-md: 768px;

@media (min-width: $bp-md) {
  .sidebar { display: block; }
}

CSS カスタムプロパティはメディアクエリの条件式の中では使えません。メディアクエリはカスケードの外側で評価されるため、カスケードに依存するカスタムプロパティを参照できないという仕様上の制限があります。

@media (min-width: var(–bp-md)) と書いても動作しない。

他にも、ミックスインの引数、@each@if での分岐、Sass の map-get() など、Sass の機能に値を渡す場面では Sass 変数が必要です。

/* ミックスインの引数 */
@mixin mq-up($bp) {
  @media (min-width: $bp) { @content; }
}

/* マップとループ */
$colors: (
  primary: #1a73e8,
  danger: #dc3545,
  success: #28a745,
);

@each $name, $value in $colors {
  .text-#{$name} {
    color: $value;
  }
}

これらはコンパイル時に展開される仕組みなので、ランタイムで解決されるカスタムプロパティでは代替できません。

両方を組み合わせるパターン

Sass 変数の利便性とカスタムプロパティのランタイムの柔軟性を両取りするために、Sass 変数で定義した値をカスタムプロパティに流し込む方法があります。

/* _tokens.scss */
$spacing-sm: 8px;
$spacing-md: 16px;
$spacing-lg: 24px;

:root {
  --spacing-sm: #{$spacing-sm};
  --spacing-md: #{$spacing-md};
  --spacing-lg: #{$spacing-lg};
}

Sass のミックスインやループの中では $spacing-md を使い、コンポーネントの CSS では var(--spacing-md) を使うという棲み分けです。

/* ミックスイン内では Sass 変数 */
@mixin card-padding {
  padding: $spacing-md;
}

/* コンポーネントではカスタムプロパティ */
.card {
  padding: var(--spacing-md);
}

値の定義は 1 箇所(_tokens.scss)に集約されるので、変更時の影響範囲も明確です。

使い分けの整理

手段使う場面
calc()ランタイム単位との計算、1 箇所限り
var()複数箇所で共有、テーマ切り替え
Sass 変数メディアクエリ、ミックスイン、ループ

判断の順番としては、まず Sass の言語機能に渡す必要があるかどうかを見ます。あるなら Sass 変数です。次に、同じ値を複数箇所で参照するか、条件で切り替えるかを見ます。あるならカスタムプロパティです。どちらでもなく、その場限りのランタイム計算なら calc() だけで済みます。

3 つは排他的な選択肢ではなく、calc(100vh - var(--header-height)) のように組み合わせて使うことも多いです。それぞれの得意な場面を理解しておけば、過不足なく使い分けられます。