Vue3 – 樣式與 CSS 管理
主題:CSS Variables(自訂屬性)
簡介
在 Vue3 專案中,CSS Variables(又稱自訂屬性)是管理顏色、字體大小、間距等 UI 常數的利器。相較於傳統的 SCSS 變數只能在編譯階段使用,CSS Variables 是原生瀏覽器支援、在執行階段即可動態改變的屬性,讓我們能夠在單一元件或全域層面即時調整樣式,提升主題切換、暗黑模式、以及多語系 UI 的彈性。
本文將從概念說明、實作範例、常見陷阱與最佳實踐,帶你在 Vue3 中善用 CSS Variables,寫出 可維護、可重用 的樣式程式碼。
核心概念
1. CSS Variables 基礎語法
/* 定義變數,必須在 :root(全域)或任何元素上 */
:root {
--primary-color: #42b983;
--font-base: 16px;
}
/* 使用變數 */
.button {
background-color: var(--primary-color);
font-size: var(--font-base);
}
- 以
--開頭的名稱是 自訂屬性。 var(--name, fallback)可帶第二個參數作為備援值。
2. 在 Vue3 中使用 CSS Variables
2.1 直接在 <style> 中定義
<template>
<button class="btn">按鈕</button>
</template>
<style scoped>
:root {
--btn-bg: #409eff;
--btn-color: #fff;
}
.btn {
background: var(--btn-bg);
color: var(--btn-color);
padding: 0.5rem 1rem;
}
</style>
注意:
scoped仍會把變數提升到對應的元素上,若想全域變數,請改用:root在非scoped樣式或src/assets/css/variables.css中統一管理。
2.2 透過 Vue 組件的 style 屬性動態改變
<template>
<div :style="themeStyle">
<p>這段文字會跟著主題變色</p>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const theme = ref('light') // light / dark
const themeStyle = computed(() => ({
'--bg-color': theme.value === 'dark' ? '#1e1e1e' : '#fff',
'--text-color': theme.value === 'dark' ? '#eee' : '#333'
}))
</script>
<style scoped>
div {
background: var(--bg-color);
color: var(--text-color);
padding: 1rem;
}
</style>
- 使用
:style綁定一個 物件,鍵名即為 CSS Variable 名稱(含--),即可在執行階段切換主題。
2.3 在 setup() 中使用 useCssModule(Vue CLI 5+)
<template>
<header :class="$style.header">Vue3 樣式範例</header>
</template>
<script setup>
import { onMounted } from 'vue'
import styles from './Header.module.css'
onMounted(() => {
// 直接操作 CSS 變數
document.documentElement.style.setProperty('--header-height', '80px')
})
</script>
<style module>
.header {
height: var(--header-height, 60px);
background: #35495e;
color: #fff;
line-height: var(--header-height, 60px);
text-align: center;
}
</style>
module讓 class 名稱自動轉為唯一值,仍可透過document.documentElement.style.setProperty全域設定變數。
3. 結合 SCSS / PostCSS
即使使用 CSS Pre‑processor,也能同時保留原生變數的彈性:
/* variables.scss */
:root {
--primary: #42b983;
--spacing: 1rem;
}
/* 其他 SCSS 檔案 */
.button {
padding: var(--spacing);
background: var(--primary);
}
SCSS 只負責計算、混入等功能,最終仍交給瀏覽器解析 var()。
程式碼範例(3–5 個實用範例)
範例 1:全域主題切換(暗黑 / 明亮)
<template>
<div class="app" :class="{ dark: isDark }">
<button @click="toggleTheme">切換主題</button>
<slot />
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
const isDark = ref(false)
function toggleTheme() {
isDark.value = !isDark.value
}
// 監聽變化,寫入根層變數
watch(isDark, (val) => {
const root = document.documentElement
if (val) {
root.style.setProperty('--bg', '#1e1e1e')
root.style.setProperty('--text', '#e0e0e0')
} else {
root.style.setProperty('--bg', '#fff')
root.style.setProperty('--text', '#333')
}
})
</script>
<style scoped>
.app {
background: var(--bg);
color: var(--text);
min-height: 100vh;
transition: background 0.3s, color 0.3s;
}
button {
margin: 1rem;
}
</style>
重點:只需要在根層改變變數,即可讓所有子元件自動套用新樣式,避免逐一調整 class。
範例 2:動態調整間距(Responsive Gap)
<template>
<div class="grid" :style="{ '--gap': gap + 'px' }">
<div v-for="n in 6" :key="n" class="item">#{{ n }}</div>
</div>
<input type="range" min="8" max="40" v-model="gap" />
</template>
<script setup>
import { ref } from 'vue'
const gap = ref(16)
</script>
<style scoped>
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(80px, 1fr));
gap: var(--gap);
}
.item {
background: #42b983;
color: #fff;
padding: 0.5rem;
text-align: center;
}
</style>
使用 :style 動態設定 --gap,即時改變 Grid 間距,適合 UI 設計師提供「間距調整」的即時預覽功能。
範例 3:多語系字體切換
<template>
<div class="content" :style="{ '--font-family': currentFont }">
<p>這是一段示範文字。</p>
<select v-model="currentFont">
<option value="'Noto Sans TC', sans-serif">中文(Noto Sans TC)</option>
<option value="'Roboto', sans-serif">英文(Roboto)</option>
</select>
</div>
</template>
<script setup>
import { ref } from 'vue'
const currentFont = ref("'Noto Sans TC', sans-serif")
</script>
<style scoped>
.content {
font-family: var(--font-family);
line-height: 1.6;
}
select {
margin-top: 1rem;
}
</style>
透過變數切換 font-family,不需要重新載入樣式檔,適合 多語系或客製化字體 的需求。
範例 4:在組件庫中提供可自訂的顏色變數
// MyButton.vue
<template>
<button class="my-btn"><slot /></button>
</template>
<script>
export default {
name: 'MyButton',
props: {
/** 自訂顏色,如不提供則使用全域變數 */
color: { type: String, default: null }
},
mounted() {
if (this.color) {
this.$el.style.setProperty('--btn-color', this.color)
}
}
}
</script>
<style scoped>
.my-btn {
--btn-color: var(--primary-color, #42b983); /* fallback */
background: var(--btn-color);
color: #fff;
border: none;
padding: 0.5rem 1rem;
cursor: pointer;
}
</style>
使用者在使用 <MyButton color="#ff5722"> 時,只會改變該實例的背景色,而不影響其他按鈕。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方案 |
|---|---|---|
| 變數作用域不明 | 在 scoped 樣式裡定義 :root 仍會被限制在該元件,導致全域無法使用。 |
將全域變數寫在 src/assets/css/variables.css,在 main.js 中 import。 |
| 變數名稱拼寫錯誤 | var(--primary-colr) 會回傳 unset,且不會拋錯。 |
使用 IDE 的 CSS 變數自動補全,或在 :root 中寫註解列舉所有變數。 |
| fallback 被忽略 | 若變數在父層未定義,var(--x) 會回傳 invalid,不會自動使用 CSS 預設值。 |
明確寫 var(--x, #default),或在根層提供基本值。 |
| 過度依賴 JavaScript 更改 | 每次 setProperty 會觸發 repaint,若頻繁更新會影響效能。 |
限制變更頻率(如 debounce),或改用 CSS @media、prefers-color-scheme。 |
| 瀏覽器相容性 | IE 不支援 CSS Variables。 | 若需支援舊版瀏覽器,可使用 postcss-custom-properties 轉譯。 |
最佳實踐
- 集中管理:所有全域變數放在
variables.css,以:root為起點。 - 語意化命名:如
--color-primary,--spacing-md,避免硬編碼顏色。 - 提供 fallback:
var(--color-primary, #42b983),保證在變數缺失時仍有預設。 - 結合
prefers-color-scheme:自動偵測系統暗色模式,減少 JavaScript 程式碼。 - 使用
@layer(CSS Layers):讓變數覆寫有明確層級,避免意外被 later CSS 覆寫。
實際應用場景
- 主題系統:企業內部平台需要支援多套品牌色,只要在
:root中切換--brand-primary即可完成。 - 動態尺寸調整:設計師在 Figma 上調整間距後,前端只需改變
--spacing-lg,所有相關元件自動更新。 - 多語系或客製字體:根據使用者語系切換
--font-family,避免重新載入樣式表。 - 組件庫:提供可透過
style或prop覆寫的變數,讓使用者在不改寫原始碼的情況下客製化 UI。 - 動畫與過渡:使用
var(--duration)控制所有過渡時間,讓全站動畫節奏統一,未來調整只改一個變數。
總結
CSS Variables 在 Vue3 中不只是「顏色的別名」,更是 跨元件、跨主題、跨執行階段 的樣式溝通橋樑。透過 :root 集中管理、setProperty 動態更新、以及在 <style scoped> 中的語意化使用,我們可以:
- 快速實作暗黑/亮色主題,不需重寫大量 CSS。
- 即時調整間距、字體、動畫時間,提升 UI 設計的彈性。
- 在組件庫中提供可自訂的樣式 API,降低使用門檻。
只要遵守 命名規範、提供 fallback、避免過度頻繁變更,CSS Variables 就能成為 Vue3 前端開發者手中最實用的樣式工具。快把它加入你的專案,讓樣式管理更簡潔、維護更容易吧!