💡 文章重點摘要:在前端開發與程式碼審查(Code Review)中,許多工程師常認為「一份 HTML 文件只能寫一個 HTML main 標籤(
<main>)」。然而,翻開 WHATWG HTML Living Standard (§4.4.14) 官方規範就會發現:標準真正限制的是「同一時間最多只能有一個未設定hidden屬性的<main>元素」。本文將從官方規格出發,完整解析單頁應用(SPA)多視圖切換架構、祖先元素階層限制(Hierarchically Correctmain),以及main[hidden]被自訂 CSSdisplay: flex / grid意外覆蓋的解決方式。
在規劃網頁骨架時,<main> 負責包覆整張頁面最核心的獨有內容(排除跨頁重複的導覽列、側邊欄與版權頁尾),並讓螢幕閱讀器使用者能透過快捷鍵(或 Skip Link)一鍵跳過頂部選單直達正文。過去在 W3C HTML 5.0 早期規範中,確實曾規定一份文件不得出現超過一個 <main>;但隨著單頁應用(SPA)與多步驟表單介面普及,WHATWG HTML Living Standard 早已調整了對 HTML main 標籤 的規範定義。
究竟什麼情況下可以在 DOM 樹中同時保留多個 <main>?如果用 CSS display: none 代替 HTML 的 hidden 屬性,為什麼還是會違反語法標準?以下先提供 30 秒即可複製使用的多視圖切換範本,再逐步拆解底層規則。
⚡ 快速簡單使用(30 秒複製即用範本)
當你在實作「無須重新載入頁面即可切換主視圖(例如:『數據總覽』切換至『帳務設定』)」的介面,且希望預先將 DOM 結構保留在頁面上時,請遵循以下符合 WHATWG §4.4.14 規範的標準寫法:
<!DOCTYPE html>
<html lang="zh-Hant-TW">
<head>
<meta charset="UTF-8">
<style>
/* 1. 關鍵防護:當對 main 設定 display: grid 或 flex 時,必須補上 [hidden] 保護,避免覆蓋瀏覽器預設隱藏樣式 */
main[hidden] {
display: none !important;
}
main {
display: grid;
gap: 1.5rem;
padding: 1.5rem;
}
</style>
</head>
<body>
<header>
<!-- 2. Skip Link 跳至當前可見的主內容區 -->
<a href="#view-overview" id="skip-to-main">跳至主要內容</a>
<nav aria-label="後台功能視圖切換">
<button type="button" data-target="view-overview" aria-current="page">數據總覽</button>
<button type="button" data-target="view-billing">帳務設定</button>
</nav>
</header>
<!-- 3. 當前可見的唯一 main(不加 hidden 屬性) -->
<main id="view-overview" tabindex="-1">
<h1>數據總覽儀表板</h1>
<p>這裡是當前顯示的主要內容區域。</p>
</main>
<!-- 4. 預先載入但目前未啟用的第二個 main(必須明確加上原生 hidden 屬性) -->
<main id="view-billing" hidden tabindex="-1">
<h1>帳務與訂閱設定</h1>
<p>切換至此視圖時,將移除此處的 hidden,並為其他 main 加上 hidden。</p>
</main>
</body>
</html>
一、 5 秒快速索引:WHATWG 現行規範 vs. 傳統認知對照表
為了協助開發團隊快速核對 HTML main 標籤 的合規條件,以下整理 WHATWG HTML Living Standard (§4.4.14 The main element) 與 W3C HTML-AAM 1.0 的核心規範:
| 檢核項目 | 早期傳統認知(舊版 HTML 5.0) | WHATWG HTML Living Standard 現行官方規範 | 實務影響與檢測工具判定 |
|---|---|---|---|
| 單頁允許數量 | 整個 DOM 只能有 1 個 <main> | 可存在多個 <main>,但同一時間最多只能有 1 個「未加 hidden 屬性」的 <main> | 若同時有 2 個以上無 hidden 的 <main>,W3C Nu Validator 與 Lighthouse 會直接報錯。 |
隱藏非活動 <main> 的方式 | 混用 .hidden { display: none } 或 hidden | 規範明確要求使用 HTML 原生 hidden 屬性(含 hidden="until-found") | 若僅寫 CSS style="display: none" 而未加 hidden 屬性,HTML 語法驗證器仍視為違規。 |
| 合法的父層與祖先元素 | 只要放在 <body> 裡即可 | 祖先僅限 <html>、<body>、<div> 或無無障礙名稱的 <form> | 不可將 <main> 放在 <article>、<section>、<aside>、<header>、<footer> 或 <nav> 內。 |
| W3C 無障礙樹映射角色 | role="main" | 階層正確且未隱藏之 <main> 映射為 role="main" 主地標 | 供螢幕閱讀器與鍵盤 Skip Link 直接定位整頁核心正文(詳見《WCAG 無障礙標準全解析》)。 |
是否支援巢狀(<main> 包 <main>) | 禁止 | 禁止(<main> 的祖先不得為另一個 <main>) | 違反階層正確性(Not a hierarchically correct main element)。 |
二、 官方規格深度解析:什麼才算是合法的 main 標籤配置?
許多文章會將 <main> 的數量限制簡化為「邏輯上的唯一性」,但瀏覽器解析器與 W3C 標準驗證器(Validator)依據的是兩條非常明確的硬性語法規則:
1. 規則一:可見性唯一原則(At most one visible main)
根據 WHATWG §4.4.14 原文定義:
"A document must not have more than one
mainelement that does not have thehiddenattribute specified."
(一份文件不得擁有超過一個未指定hidden屬性的main元素。)
這項規則是專為 SPA 多視圖快取或條件式畫面切換所設計的。當網頁預先渲染了多個視圖容器時,只要確保當前畫面上僅有 1 個 <main> 沒有 hidden 屬性,而其餘所有備用 <main> 皆明確標註了 hidden 屬性,那麼無論在 HTML 語法驗證器、搜尋引擎爬蟲(Googlebot)還是螢幕閱讀器無障礙樹中,都 100% 合法且只會暴露出當前活動的那一個 role="main" 地標。
2. 規則二:階層正確性原則(Hierarchically correct main element)
除了數量限制,WHATWG 還定義了「階層正確的 main 元素(Hierarchically correct main element)」:從 <main> 一路往上追溯到根元素 <html> 的所有祖先節點(Ancestors),只能是 <html>、<body>、<div> 或「未設定無障礙名稱(No accessible name)的 <form>」。
換言之,如果你把 <main> 包在 <section>、<article> 或 <aside> 裡面,即便全頁只有這 1 個 <main>,依然違反了 HTML 標準!請透過下圖的判定流程檢視完整規則:

三、 實務開發常見的 3 個配置盲點與解決方式
在實際專案中導入多重 <main> 或調整版面結構時,最常遇到以下三個技術細節問題:
1. 盲點一:為 main 設定 display: flex 或 display: grid 意外覆蓋了 hidden 屬性
這是在現代 CSS 排版中最容易忽略的樣式優先權問題!
- 成因解析:HTML 的
hidden屬性在瀏覽器內建樣式表(User-Agent Stylesheet)中的實作僅為[hidden] { display: none; }。當你在專案 CSS 中寫下main { display: grid; }或main { display: flex; }時,因為作者樣式表(Author Stylesheet)的優先權高於瀏覽器預設樣式,這行規則會直接覆蓋掉[hidden]的display: none,導致所有加了hidden的備用<main>全部同時顯示在畫面上! - 解法:在全域 CSS Reset 中加入一行防護規則:
/* 確保帶有 hidden 屬性的元素不會被 display: flex / grid 覆蓋而意外顯示 */
[hidden] {
display: none !important;
}
2. 盲點二:只用 CSS .d-none { display: none } 隱藏第 2 個 main,卻沒加 HTML hidden 屬性
有些開發者疑問:「既然在渲染樹(Render Tree)與無障礙樹(Accessibility Tree)中,設定 CSS display: none 也會把元素隱藏起來,為什麼一定要寫 HTML 的 hidden 屬性?」
- 標準規範差異:
- HTML 語法與靜態分析層面:W3C HTML Validator、ESLint
jsx-a11y以及部分不解析外部 CSS 的輕量級爬蟲在檢視 HTML DOM 時,是直接檢查<main>標籤上是否存在hidden屬性。若只靠外部 CSS 類別隱藏,HTML 驗證器依然會判定違反「超過一個未標註hidden之<main>」錯誤。 - 無樣式環境與閱讀器模式(Reader Mode):當網路異常導致 CSS 載入失敗,或使用者開啟瀏覽器內建的「閱讀模式」擷取 HTML 原始結構時,原生的
hidden屬性屬於語意層級的「當前不相關(Not yet or no longer relevant)」,能確保備用視圖不會被誤抓進文章正文。
- HTML 語法與靜態分析層面:W3C HTML Validator、ESLint
3. 盲點三:視圖切換時忘記同步更新 Skip Link(跳至主要內容)與鍵盤焦點
當你在 SPA 中透過 JavaScript 將原本的 <main id="view-a"> 加上 hidden,並把 <main id="view-b"> 的 hidden 移除時,別忘了頁首(參考《網頁無障礙:別讓 header 成為螢幕閱讀器的盲點》)還有一顆「跳至主要內容」的 Skip Link!
- 解法:在切換視圖的函式中,同步將 Skip Link 的
href指向當前顯示的<main>ID(例如#view-b),或將焦點移至新視圖容器,如下圖所示:

四、 實戰程式碼對比:從常見錯誤結構到符合 WHATWG 標準的多視圖切換
以下我們對比常見的錯誤寫法,並提供包含自動化無障礙與規格檢驗腳本的完整生產級範例。
1. ❌ 常見錯誤寫法(同時暴露多個可見 main、誤包進 section、display: flex 覆蓋 hidden)
<style>
/* ❌ 問題 1:直接設定 main { display: flex } 且未保護 [hidden],導致下方加了 hidden 的 main 依然顯示在畫面上 */
main {
display: flex;
flex-direction: column;
}
</style>
<!-- ❌ 問題 2:將 <main> 包在 <section> 內部,違反 WHATWG 階層正確性(Hierarchically correct main)規範 -->
<section class="page-wrapper">
<!-- ❌ 問題 3:把兩張並排的獨立卡片都寫成 <main>,導致同頁同時存在多個未隱藏的 main 地標 -->
<main class="card-left">
<h2>左側熱門文章列表</h2>
</main>
<main class="card-right">
<h2>右側最新公告列表</h2>
</main>
<main id="backup-view" hidden>
<h2>原本想隱藏的備用視圖,卻因上方 main { display: flex } 而意外顯示出來</h2>
</main>
</section>
2. 🟢 生產級標準重構範例(符合 WHATWG §4.4.14 多視圖 hidden 切換 + 自動化規格診斷腳本)
<!DOCTYPE html>
<html lang="zh-Hant-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WHATWG 標準多重 main 標籤與 hidden 視圖切換實戰範本</title>
<style>
/* ✅ 1. 強制保護 hidden 屬性,避免被 display: grid / flex 覆蓋 */
[hidden] {
display: none !important;
}
body {
margin: 0;
font-family: system-ui, -apple-system, sans-serif;
}
.view-switcher {
display: flex;
gap: 0.75rem;
padding: 1rem 1.5rem;
background: #f8fafc;
border-bottom: 1px solid #e2e8f0;
}
.view-switcher button[aria-current="page"] {
background: #2563eb;
color: #ffffff;
font-weight: 600;
}
main {
display: grid;
gap: 1rem;
max-width: 760px;
margin: 0 auto;
padding: 2rem 1.5rem;
}
</style>
</head>
<body>
<header>
<a href="#view-dashboard" id="skip-link" class="skip-link">跳至主要內容</a>
<nav class="view-switcher" aria-label="主視圖切換">
<button type="button" data-view="view-dashboard" aria-current="page">數據總覽視圖</button>
<button type="button" data-view="view-settings">系統設定視圖</button>
</nav>
</header>
<!-- ✅ 視圖 1:預設可見的唯一 <main>(直接位於 <body> 下,符合階層正確性) -->
<main id="view-dashboard" tabindex="-1">
<h1>數據總覽視圖(當前活動的 Main 地標)</h1>
<p>同一時間整張網頁只有這一個 main 沒有 hidden 屬性,無障礙樹僅暴露單一 role="main" 地標。</p>
</main>
<!-- ✅ 視圖 2:預先渲染但標註 hidden 的備用 <main> -->
<main id="view-settings" hidden tabindex="-1">
<h1>系統設定視圖(切換後才會成為活動的 Main 地標)</h1>
<p>當切換至此視圖時,上方的數據總覽會自動加上 hidden 屬性。</p>
</main>
<script>
// 1. 多視圖切換控制器:確保任何時刻僅有 1 個 <main> 不帶 hidden 屬性
const buttons = document.querySelectorAll('.view-switcher button[data-view]');
const mains = document.querySelectorAll('main');
const skipLink = document.getElementById('skip-link');
function switchMainView(targetId) {
mains.forEach((mainEl) => {
const isTarget = mainEl.id === targetId;
mainEl.hidden = !isTarget;
});
buttons.forEach((btn) => {
if (btn.dataset.view === targetId) {
btn.setAttribute('aria-current', 'page');
} else {
btn.removeAttribute('aria-current');
}
});
// 同步更新 Skip Link 的目標錨點
if (skipLink) {
skipLink.setAttribute('href', `#${targetId}`);
}
}
buttons.forEach((btn) => {
btn.addEventListener('click', () => switchMainView(btn.dataset.view));
});
// 2. 🔍 WHATWG §4.4.14 <main> 規範合規性自動檢測腳本
function validateMainElements() {
const allMains = [...document.querySelectorAll('main')];
const visibleMains = allMains.filter(m => !m.hasAttribute('hidden'));
const allowedAncestors = new Set(['HTML', 'BODY', 'DIV', 'FORM']);
const invalidHierarchyMains = allMains.filter((m) => {
let parent = m.parentElement;
while (parent) {
if (!allowedAncestors.has(parent.tagName)) return true;
if (parent.tagName === 'FORM' && (parent.hasAttribute('aria-label') || parent.hasAttribute('aria-labelledby'))) {
return true;
}
parent = parent.parentElement;
}
return false;
});
// 檢查是否有加了 hidden 卻因 CSS 覆蓋而未隱藏的 main
const brokenHiddenMains = allMains.filter(
m => m.hasAttribute('hidden') && window.getComputedStyle(m).display !== 'none'
);
console.log('=== [WHATWG HTML main 標籤合規性檢測報告] ===');
console.log('1. DOM 中 <main> 總數 / 當前可見(無 hidden)數量:', `${allMains.length} / ${visibleMains.length}`);
console.log('2. 符合「最多僅 1 個可見 <main>」規範:', visibleMains.length === 1);
console.log('3. 所有 <main> 皆符合祖先階層限制 (Hierarchically Correct):', invalidHierarchyMains.length === 0);
console.log('4. 零 main[hidden] 被 CSS display 意外覆蓋:', brokenHiddenMains.length === 0);
}
validateMainElements();
</script>
</body>
</html>
Expected Output (預期 Console 輸出結果)
=== [WHATWG HTML main 標籤合規性檢測報告] ===
1. DOM 中 <main> 總數 / 當前可見(無 hidden)數量: 2 / 1
2. 符合「最多僅 1 個可見 <main>」規範: true
3. 所有 <main> 皆符合祖先階層限制 (Hierarchically Correct): true
4. 零 main[hidden] 被 CSS display 意外覆蓋: true
五、 上線前 5 點自我檢驗清單 (Checklist)
在完成頁面佈局或實作 SPA 多視圖切換前,請逐一核對以下 5 項指標:
- ☐ 1. 同一時間僅有 1 個無
hidden的<main>:確認頁面上未標註hidden屬性的<main>數量剛好為1,避免把頁面內的並排區塊或卡片誤寫成多個可見的<main>。 - ☐ 2. 祖先節點符合階層正確性(Hierarchically Correct):確認從
<main>往上到<html>之間,僅經過<body>、<div>或無命名的<form>,沒有被包在<article>、<section>、<aside>、<header>、<footer>或<nav>內。 - ☐ 3. 全域 CSS 已配置
[hidden] { display: none !important; }:確認對<main>設定display: flex或display: grid時,不會覆蓋掉備用<main hidden>的隱藏狀態。 - ☐ 4. 使用原生
hidden屬性而非單靠 CSS class 隱藏:若有多個預先渲染的<main>視圖,確認非活動視圖皆直接加上 HTMLhidden屬性(透過 JSelement.hidden = true控制),確保通過 W3C HTML 驗證器檢查。 - ☐ 5. 支援 Skip Link 跳轉與鍵盤聚焦:確認可見的
<main>設有對應的id與tabindex="-1",且切換視圖時頁首的「跳至主要內容」連結能正確指向當前活動的<main>。
參考資料與延伸閱讀
- WHATWG HTML Living Standard: The
mainelement (§4.4.14) — 官方定義單頁最多僅能有一個無hidden屬性的<main>元素,以及「階層正確main元素(Hierarchically correctmainelement)」的祖先限制。 - MDN Web Docs:
<main>The Main element — Mozilla 開發者社群針對<main>語意模型、hidden屬性多視圖切換與 Skip Navigation 無障礙實務的完整說明。 - W3C HTML Accessibility API Mappings 1.0 (HTML-AAM):
mainElement — W3C 規範階層正確的<main>元素如何映射為無障礙樹中的role="main"地標。 - W3C ARIA Authoring Practices Guide (APG): Main Landmark — W3C 官方無障礙指南說明
main地標在螢幕閱讀器導覽中的設計原則。 - MDN Web Docs: HTML
hiddenglobal attribute — 官方詳解hidden全域屬性的語意行為,以及為何 CSSdisplay屬性會覆蓋瀏覽器預設的[hidden]樣式。
發表迴響