當 BEM 元素具有明確一致的用途,且能在父區塊之外自行管理實作時,就應將它提升為區塊。先使用下方的 Mac 本機練習檢驗這條界線,再重新命名類別或移動 CSS。

元素如何具備獨立性?

對於維護手寫 HTML 頁面的人來說,實際問題是:逐漸擴大的區段應繼續留在父項目中,還是成為一個獨立維護的區塊。BEM 將區塊定義為在邏輯和功能上獨立的元件;元素則屬於某個區塊,不能脫離該區塊獨立使用。區塊可以包含其他區塊。重要概念

假設有一個合成頁面,其中包含 workbench 區塊。它的 workbench__preview 包含一個抽象形狀、一個標籤和一段說明文字。起初,這三者都只服務於工作台。之後,同一個預覽也需要放在較窄的 shelf 區域中。第二個放置位置帶來一項具體的抽取工作:判斷這個預覽是否能成為具有自身外觀和內部結構的 shape-tile 區塊。

巢狀深度本身無法決定這件事。BEM 允許 DOM 中的元素彼此巢狀,但名稱保持扁平:workbench__preview、workbench__label 和 workbench__caption。像 workbench__preview__caption 這樣的名稱,錯誤地表達某個元素隸屬於另一個元素。官方快速入門也指出,當複雜元素需要獨立組織內部部分時,可以選擇將其設為服務區塊。快速入門

要區分的是:為了提升可讀性而將標記移至獨立範本,與建立新的 BEM 區塊並不相同。你可以先進行前者,不必立刻承諾進行後者。當你能說清楚抽取出的部分負責什麼、宿主提供什麼,以及它將如何用於另一個位置時,就可以明確劃定區塊界線。

抽取前先檢查相依性

以下決策輔助是根據 BEM 的獨立性原則整理出的編輯方法,並非官方評分制度。一次評估一個候選子樹,而不是試圖一次評估整個頁面。

問題 支持抽取的證據 保留為元素的理由
它是否有明確用途? 「顯示帶有標籤和說明文字的形狀」 它的用途只是「工作台的內層包裝器」
它能否自行管理內部呈現? 邊框、間距和字體排版會一起移動 這些規則與父項目的結構密不可分
宿主能否獨立提供放置方式? 宿主控制可用空間和周圍間距 候選項目需要特定的祖先排版配置
它的變體是否描述自身? 緊湊型圖塊在多個位置都有意義 每個變體都以特定父項目命名
現在分開維護是否有幫助? 已有第二個放置位置,或承擔大量內部責任 抽取會建立一個目前用不到的介面

修改程式碼前,先寫下一句話:「shape-tile 負責自己的形狀、標籤、說明文字和內部間距;宿主負責放置位置和可用寬度。」如果這句話開始納入工作台的欄位排列或同層項目順序,就重新考慮界線應該放在哪裡。

將外部排版與內部外觀分開。BEM 的 CSS 指引指出,外部幾何和定位應由父項目配置,並示範如何在同一個節點上組合獨立區塊類別與父項目元素類別。這種組合稱為混合(mix)。CSS 方法論

例如,class="shape-tile workbench__preview" 可以同時表達兩種身分。讓 shape-tile 定義它的邊框和內部排版,讓 workbench__preview 定義它的放置位置。除非覆寫是合約中刻意設計的一部分,否則避免透過兩種身分指派相同屬性。

在 Mac 上建立本機比較範例

為這項練習建立新資料夾,就能比較抽取後的設計,而不必修改現有頁面。你需要純文字編輯器和桌面瀏覽器;這個範例不需要安裝套件、JavaScript、下載字型或圖片檔案。

在 TextEdit 中選擇 File > New,再選擇 Format > Make Plain Text。貼上以下 HTML,並將檔案儲存為 index.html;如果系統詢問,請選擇 Use .html。Apple 說明了這些步驟,也介紹如何使用 Ignore rich text commands 以原始碼方式重新開啟 HTML。在 Mac 上使用 TextEdit 處理 HTML 文件

建立完整的 HTML 文件

這份文件會在兩個宿主中放置同一個提議元件。所有標籤都是合成內容,抽象形狀則以 CSS 繪製。將以下內容儲存為 index.html。

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Shape tile boundary exercise</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body class="demo">
  <h1>Shape tile boundary exercise</h1>
  <main class="demo__regions">
    <section class="workbench">
      <h2>Workbench A</h2>
      <div class="shape-tile workbench__preview">
        <span class="shape-tile__shape" aria-hidden="true"></span>
        <div class="shape-tile__content">
          <h3 class="shape-tile__label">Form A</h3>
          <p class="shape-tile__caption">Rounded sample, first arrangement.</p>
        </div>
      </div>
    </section>
    <section class="shelf">
      <h2>Shelf B</h2>
      <div class="shape-tile shelf__preview">
        <span class="shape-tile__shape" aria-hidden="true"></span>
        <div class="shape-tile__content">
          <h3 class="shape-tile__label">Form B</h3>
          <p class="shape-tile__caption">Rounded sample, second arrangement.</p>
        </div>
      </div>
    </section>
  </main>
</body>
</html>

加入完整的樣式表

在同一個資料夾中建立另一個名為 styles.css 的純文字檔案。確認實際檔名是 styles.css,而不是 styles.css.txt。貼上以下完整樣式表:

/* Page and host styles */
.demo {
  margin: 0 auto;
  padding: 24px;
  max-width: 960px;
  font-family: system-ui, sans-serif;
  background: #f1f3f5;
  color: #243047;
}
.demo__regions {
  display: flex;
  flex-wrap: wrap;
  gap: 24px;
}
.workbench,
.shelf {
  box-sizing: border-box;
  padding: 16px;
  max-width: 100%;
  border: 1px dashed #6b778a;
}
.workbench { width: 480px; }
.shelf { width: 256px; }
.workbench__preview { margin-top: 24px; }
.shelf__preview { margin-top: 12px; }

/* Independent component styles */
.shape-tile {
  box-sizing: border-box;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  padding: 16px;
  border: 1px solid #718096;
  border-radius: 12px;
  background: #fff;
  color: #243047;
  font: 16px/1.5 system-ui, sans-serif;
}
.shape-tile__shape {
  flex: 0 0 48px;
  height: 48px;
  border-radius: 16px 24px 12px 24px;
  background: #536dbe;
}
.shape-tile__content {
  flex: 1 1 160px;
  min-width: 0;
  overflow-wrap: anywhere;
}
.shape-tile__label {
  margin: 0;
  font-size: 18px;
  line-height: 1.3;
}
.shape-tile__caption { margin: 6px 0 0; }

在 Safari 或其他桌面瀏覽器中開啟 index.html。預期看到的比較是一個較寬的工作台和一個較窄的架子。兩者的圖塊共用元件樣式;可用空間則決定形狀和文字能否並排顯示,或是否需要換行。

這裡的資源合約很重要:rel="stylesheet" 建立外部樣式表資源,而 href="styles.css" 指定此文件所需的檔案。加入 BEM 類別並不會建立這項資源連結。移動練習時,請將兩個檔案放在一起。HTML 標準定義了這種樣式表關係。HTML 標準:連結類型「stylesheet」

如何判斷抽取是否成功?

將這項比較當作診斷練習。以下預期結果是根據提供的程式碼推導而來,供你在瀏覽器中自行檢查,並非已回報的瀏覽器測試結果。

  1. 檢查基準狀態。 兩個圖塊都應有白色背景、藍色圓角形狀、標籤和說明文字。如果所有內容都沒有套用樣式,請先檢查樣式表檔名和連結,再評估元件界線。
  2. 移除一個放置類別。 從第一個圖塊刪除 workbench__preview,儲存並重新載入。它的上方邊界應有所變化,而內部外觀應保持完整。
  3. 移除一個宿主類別。 暫時從包含區段移除 workbench。宿主框架和固定寬度應消失;圖塊則應保留元件樣式。
  4. 修改共用元件。 修改 .shape-tile 中的邊框圓角。兩個實例都應隨之變化,而不必修改任何宿主選擇器。
  5. 測試較長的內容。 將一段說明文字替換為較長的合成句子,並縮窄瀏覽器視窗。檢查文字是否換行,以及所有文字是否仍然可見。

根據職責解讀失敗情況。移除宿主類別後邊框消失,表示元件樣式可能仍設在宿主選擇器中。周圍間距出現非預期變化,則可能是放置規則所致。文字差異可能揭示繼承樣式或全域選擇器的影響。先修正相應的相依關係,再加入其他覆寫。

BEM 命名不會建立由瀏覽器強制執行的樣式隔離。CSS 繼承仍會沿著文件樹運作:當級聯沒有為元素提供值時,繼承屬性會從父項目取得值。這一點由 CSS 級聯與繼承規範定義,MDN 也有說明。CSS 級聯與繼承模組第 5 級,第 7.2 節、繼承

範例刻意在圖塊的根元素設定字型和文字顏色。實際頁面也可以選擇繼承網站的字體排版。請將此記錄為一項相依性。獨立性表示所需環境清楚且可移植;不代表每個元件都必須重現所有瀏覽器預設值。

何時應保留為元素?

當候選項目的意義和實作仍然專屬於父項目時,請保留為元素。只用於工作台的對齊包裝器,即使有許多子項目,也不一定需要獨立的公開身分。在引入新區塊前,先將 BEM 名稱扁平化並改善組織方式。

反過來說,元件即使只使用一次,當其內部職責重大且相依性界線明確時,仍可能值得抽取。將重用視為支持證據,而不是必備次數。在這項練習中,第二個放置位置讓界線更容易檢視;但這不代表普遍適用的「使用兩次」規則。

也要重新考慮那些逐漸累積宿主專屬變體的抽取項目。像 shape-tile--inside-workbench 這樣的名稱,表示放置方式可能正成為元件介面的一部分。先確認差異是否應屬於 workbench__preview。只有當你能用元件自身的概念解釋差異時,才將其保留為元件變體。

現代 CSS 範圍限定會改變這項決策嗎?

@scope 規則可以限制其中選擇器的匹配範圍,這有助於管理文件子樹中的選擇器。MDN 目前將它標記為 Baseline 2026,表示自 2026 年 3 月起已在目前版本的瀏覽器中普遍可用;但也指出較舊的裝置或瀏覽器可能不支援。不要假設每台 Mac 都支援它。@scope CSS at-rule

提供的練習使用一般類別選擇器,不需要 @scope。若要支援不具備範圍限定功能的瀏覽器,請將必要的元件樣式保留在這些規則中。範圍限定能改善界線的表達方式,但所有權決策仍需要清楚說明元件的用途、相依性和放置位置。

當你能將複雜元素的用途和內部實作一併移動,讓周圍排版留給宿主負責,並在另一種情境中展示成果時,就應將它抽取出來。如果這些職責仍糾纏不清,就先保留為元素並釐清職責;單靠縮短類別名稱,無法完成重構。

資料來源