Skip to content

Sass 工具類別:用 map 產生,與 Tailwind 的取捨

用 Sass map 產生工具類別

工具類別(utility class)是一個類別只做一件事:.mt-4 就是 margin-top: 24px。它把樣式的決定權從 .scss 移到 HTML。

html
<div class="flex items-center gap-2 p-4">

為什麼有人要這樣寫

傳統元件寫法的問題工具類別的回應
每個小調整都要發明一個類別名不用命名
樣式表只會長大,沒人敢刪類別是共用的,總量固定
改樣式要在兩個檔案間跳就在 HTML 上
不知道某個類別還有誰在用不需要知道

代價是 HTML 的 class 會變長,而且樣式散在標記裡。這是取捨,不是對錯。

一張表驅動

scss
@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;
    }
  }
}
css
.m-0 { margin: 0; }
.m-1 { margin: 4px; }
/* ... */
.d-flex { display: flex; }

新增一整類工具只要在 $utilities 加一筆,不必寫新的迴圈。

加上斷點變體

scss
@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;
        }
      }
    }
  }
}
html
<div class="d-block d-md-flex">

產出會爆炸

算一下:

3 類 × 5 個值 × 4 個斷點 = 60 條

看起來還好。但實務上會有間距(含四個方向)、色彩、字級、圓角、陰影、對齊……

20 類 × 8 個值 × 4 個斷點 × 3 個狀態 = 1920 條

而專案裡真正用到的可能不到兩百條。

這是自己刻工具類別最大的問題

Sass 的迴圈 不知道你的 HTML 用了哪些類別,只能全部輸出。

三個控制策略

一、只產生真的會用到的。 間距刻度砍到六階、方向只留 xy、斷點只留兩個。

二、讓變體可以關掉。

scss
$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 可以並存,前提是界線清楚:

html
<article class="card mt-4">
  <h3 class="card__title">標題</h3>
</article>
用途用什麼
元件本體的樣式BEM 類別
一次性的間距、對齊工具類別
需要在多處重複的元件BEM,不要用一長串工具類別

不要用工具類別組出元件

class="p-4 rounded shadow border bg-white" 出現在十個地方時,它就該是一個 .card

優先權的處理

工具類別要能蓋過元件樣式:

scss
// ✅ 讓元件選取器維持單一類別,並把工具類別放在最後載入
@use "components";
@use "utilities";   // 最後

少用 !important

除非元件那邊真的無法降低優先權。靠順序取勝比較乾淨,見 CSS 優先權

常見問題

自己產生工具類別和用 Tailwind 差在哪?

最大的差別在按需產生。Tailwind 與 UnoCSS 會掃描原始碼,只輸出真正用到的類別;自己用 Sass 迴圈產生的則是全部輸出,不管有沒有用到。規模一大,體積差距會很明顯。

工具類別會不會讓 HTML 很醜?

類別確實會變長,但換來的是不必為每個小調整發明類別名,也不會累積沒人敢刪的樣式。取捨在於團隊比較在意 HTML 的乾淨,還是 CSS 的可控。

工具類別需要加 !important 嗎?

如果元件樣式的優先權比它高,就會需要。比較好的解法是讓元件選取器維持單一類別,並把工具類別放在樣式表的最後面,靠順序取勝而不是靠 !important

什麼時候該直接用現成的框架?

當你發現自己在補變體語法、寫按需產生的邏輯、處理狀態前綴時。那些問題原子化框架都解過了,繼續自己刻只是在重寫一個比較差的版本。

延伸閱讀