BEM 命名規則:區塊、元素與修飾子
BEM 是 Block Element Modifier 的縮寫,用固定的三段式命名把元件的從屬關係寫進類別名裡,讓每條規則都只用一個 類別選取器 就選得到。
三個部分
.card { } /* Block:獨立、可搬動的區塊 */
.card__title { } /* Element:只屬於這個區塊的一部分 */
.card__title--muted { } /* Modifier:同一個東西的變體 */| 部分 | 分隔符號 | 判斷標準 |
|---|---|---|
| Block 區塊 | 無 | 拿掉周圍的一切還站得住,就是一個區塊。 |
| Element 元素 | __ | 離開這個區塊就沒有意義的組成部分。 |
| Modifier 修飾子 | -- | 同一個東西的外觀或行為變體。 |
對應到 HTML:
<article class="card">
<h2 class="card__title card__title--muted">標題</h2>
<p class="card__text">內文</p>
</article>選取器永遠只有一層
這是 BEM 真正的價值所在,也是它和「隨便加前綴」的差別。
/* ❌ 後代選取器:權重疊高,元件也搬不走 */
.card .card__title { font-size: 1.25rem; }
/* ✅ 單一類別:權重一致,蓋樣式不必比長度 */
.card__title { font-size: 1.25rem; }從屬關係既然已經寫在名字裡,選取器就沒有必要再往下鑽一次。好處有兩個:
- 權重 保持扁平。 每條規則都是同一個層級,誰勝出只看書寫順序,不會走到得靠
!important收尾的地步。 - 元件可以搬家。
.card__title不在乎自己被放在側欄還是主內容區,樣式都成立。
注意
.card .card__title 這種寫法在前處理器裡特別容易寫出來,因為巢狀結構會自動幫你產生後代選取器。用 Sass 寫 BEM 時要靠 & 串接字串而不是巢狀,做法見 Sass 與 BEM。
元素只有一層
HTML 巢狀再深,類別名也不會跟著變深:
<article class="card">
<div class="card__author">
<!-- ✅ 不是 card__author__avatar -->
<img class="card__author-avatar" src="avatar.jpg" alt="">
</div>
</article>名字要回答的問題是「它屬於哪個區塊」,而不是「它在 DOM 的第幾層」。多寫一層 __ 只會讓名字變長,而且 DOM 結構一調整,名字就對不上了。
修飾子要和基底並存
<!-- ❌ 只掛修飾子,基底樣式全沒了 -->
<button class="btn--primary">送出</button>
<!-- ✅ 兩個一起寫 -->
<button class="btn btn--primary">送出</button>.btn { padding: 8px 16px; border-radius: 4px; }
.btn--primary { background: #2965f1; color: #fff; }修飾子的職責是 只描述差異,共同的部分留在基底類別上。這也是它和 OOCSS 想法相通的地方:一個類別只管一件事。
什麼時候該拆成新的區塊
card__button 還是 btn?用這兩個問題判斷:
- 它會出現在別的地方嗎? 會,就是獨立區塊。
- 它自己也開始長出元素或修飾子了嗎? 是,就是獨立區塊。
<!-- 按鈕在別處也用得到,讓它當自己的區塊 -->
<article class="card">
<div class="card__action">
<button class="btn btn--primary">立即購買</button>
</div>
</article>區塊之間要調整位置時,把外距寫在 外層區塊的元素 上,而不是塞進被放進來的那個區塊裡:
/* ✅ 位置由容器決定,btn 本身保持乾淨 */
.card__action { margin-top: 16px; }狀態的處理
由 JavaScript 在執行期切換的狀態,寫成 is- 前綴比修飾子好認:
.card.is-loading { opacity: .5; pointer-events: none; }card.classList.toggle("is-loading");固定的變體(主要/次要、大/小)用修飾子,臨時的狀態(載入中、已展開、有錯誤)用 is-。這個前綴的來源是 SMACSS 的 State 分類,和 BEM 並用很常見。
常見的誤用
| 寫法 | 問題 |
|---|---|
.card__title__link | 元素只有一層,改成 .card__title-link。 |
.btn--primary 單獨使用 | 少了基底樣式,要和 .btn 並存。 |
.card--dark .card__title | 變體的內部樣式仍該是元素的修飾子 .card__title--dark。 |
.red-card | 描述外觀,改版就名實不符,改成描述用途。 |
.main .card | 綁定位置,元件搬走就失效。 |
它的代價
BEM 不是沒有成本,選它之前先知道要付什麼:
- 名字長。
.notification__close-button--disabled這種長度是常態。 - 不好一次搜尋。 用前處理器的
&串接時,原始碼裡根本沒有完整字串,全域搜尋card__title會找不到。解法見 Sass 與 BEM。 - 要先想清楚邊界。 哪些是區塊、哪些是元素,得在動手前有共識,否則每個人拆的粒度都不一樣。
常見問題
BEM 的雙底線可以接兩層嗎?
不行。元素名只有一層,HTML 巢狀三層也一樣寫成 card__author-avatar,而不是 card__author__avatar。名字反映的是從屬於哪個區塊,不是 DOM 有多深。
修飾子可以單獨寫在元素上嗎?
不行。修飾子只調整差異的部分,共同樣式仍在基底類別上,所以兩個要一起寫。單獨掛修飾子的元素會少掉所有基底樣式。
什麼時候該把元素拆成獨立的區塊?
當它開始出現在別的區塊裡,或是自己也長出了元素與修飾子的時候。卡片裡的按鈕若別處也會用到,它就該是獨立的 btn,而不是 card__button。
狀態要寫成修飾子還是 is- 前綴?
由 CSS 決定的固定變體寫成修飾子,由 JavaScript 在執行期切換的狀態用 is- 前綴比較好認。專案內統一一種即可,重點是別兩種混著用。