當 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」
如何判斷抽取是否成功?
將這項比較當作診斷練習。以下預期結果是根據提供的程式碼推導而來,供你在瀏覽器中自行檢查,並非已回報的瀏覽器測試結果。
- 檢查基準狀態。 兩個圖塊都應有白色背景、藍色圓角形狀、標籤和說明文字。如果所有內容都沒有套用樣式,請先檢查樣式表檔名和連結,再評估元件界線。
- 移除一個放置類別。 從第一個圖塊刪除
workbench__preview,儲存並重新載入。它的上方邊界應有所變化,而內部外觀應保持完整。 - 移除一個宿主類別。 暫時從包含區段移除
workbench。宿主框架和固定寬度應消失;圖塊則應保留元件樣式。 - 修改共用元件。 修改
.shape-tile中的邊框圓角。兩個實例都應隨之變化,而不必修改任何宿主選擇器。 - 測試較長的內容。 將一段說明文字替換為較長的合成句子,並縮窄瀏覽器視窗。檢查文字是否換行,以及所有文字是否仍然可見。
根據職責解讀失敗情況。移除宿主類別後邊框消失,表示元件樣式可能仍設在宿主選擇器中。周圍間距出現非預期變化,則可能是放置規則所致。文字差異可能揭示繼承樣式或全域選擇器的影響。先修正相應的相依關係,再加入其他覆寫。
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。若要支援不具備範圍限定功能的瀏覽器,請將必要的元件樣式保留在這些規則中。範圍限定能改善界線的表達方式,但所有權決策仍需要清楚說明元件的用途、相依性和放置位置。
當你能將複雜元素的用途和內部實作一併移動,讓周圍排版留給宿主負責,並在另一種情境中展示成果時,就應將它抽取出來。如果這些職責仍糾纏不清,就先保留為元素並釐清職責;單靠縮短類別名稱,無法完成重構。
資料來源
- 重要概念 — BEM。
- 快速入門 — BEM。
- CSS — BEM。
- 在 Mac 上使用 TextEdit 處理 HTML 文件 — Apple 支援。
- HTML 標準:連結 — WHATWG。
- CSS 級聯與繼承模組第 5 級 — CSS 工作小組、W3C、編輯草案。
- 繼承 — MDN Web Docs。
- @scope CSS at-rule — MDN Web Docs。