Sass 工具類別:用 map 產生,與 Tailwind 的取捨
工具類別(utility class)是一個類別只做一件事:.mt-4 就是 margin-top: 24px。它把樣式的決定權從 .scss 移到 HTML。
<div class="flex items-center gap-2 p-4">為什麼有人要這樣寫
| 傳統元件寫法的問題 | 工具類別的回應 |
|---|---|
| 每個小調整都要發明一個類別名 | 不用命名 |
| 樣式表只會長大,沒人敢刪 | 類別是共用的,總量固定 |
| 改樣式要在兩個檔案間跳 | 就在 HTML 上 |
| 不知道某個類別還有誰在用 | 不需要知道 |
代價是 HTML 的 class 會變長,而且樣式散在標記裡。這是取捨,不是對錯。
一張表驅動
@use "sass:map";
$utilities: (
"margin": (
prefix: "m",
property: margin,
values: (0: 0, 1: 4px, 2: 8px, 3: 16px, 4: 24px),
),
"padding": (
prefix: "p",
property: padding,
values: (0: 0, 1: 4px, 2: 8px, 3: 16px, 4: 24px),
),
"display": (
prefix: "d",
property: display,
values: (none: none, block: block, flex: flex, grid: grid),
),
);
@each $name, $config in $utilities {
$prefix: map.get($config, prefix);
$property: map.get($config, property);
@each $key, $value in map.get($config, values) {
.#{$prefix}-#{$key} {
#{$property}: $value;
}
}
}.m-0 { margin: 0; }
.m-1 { margin: 4px; }
/* ... */
.d-flex { display: flex; }新增一整類工具只要在 $utilities 加一筆,不必寫新的迴圈。
加上斷點變體
@each $bp in (sm, md, lg) {
@include respond-to($bp) {
@each $name, $config in $utilities {
$prefix: map.get($config, prefix);
$property: map.get($config, property);
@each $key, $value in map.get($config, values) {
.#{$prefix}-#{$bp}-#{$key} {
#{$property}: $value;
}
}
}
}
}<div class="d-block d-md-flex">產出會爆炸
算一下:
3 類 × 5 個值 × 4 個斷點 = 60 條看起來還好。但實務上會有間距(含四個方向)、色彩、字級、圓角、陰影、對齊……
20 類 × 8 個值 × 4 個斷點 × 3 個狀態 = 1920 條而專案裡真正用到的可能不到兩百條。
這是自己刻工具類別最大的問題
Sass 的迴圈 不知道你的 HTML 用了哪些類別,只能全部輸出。
三個控制策略
一、只產生真的會用到的。 間距刻度砍到六階、方向只留 x 與 y、斷點只留兩個。
二、讓變體可以關掉。
$enable-responsive-utilities: true !default;
$enable-hover-utilities: false !default;
@if $enable-responsive-utilities {
// 產生斷點變體
}用 @use ... with 讓每個專案自己決定。
三、接上能移除未使用樣式的工具。 PurgeCSS 這類工具掃描 HTML 與模板,把沒出現的類別刪掉。加在 Sass 編譯之後跑,見 效能與產出品質。
那不如直接用現成的
到了要自己實作「按需產生」與「狀態變體語法」的時候,就該停下來想想:
| 自己用 Sass 產生 | Tailwind / UnoCSS | |
|---|---|---|
| 按需產生 | ❌ 全部輸出 | ✅ 掃描原始碼 |
| 任意值語法 | ❌ | ✅ 支援中括號寫法 |
| 狀態前綴 | 要自己實作 | ✅ 內建 |
| 設定方式 | Sass map | 設定檔 |
| 額外相依 | 無 | 需要工具鏈 |
這個站就是用 UnoCSS
本專案用 UnoCSS 以 Vite 外掛的形式整合,見 前端開發工具總覽。它掃描原始碼、只產生用到的類別,不必自己處理數量問題。
混用的界線
工具類別與 BEM 可以並存,前提是界線清楚:
<article class="card mt-4">
<h3 class="card__title">標題</h3>
</article>| 用途 | 用什麼 |
|---|---|
| 元件本體的樣式 | BEM 類別 |
| 一次性的間距、對齊 | 工具類別 |
| 需要在多處重複的元件 | BEM,不要用一長串工具類別 |
不要用工具類別組出元件
class="p-4 rounded shadow border bg-white" 出現在十個地方時,它就該是一個 .card。
優先權的處理
工具類別要能蓋過元件樣式:
// ✅ 讓元件選取器維持單一類別,並把工具類別放在最後載入
@use "components";
@use "utilities"; // 最後少用 !important
除非元件那邊真的無法降低優先權。靠順序取勝比較乾淨,見 CSS 優先權。
常見問題
自己產生工具類別和用 Tailwind 差在哪?
最大的差別在按需產生。Tailwind 與 UnoCSS 會掃描原始碼,只輸出真正用到的類別;自己用 Sass 迴圈產生的則是全部輸出,不管有沒有用到。規模一大,體積差距會很明顯。
工具類別會不會讓 HTML 很醜?
類別確實會變長,但換來的是不必為每個小調整發明類別名,也不會累積沒人敢刪的樣式。取捨在於團隊比較在意 HTML 的乾淨,還是 CSS 的可控。
工具類別需要加 !important 嗎?
如果元件樣式的優先權比它高,就會需要。比較好的解法是讓元件選取器維持單一類別,並把工具類別放在樣式表的最後面,靠順序取勝而不是靠 !important。
什麼時候該直接用現成的框架?
當你發現自己在補變體語法、寫按需產生的邏輯、處理狀態前綴時。那些問題原子化框架都解過了,繼續自己刻只是在重寫一個比較差的版本。