Skip to content

Sass 與 BEM:用 & 串接類別名的取捨

Sass 與 BEM 命名慣例的搭配

BEM(Block、Element、Modifier)把元件結構寫進 類別名稱,而不是寫進選取器的巢狀層數。搭配 & 之後,原始碼可以保持巢狀的組織感,產出卻是一層層獨立的類別。

三個部分

.card            區塊 Block:獨立的元件
.card__title     元素 Element:區塊的一部分,兩個底線
.card--featured  修飾子 Modifier:變體或狀態,兩個連字號
html
<article class="card card--featured">
  <h3 class="card__title">標題</h3>
  <p class="card__text">內文</p>
</article>

用 & 串接

scss
.card {
  padding: 16px;
  border: 1px solid #eee;

  &__title {
    font-size: 20px;
    font-weight: 700;
  }

  &__text {
    color: #666;
  }

  &--featured {
    border-color: #2965f1;
  }
}
css
.card { padding: 16px; border: 1px solid #eee; }
.card__title { font-size: 20px; font-weight: 700; }
.card__text { color: #666; }
.card--featured { border-color: #2965f1; }

每一條選取器都只有一層。 優先權 全部是 0,1,0,誰要覆蓋誰只看順序,不會出現「得再加一個類別才蓋得過去」的窘境。

為什麼這比巢狀好

對照 巢狀 的寫法:

scss
.card {
  .title { }         // → .card .title,優先權 0,2,0
}
BEM + &巢狀後代
選取器長度一層跟著巢狀增加
優先權固定 0,1,0逐層升高
依賴 DOM 結構不依賴少一層 <div> 就失效
類別名稱較長較短

BEM 用「比較長的類別名」換掉「比較高的優先權與結構依賴」。在會長大的專案裡,這筆交易划算得多。

唯一的代價:搜尋不到

&__title 在原始碼裡不是字串 .card__title。從瀏覽器看到一個類別名,回頭全域搜尋會找不到。

解法是讓檔名對應區塊名

components/
├── _card.scss     ← .card 相關的全部在這
├── _button.scss   ← .btn
└── _nav.scss      ← .nav

看到 card__title 就去開 _card.scss。這個約定成立之後,搜尋不到就不是問題了。架構見 7-1 檔案架構

不接受這個代價的話,就把名稱寫完整:

scss
.card { padding: 16px; }
.card__title { font-size: 20px; }

搜尋得到,但 .card 相關的樣式就不再集中在一個區塊裡。兩種都可以,團隊統一就好。

修飾子與狀態

scss
.btn {
  padding: 8px 16px;

  &--primary { background: #2965f1; }
  &--large   { padding: 12px 24px; }

  // 狀態用 is- 前綴,通常由 JavaScript 加上
  &.is-loading {
    opacity: 0.6;
    pointer-events: none;
  }
}
html
<button class="btn btn--primary is-loading">送出</button>

修飾子不能單獨使用

class="btn--primary" 沒有帶 btn 的話,基底樣式全部拿不到。修飾子只寫「與基底的差異」。

元素只有一層

scss
// ❌ BEM 沒有這種東西
.card__header__title { }

需要寫到第三段時,代表那個部分應該 獨立成新的區塊

scss
.card {
  &__header { }
}

.card-title {   // 獨立區塊
  &__icon { }
}

名稱裡的層級反映的是元件關係,不是 DOM 有幾層。 <div class="card__title"> 不管被包在幾層裡面,類別名都一樣。

常見的誤用

scss
// ❌ 把 BEM 又寫成巢狀,兩邊的缺點都拿到了
.card {
  &__header {
    &__title { }   // → .card__header__title
  }
}

// ❌ 元素裡再包元素的巢狀後代
.card__header {
  .card__title { }  // → .card__header .card__title,優先權 0,2,0
}

BEM 的整個重點就是 讓選取器維持一層,寫成上面這樣等於白做。

與工具類別並用

html
<article class="card mt-4">

常見的分工是:元件本體用 BEM、一次性的間距與對齊用 工具類別

界線要畫清楚,否則兩套系統會互相覆蓋,最後沒人知道某個 margin 到底來自哪裡。

常見問題

用 & 串接之後全域搜尋不到類別名,怎麼辦?

這是這個寫法唯一的代價。實務上的解法是讓檔名與區塊名一致,找 card__title 就去開 _card.scss。這個約定成立的話,搜尋不到反而不是問題。

元素可以有第二層嗎?

不行,BEM 的元素只有一層。需要寫成三段式的話,代表那個元素應該獨立成新的區塊。名稱裡的層級反映的是元件關係,不是 DOM 的巢狀深度。

修飾子要不要跟區塊寫在一起?

HTML 上兩個類別要並存,區塊類別提供基底、修飾子只寫差異。單獨使用修飾子而不帶區塊類別是常見的誤用,那樣就失去共用基底樣式的意義。

BEM 和工具類別可以並用嗎?

可以,常見的分工是元件本體用 BEM、間距與對齊這類一次性調整用工具類別。但要有明確界線,否則兩套系統會互相覆蓋,最後誰也說不清樣式從哪來。

延伸閱讀