Sass 變數與 CSS 自訂屬性:什麼交給誰
需要。兩者活在 不同的時間點,能做的事根本不同。
核心差異
scss
$brand: #2965f1; // 編譯期,產出裡不存在
:root {
--brand: #2965f1; // 執行期,產出裡還在
}| Sass 變數 | CSS 自訂屬性 | |
|---|---|---|
| 生效時機 | 編譯期 | 瀏覽器執行時 |
| 產出的 CSS | 只剩最終的值 | 變數本身還在 |
| 會不會繼承 | 不會 | 會,可分區覆蓋 |
| JavaScript 能改嗎 | 不行 | 可以 |
| 能用在媒體查詢條件 | 可以 | 不行 |
| 能參與迴圈與運算 | 可以 | 不行 |
| 能傳給 Sass 函式 | 可以 | 不行 |
粗體的部分就是各自無法被取代的地方。
決策表
| 這個值… | 用 |
|---|---|
| 要能在瀏覽器裡切換(主題、深色模式) | 自訂屬性 |
| 要用 JavaScript 改(拖曳、動態尺寸) | 自訂屬性 |
| 元件內部要能被外部覆寫 | 自訂屬性 |
| 要當媒體查詢的斷點 | Sass |
| 要拿去 迴圈 產生類別 | Sass |
要傳給 color.scale() 算色階 | Sass |
| 從頭到尾都不會變的常數 | Sass |
一句話:編譯期算得完的交給 Sass,執行期才知道的交給自訂屬性。
寫進自訂屬性要插值
這是最常踩的坑:
scss
$brand: #2965f1;
:root {
--a: $brand; // ❌ 輸出字面上的 $brand
--b: #{$brand}; // ✅ 輸出 #2965f1
}自訂屬性的值對 Sass 來說是「原樣輸出」的內容,不會做替換。必須用 插值。
兩層變數的組織
實務上的標準做法是 Sass 當資料來源,自訂屬性當執行期介面:
scss
// abstracts/_tokens.scss:Sass 這一層
$spacing: (1: 4px, 2: 8px, 3: 16px) !default;
$themes: (
light: (bg: #fff, text: #212529),
dark: (bg: #1a1a1a, text: #f1f3f5),
) !default;scss
// base/_variables.scss:產生 CSS 這一層
@use "sass:map";
@use "../abstracts/tokens";
:root {
@each $key, $value in map.get(tokens.$themes, light) {
--#{$key}: #{$value};
}
}
:root[data-theme="dark"] {
@each $key, $value in map.get(tokens.$themes, dark) {
--#{$key}: #{$value};
}
}scss
// components/_card.scss:使用
@use "../abstracts" as a;
.card {
padding: a.space(3); // Sass:固定值,用不著活到執行期
background: var(--bg); // 自訂屬性:跟著主題變
color: var(--text);
}完整的主題實作見 多主題切換。
自訂屬性不能餵給 Sass 函式
scss
.a {
background: color.scale(var(--brand), $lightness: -20%); // ❌ Error
}Sass 在編譯期看到的只是一段 var(--brand) 文字,它不知道那是什麼顏色。
需要在執行期調色,只能用 CSS 原生的相對色彩語法:
css
.btn:hover {
background: hsl(from var(--brand) h s calc(l - 10%));
}或者,事先用 Sass 把每個色階都算成一個自訂屬性:
scss
:root {
--brand: #{$brand};
--brand-hover: #{color.scale($brand, $lightness: -12%)};
}元件的可設定介面
自訂屬性會 繼承,所以能當成元件的「外部參數」:
scss
.card {
--card-padding: #{a.space(3)};
--card-radius: 8px;
padding: var(--card-padding);
border-radius: var(--card-radius);
}html
<div class="card" style="--card-padding: 32px">或在某個區塊整批改:
scss
.sidebar .card {
--card-padding: 8px;
}這比多定義一個 .card--compact 修飾子彈性得多。
別把所有東西都變成自訂屬性
scss
// ⚠️ 過頭了
:root {
--z-modal: 1000;
--transition-fast: 0.15s;
--border-width: 1px;
}這些值從來不會在執行時改變。放成自訂屬性只是讓:
- 產出的 CSS 多一堆用不到的宣告。
- 開發者工具裡看到的都是
var(--x)而不是實際值。 - 沒辦法拿它們做 Sass 運算。
只有真的會變的才需要活到執行期。
常見問題
有了 CSS 自訂屬性,還需要 Sass 變數嗎?
需要。自訂屬性不能用在媒體查詢的條件裡,也不能參與編譯期的運算與迴圈。斷點、間距刻度、要拿去產生類別的資料,這些仍然只能用 Sass 變數。
為什麼自訂屬性裡的 Sass 變數沒被替換?
因為自訂屬性的值被 Sass 視為原樣輸出的內容。要把 Sass 的值寫進去必須用插值包起來,這是兩者搭配時最常踩的坑。
自訂屬性可以放進 Sass 的色彩函式嗎?
不行。Sass 在編譯期看到的只是一段 var() 文字,不是色彩值,傳給色彩函式會報錯。需要在執行時調色只能用 CSS 原生的相對色彩語法。
所有變數都改成自訂屬性可以嗎?
不建議。只有真的會隨狀態改變的值才需要活到執行期,其餘的用 Sass 變數在編譯期解決掉,產出比較單純,也不會讓開發者工具裡滿是變數參照。