Skip to content

CSS 自訂屬性:CSS 變數的宣告、取值與主題切換

CSS 自訂屬性:CSS 變數的宣告與取值

以兩個連字號開頭的自訂屬性(Custom Properties)就是一般所說的 CSS 變數。同一個顏色、間距或圓角在專案裡往往出現幾十次,把它抽成一個名字之後,改一個地方就能全站生效。它與前處理器的變數最大的不同是:它活在層疊裡:會被子層 繼承、可以分區覆蓋,也能在執行時用 JavaScript 修改。

宣告與取值

宣告時用兩個連字號開頭,取值時用 var() 包起來:

css
:root {
  --brand: #2965f1;
  --radius: 8px;
}

.btn {
  background: var(--brand);
  border-radius: var(--radius);
}
寫法說明
--名稱: 值;宣告,可以寫在任何選取器上。
var(--名稱)取值,只能用在屬性值的位置。
var(--名稱, 預設值)取值並指定找不到時的替代值。

名稱有大小寫之分

--Brand--brand 是兩個不同的變數。命名慣例上多用全小寫加連字號。

預設值

var() 的第二個參數是找不到該變數時的替代值,適合寫元件時留一個安全的底:

css
.card {
  /* 外層有給就用外層的,沒給就用白色 */
  background: var(--card-bg, #fff);
}

預設值本身也可以是另一個 var(),形成多層備援:

css
.card {
  background: var(--card-bg, var(--surface, #fff));
}

它會繼承,所以能分區覆蓋

自訂屬性是 可繼承的屬性,這一點決定了它的用法:宣告在 :root 上,整份文件都拿得到;宣告在某個區塊上,就只有那個區塊與它的子層拿得到。

css
:root {
  --text: #333;
}

/* 這一區塊裡的 --text 換成淺色,子層全部跟著變 */
.dark-panel {
  --text: #f5f5f5;
}

p {
  color: var(--text);
}

這不是「作用域」,是繼承

自訂屬性沒有真正的作用域概念,它單純遵循 繼承優先權 的規則。所以在子層重新宣告同名變數,就等於覆蓋掉繼承來的值。

用 JavaScript 讀寫

這是自訂屬性最實用的一點:值可以在執行時改,不必事先窮舉所有可能的樣式:

js
const root = document.documentElement;

// 讀取
const brand = getComputedStyle(root).getPropertyValue('--brand').trim();

// 寫入
root.style.setProperty('--brand', '#e93e30');

因此像「跟著滑鼠移動的光暈」、「依後端回傳色碼換主題色」這類需求,都可以只改一個變數,樣式邏輯留在 CSS 裡。

實作:主題切換

把顏色全部抽成變數,再依 屬性選取器 換一組值,就是最常見的深色模式做法:

css
:root {
  --bg: #ffffff;
  --text: #333333;
}

[data-theme='dark'] {
  --bg: #1b1b1f;
  --text: #f5f5f5;
}

body {
  background: var(--bg);
  color: var(--text);
}
js
document.documentElement.dataset.theme = 'dark';

只要所有樣式都透過變數取值,切換主題就只是換掉那一組宣告,不必寫兩套 CSS。色彩管理的實務寫法另見 色彩表示法

與 Sass 變數的差別

比較項目自訂屬性Sass 變數
生效時機瀏覽器執行時編譯時就被替換掉
產出的 CSS變數本身還存在只留下最終的值
會不會繼承會,可分區覆蓋不會,只看程式碼的作用範圍
能否用 JavaScript 改可以不行
能否用在媒體查詢條件不行可以

簡單說:值會隨狀態改變的用自訂屬性,只是為了少打字的用前處理器變數。 兩者也常一起使用,斷點與計算交給 Sass,主題與狀態交給自訂屬性,分工見 搭配 CSS 自訂屬性

常見問題

自訂屬性和 Sass 變數差在哪?

Sass 變數在編譯時就被替換成固定值,產出的 CSS 裡不存在;自訂屬性活在瀏覽器裡,會被子層 繼承、能分區覆蓋,也能在執行時用 JavaScript 修改。需要隨狀態改變的值就該用自訂屬性。

為什麼一定要寫在 :root?

不一定。自訂屬性會被子層繼承,寫在 :root 只是讓整份文件都拿得到。若某個值只在特定區塊裡用,寫在那個區塊的選取器上更好,也能在子層重新宣告來覆蓋。

var() 可以給預設值嗎?

可以,寫成 var(--brand, #2965f1),逗號後面就是找不到該變數時的替代值。預設值也可以是另一個 var(),形成多層備援。

自訂屬性能用在媒體查詢的條件裡嗎?

不行。自訂屬性只能當屬性值使用,不能拿來組成媒體查詢的條件、選取器或屬性名稱。斷點的數值仍需要寫死,或交給前處理器的變數。

延伸閱讀