Skip to content

BEM 命名規則:區塊、元素與修飾子

BEM 命名規則:區塊、元素與修飾子

BEM 是 Block Element Modifier 的縮寫,用固定的三段式命名把元件的從屬關係寫進類別名裡,讓每條規則都只用一個 類別選取器 就選得到。

三個部分

css
.card { }                 /* Block:獨立、可搬動的區塊 */
.card__title { }          /* Element:只屬於這個區塊的一部分 */
.card__title--muted { }   /* Modifier:同一個東西的變體 */
部分分隔符號判斷標準
Block 區塊拿掉周圍的一切還站得住,就是一個區塊。
Element 元素__離開這個區塊就沒有意義的組成部分。
Modifier 修飾子--同一個東西的外觀或行為變體。

對應到 HTML:

html
<article class="card">
  <h2 class="card__title card__title--muted">標題</h2>
  <p class="card__text">內文</p>
</article>

選取器永遠只有一層

這是 BEM 真正的價值所在,也是它和「隨便加前綴」的差別。

css
/* ❌ 後代選取器:權重疊高,元件也搬不走 */
.card .card__title { font-size: 1.25rem; }

/* ✅ 單一類別:權重一致,蓋樣式不必比長度 */
.card__title { font-size: 1.25rem; }

從屬關係既然已經寫在名字裡,選取器就沒有必要再往下鑽一次。好處有兩個:

  • 權重 保持扁平。 每條規則都是同一個層級,誰勝出只看書寫順序,不會走到得靠 !important 收尾的地步。
  • 元件可以搬家。 .card__title 不在乎自己被放在側欄還是主內容區,樣式都成立。

注意

.card .card__title 這種寫法在前處理器裡特別容易寫出來,因為巢狀結構會自動幫你產生後代選取器。用 Sass 寫 BEM 時要靠 & 串接字串而不是巢狀,做法見 Sass 與 BEM

元素只有一層

HTML 巢狀再深,類別名也不會跟著變深:

html
<article class="card">
  <div class="card__author">
    <!-- ✅ 不是 card__author__avatar -->
    <img class="card__author-avatar" src="avatar.jpg" alt="">
  </div>
</article>

名字要回答的問題是「它屬於哪個區塊」,而不是「它在 DOM 的第幾層」。多寫一層 __ 只會讓名字變長,而且 DOM 結構一調整,名字就對不上了。

修飾子要和基底並存

html
<!-- ❌ 只掛修飾子,基底樣式全沒了 -->
<button class="btn--primary">送出</button>

<!-- ✅ 兩個一起寫 -->
<button class="btn btn--primary">送出</button>
css
.btn { padding: 8px 16px; border-radius: 4px; }
.btn--primary { background: #2965f1; color: #fff; }

修飾子的職責是 只描述差異,共同的部分留在基底類別上。這也是它和 OOCSS 想法相通的地方:一個類別只管一件事。

什麼時候該拆成新的區塊

card__button 還是 btn?用這兩個問題判斷:

  1. 它會出現在別的地方嗎? 會,就是獨立區塊。
  2. 它自己也開始長出元素或修飾子了嗎? 是,就是獨立區塊。
html
<!-- 按鈕在別處也用得到,讓它當自己的區塊 -->
<article class="card">
  <div class="card__action">
    <button class="btn btn--primary">立即購買</button>
  </div>
</article>

區塊之間要調整位置時,把外距寫在 外層區塊的元素 上,而不是塞進被放進來的那個區塊裡:

css
/* ✅ 位置由容器決定,btn 本身保持乾淨 */
.card__action { margin-top: 16px; }

狀態的處理

由 JavaScript 在執行期切換的狀態,寫成 is- 前綴比修飾子好認:

css
.card.is-loading { opacity: .5; pointer-events: none; }
js
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- 前綴比較好認。專案內統一一種即可,重點是別兩種混著用。

延伸閱讀