紀錄工作經驗、相關知識,解決技術相關問題。

HTML, 新手 Jamp Holes

HTML5 header 內只能放標題嗎?剖析導覽列、搜尋框與巢狀結構的最佳實踐

HTML5 header 內只能放標題嗎?剖析導覽列、搜尋框與巢狀結構的最佳實踐 特色圖片

💡 文章重點摘要:在前端切版與 Code Review 時,最常聽到兩個極端派別的爭論:一派認為「HTML5 header 字面上就是標題,裡面只能放 <h1>~<h6>」;另一派則把整個頁面的側邊欄、彈出視窗甚至頁尾連結通通塞進 <header>。究竟 <header> 的標準內部結構長怎樣?本文將依據 WHATWG 最新規範,帶你一次掌握品牌 Logo、<nav> 主導覽列、現代 <search> 搜尋標籤與 <hgroup> 副標題群組的黃金組合!

如果你已經讀過前一篇《HTML header 標籤與 head 的致命混淆》,相信你已經明白 <header> 是位於 <body> 內部的視覺與語意門面。但在實際動手切版時,許多工程師面對 HTML5 header 內部該放哪些元素依然充滿疑問:「全站搜尋框放進 <header> 合規嗎?」、「文章的副標題跟作者日期寫在 <header> 裡該用 <h2> 還是 <p>?」、「為什麼把網站 Logo 包進 <h1> 會害慘內頁 SEO?」

今天我們就直接對照 WHATWG HTML Living Standard 與 W3C 無障礙規範,將 <header> 的內部解剖圖與合法巢狀邊界一次拆解到位!


⚡ 快速簡單使用(30 秒複製即用範本)

以下提供兩組最常使用的生產級 HTML5 header 標準範本(包含「全站頂部導覽橫幅」與「單篇文章標頭含副標題」),可直接複製至專案中修改套用:

<!-- 範本 A:全站頂部橫幅 Header(含 Logo、主導覽列 nav 與現代 HTML5 search 搜尋框) -->
<header class="site-header">
  <!-- 1. 品牌識別區:內頁請直接用 <a> 包裹 Logo,把全頁唯一 <h1> 留給下方的文章主標題! -->
  <a href="/" class="brand-logo" aria-label="Sean 技術筆記首頁">
    <img src="/assets/logo.svg" alt="Sean 技術筆記" width="140" height="36">
  </a>

  <!-- 2. 主導覽列:使用 <nav> 建立導覽子地標 (role="navigation") -->
  <nav aria-label="主選單">
    <ul role="list">
      <li><a href="/html" aria-current="page">HTML 專欄</a></li>
      <li><a href="/css">CSS 實戰</a></li>
      <li><a href="/seo">SEO 指南</a></li>
    </ul>
  </nav>

  <!-- 3. 站內搜尋框:使用現代 HTML5 <search> 標籤自動建立搜尋子地標 (role="search") -->
  <search aria-label="全站文章搜尋">
    <form action="/search" method="get">
      <label for="global-search" class="visually-hidden">搜尋技術文章</label>
      <input type="search" id="global-search" name="q" placeholder="搜尋 HTML / SEO..." required>
      <button type="submit">搜尋</button>
    </form>
  </search>
</header>

<!-- 範本 B:文章內部 Header(使用 <hgroup> 整合主標題與副標題,不破壞 H1->H2 階層) -->
<article>
  <header class="article-header">
    <hgroup>
      <p class="eyebrow-tag">HTML5 語意化系列 · 實戰篇</p>
      <h1>HTML5 header 內只能放標題嗎?剖析導覽列、搜尋框與巢狀結構的最佳實踐</h1>
      <p class="subtitle">從 &lt;search&gt; 到 &lt;hgroup&gt;,打造符合 WHATWG 標準的現代標頭架構</p>
    </hgroup>
    <div class="article-meta">
      <span>作者:Sean</span> · 
      <time datetime="2026-10-24">2026 年 10 月 24 日</time>
    </div>
  </header>
  <p>文章正文從這裡開始...</p>
</article>

一、 5 秒快速索引:header 內部元素合法性與無障礙角色速查表

根據 WHATWG HTML Living Standard (§4.3.7 The header element) 定義,<header> 代表「一組介紹性或導覽性的輔助內容(a group of introductory or navigational aids)」。它絕對不只能放標題!請參考下表快速判斷哪些元件應該放進 <header>,哪些則是嚴格禁止的語法紅線:

擬放入 <header> 的元件是否合規?標準 HTML5 標籤寫法在無障礙樹(Accessibility Tree)與 SEO 的作用
主標題/區段標題🟢 強烈推薦<h1> ~ <h6>提供頁面或該 <article> / <section> 的核心主題大綱。
主標題 + 副標題/小標🟢 強烈推薦<hgroup> 包裹 <h1> + <p>將主標題與副標題綁定為單一語意群組,避免誤用 <h2> 當副標題而產生假章節。
品牌 Logo 與首頁連結🟢 強烈推薦<a href="/"><img alt="..."></a>在全站頂部 <header> 提供返回首頁入口;內頁切勿將全站 Logo 包進 <h1>。
全站主選單/文章目錄🟢 強烈推薦<nav aria-label="...">在 role="banner" 橫幅地標內建立 role="navigation" 導覽子地標。
站內搜尋表單🟢 強烈推薦<search><form>...</form></search>使用現代 <search> 元素原生映射為 role="search" 搜尋地標(Baseline 2023+ 全面支援)。
作者、發布日期、分類標籤🟢 合法常用<time datetime="...">、<p>在 <article> 的 <header> 內提供文章中繼資訊(Byline / Dateline)。
另一個 <header> 或 <footer>🔴 嚴格禁止❌ 違反 WHATWG 內容模型HTML 驗證器直接報錯!<header> 的後代中絕對不可包含 <header> 或 <footer>。
全站側邊欄或正文內容🔴 語意錯誤❌ 應移至 <main> 或 <aside>把正文或無關側邊欄塞進 <header> 會讓螢幕閱讀器無法跳過導覽區。

二、 深度拆解:生產級全站 header 的四大黃金模組

一個現代化的企業官網、SaaS 儀表板或技術部落格,其頂層 <header class="site-header"> 通常由四大功能模組共同組成。我們先透過下圖檢視它們在 DOM 與無障礙樹中的階層關係:

圖 1:生產級全站 header 內部四大功能模組與 W3C 無障礙地標(Landmark)階層圖
圖 1:生產級全站 header 內部四大功能模組與 W3C 無障礙地標(Landmark)階層圖

模組 1:品牌識別區(Branding)—— 為什麼內頁不該把 Logo 包進 h1?

許多早期教學會示範在全站共用的 <header> 裡寫 <h1><a href="/">網站名稱</a></h1>。
這在網站「首頁」或許沒問題,但一旦套用到全站共用的 Layout 母版,就會導致每一篇技術文章頁都出現兩個 <h1>(一個是全站 Logo,一個是文章標題),甚至把文章主標題被迫降級為 <h2>!

  • 最佳實踐:在文章內頁與功能頁中,全站 <header> 內的品牌 Logo 請直接使用 <a href="/"> 包裹 <img alt="網站名稱" width="..." height="...">,將全頁唯一的 <h1> 100% 留給 <main> 裡面的文章主標題,確保單頁 SEO 主題權重高度集中。

模組 2:主導覽列(Global Navigation)—— nav 該放 header 裡面還是外面?

如我們在《HTML nav 標籤與一般 div 的本質差異》中所分析的,<nav> 代表主要導覽區塊。

  • 什麼時候放進 <header> 內? 當該導覽列屬於「全站主選單(Primary Navigation)」或「單篇文章開頭的章節快速跳轉目錄(Table of Contents)」時,將 <nav> 放在對應的 <header> 內部最符合視覺與語意邏輯。
  • 什麼時候放在 <header> 外? 像是頁尾的網站地圖連結(應放在 <footer> 內)或側邊欄的關聯文章分類導覽(應放在 <aside> 內),就不應塞進頂部 <header>。

模組 3:站內搜尋艙(Site Search)—— 擁抱現代 HTML5 search 標籤!

過去十幾年,為了讓螢幕閱讀器識別 <header> 裡的搜尋框,開發者必須手動在 <form> 加上 ARIA 屬性:<form role="search">。
但在 WHATWG HTML Living Standard (§4.4.15 The search element) 中,官方正式新增了原生語意化標籤 <search>(已於 Chrome 118+、Firefox 118+、Safari 17.0+ 達成 Baseline Widely Available 全平台支援)!

  • 只要將搜尋表單包裹在 <search> ... </search> 內,瀏覽器就會自動將其映射為無障礙樹中的 role="search" 地標,不再需要手寫 role="search",就算你的搜尋功能是由純 JavaScript 即時過濾(沒有 <form> 標籤的 SPA 搜尋組件)也能完美適用!

三、 WHATWG 內容模型與巢狀邊界:如何優雅處理「主標題 + 副標題」?

除了全站頂部導覽外,在 <article> 或 <section> 的內部 <header> 中,我們經常需要同時呈現「專欄分類小標(Eyebrow)+ 主標題(Heading)+ 副標題(Subtitle)」。這時該如何編寫 HTML 才不會踩中語法與 SEO 地雷?請看下圖的 WHATWG 官方決策樹:

圖 2:WHATWG 官方規範 — header 內容模型(Content Model)與巢狀邊界決策樹
圖 2:WHATWG 官方規範 — header 內容模型(Content Model)與巢狀邊界決策樹

1. 致命誤區:在 header 裡用 h1 + h2 寫主副標題

許多新手為了讓副標題字體稍微小一點,會在文章 <header> 裡這樣寫:

<!-- ❌ 錯誤反模式:把 <h2> 當成副標題使用! -->
<header>
  <h1>深入淺出 WebAssembly 效能優化</h1>
  <h2>從 C++ 編譯到瀏覽器執行期的完整實戰指南</h2>
</header>

🔥 為什麼這會破壞結構?
對 Googlebot 爬蟲、螢幕閱讀器以及 WordPress 的自動文章目錄外掛(如 Easy Table of Contents)而言,<h2> 代表「底下有一個獨立的第二層級章節內容」!如果你把副標題寫成 <h2>,緊接著卻直接出現另一個真正的章節 <h2>一、 什麼是 WebAssembly?</h2>:

  1. 自動目錄外掛會把你的副標題誤判為「沒有內文的空章節」列在目錄第一行;
  2. 視障使用者透過 H 鍵巡覽標題時,會以為該 <h2> 下方有對應段落,往下讀卻發現直接跳到下一個標題。

2. 🟢 現代正解:使用復活升級的 hgroup 包裹 h1 與 p

根據 WHATWG HTML Living Standard (§4.3.6 The hgroup element) 最新規範,<hgroup> 的內容模型已經正式改版為:允許包含一個 <h1>~<h6> 主標題,搭配一個或多個 <p> 段落作為副標題或標語!

當你將 <hgroup> 放進 <header> 時,既能清楚表達「這段 <p> 是附屬於 <h1> 的副標題(而非正文第一段)」,又完全不會污染頁面的 <h1> ➔ <h2> ➔ <h3> 標題大綱樹!


四、 生產級實戰對決:從混亂巢狀重構為滿分響應式 header

下面我們將常見的四大錯誤寫法(包含非法嵌套 <header>、濫用 <h2> 副標、漏寫搜尋地標與無障礙標籤)與符合 WHATWG / WCAG 2.2 標準的生產級程式碼進行完整對照。

1. ❌ 常見翻車反模式(違反 WHATWG 內容模型與大綱階層)

<!-- ❌ 陷阱 1:在頂層 <header> 內又嵌套了另一個 <header>,違反 WHATWG 嚴格禁令! -->
<header class="top-wrapper">
  <div class="announcement">全站限時免運中!</div>

  <header class="main-nav-bar">
    <!-- ❌ 陷阱 2:在文章內頁把網站 Logo 包進 <h1>,搶走文章主標題的唯一 <h1> 權重 -->
    <h1><a href="/">極客商城</a></h1>

    <!-- ❌ 陷阱 3:搜尋框沒有 <search> 或 role="search",且 <input> 缺少對應 <label> -->
    <div class="search-box">
      <input type="text" placeholder="找商品...">
      <button>GO</button>
    </div>
  </header>
</header>

<main>
  <article>
    <header>
      <!-- ❌ 陷阱 4:用 <h2> 充當文章副標題,造成大綱階層出現空章節 -->
      <h1>2026 前端框架效能評測報告</h1>
      <h2>React 19、Vue 3.5 與 Svelte 5 真實壓測數據</h2>
    </header>
  </article>
</main>

2. 🟢 生產級標準重構範例(HTML5 + CSS Flexbox + 結構自檢腳本)

<!DOCTYPE html>
<html lang="zh-Hant-TW">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>HTML5 header 內部結構與巢狀最佳實踐</title>
  <style>
    .site-header {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 1.5rem;
      padding: 0.875rem 1.5rem;
      background-color: #0f172a;
      color: #f8fafc;
    }
    .nav-list {
      display: flex;
      gap: 1.25rem;
      list-style: none;
      margin: 0;
      padding: 0;
    }
    .nav-list a {
      color: #e2e8f0;
      text-decoration: none;
    }
    .nav-list a[aria-current="page"] {
      color: #38bdf8;
      font-weight: 700;
      border-bottom: 2px solid #38bdf8;
    }
    .visually-hidden {
      position: absolute;
      width: 1px;
      height: 1px;
      padding: 0;
      margin: -1px;
      overflow: hidden;
      clip: rect(0, 0, 0, 0);
      white-space: nowrap;
      border: 0;
    }
  </style>
</head>
<body>
  <!-- ✅ 頂層唯一的全站 <header> (role="banner"),絕不嵌套第二層 <header> -->
  <header class="site-header">
    <a href="/" class="brand-link">
      <img src="https://example.com/logo.svg" alt="Sean 技術筆記首頁" width="132" height="32">
    </a>

    <nav aria-label="主導覽選單">
      <ul class="nav-list" role="list">
        <li><a href="/html" aria-current="page">HTML5 核心</a></li>
        <li><a href="/a11y">無障礙開發</a></li>
        <li><a href="/perf">渲染效能</a></li>
      </ul>
    </nav>

    <!-- ✅ 使用現代 HTML5 <search> 元素建立原生搜尋地標 -->
    <search aria-label="站內文章搜尋">
      <form action="/search" method="get">
        <label for="site-search-input" class="visually-hidden">輸入關鍵字搜尋文章</label>
        <input type="search" id="site-search-input" name="q" placeholder="搜尋技術主題...">
        <button type="submit">搜尋</button>
      </form>
    </search>
  </header>

  <main>
    <article>
      <!-- ✅ 文章專屬 <header> (位於 <article> 內,自動映射為 role="generic") -->
      <header class="article-header">
        <!-- ✅ 使用 <hgroup> 包裹專欄小標 <p>、唯一主標題 <h1> 與副標題 <p> -->
        <hgroup>
          <p class="eyebrow">HTML5 語意化架構深度剖析</p>
          <h1>2026 前端框架效能評測報告</h1>
          <p class="subtitle">React 19、Vue 3.5 與 Svelte 5 真實壓測數據與記憶體分析</p>
        </hgroup>
        <p class="byline">
          作者:<strong>Sean</strong> · 發布於 <time datetime="2026-10-24">2026-10-24</time>
        </p>
      </header>

      <section aria-labelledby="sec-benchmark">
        <h2 id="sec-benchmark">一、 測試環境與基準指標</h2>
        <p>這裡是第一章節的正文內容,與上方的 <h1> 形成完美的 H1 ➔ H2 階層...</p>
      </section>
    </article>
  </main>

  <script>
    // 🔍 自動化查核腳本:檢測 <header> 是否違反 WHATWG 巢狀禁令並確認地標結構
    function validateHeaderContentModel() {
      const invalidNestedHeaders = document.querySelectorAll('header header, header footer, footer header, address header');
      const hasSearchElement = document.querySelector('header search') !== null;
      const hgroupSubtitles = document.querySelectorAll('header hgroup > p');

      console.log('=== [HTML5 Header 內容模型檢驗報告] ===');
      console.log('1. 非法巢狀 (header/footer/address 互包) 數量:', invalidNestedHeaders.length);
      console.log('2. 頂部 Header 是否包含原生 <search> 地標:', hasSearchElement);
      console.log('3. <hgroup> 內安全包裹的副標題 <p> 數量:', hgroupSubtitles.length);
    }

    validateHeaderContentModel();
  </script>
</body>
</html>

Expected Output (預期 Console 輸出結果)

=== [HTML5 Header 內容模型檢驗報告] ===
1. 非法巢狀 (header/footer/address 互包) 數量: 0
2. 頂部 Header 是否包含原生 <search> 地標: true
3. <hgroup> 內安全包裹的副標題 <p> 數量: 2

五、 實務開發 4 大高頻踩坑雷區

在日常專案維護中,針對 <header> 內部元素的配置,請特別避開以下 4 個常見盲點:

  1. 為了做「雙層頂部列(公告列 + 導覽列)」而寫出 <header> 包 <header>:
    許多電商網站最上方有一條「全站免運公告列(Top Utility Bar)」,下面緊接著「品牌 Logo 與主選單列」。新手常把外層包一個 <header class="site-header">,裡面又寫一個 <header class="main-bar">。
    • 正解:根據 WHATWG <header> 規範,<header> 內部嚴禁出現另一個 <header>!請在外層使用唯一的 <header class="site-header">,內部的公告列與導覽列分別使用 <div class="announcement-bar"> 與 <nav class="main-nav"> 即可。
  2. 把「麵包屑導覽(Breadcrumbs)」跟「主選單」混在同一個 <nav> 裡塞進 <header>:
    有些網站將麵包屑放在頂部 <header> 下緣,但卻沒有將它與主選單分開,導致螢幕閱讀器把兩組毫無關聯的連結當成同一條選單播報。
    • 正解:若 <header> 內同時有主選單與麵包屑,請分別使用兩個獨立的 <nav aria-label="主選單"> 與 <nav aria-label="麵包屑導覽">(或將麵包屑放在 <main> 的開頭)。關於多重 <header> 在不同區段中的進階巢狀拆解,我們將在下一篇《多重 header 怎麼用?HTML5 區段內容與頁首標籤的巢狀階層拆解》深入探討。
  3. 在 <header> 的搜尋框只放放大鏡圖示按鈕,卻漏寫 <label> 或 aria-label:
    現代極簡 UI 常在 <header> 右側放一個沒有文字標籤的搜尋輸入框與 SVG 放大鏡按鈕。若未幫 <input type="search"> 加上 .visually-hidden 的 <label>(或 aria-label="搜尋全站文章"),以及未幫純圖示 <button type="submit"> 加上 aria-label="執行搜尋",會在 Lighthouse 無障礙檢測中直接被扣分退件!
  4. 把彈出式登入 Modal 或全站側邊抽屜(Drawer)的龐大 DOM 直接塞在 <header> 裡:
    雖然「登入按鈕」與「漢堡選單觸發鈕」位於 <header> 內,但點擊後彈出的「大型會員登入燈箱(<dialog>)」建議放在 <body> 末端(或透過 React Portal 掛載),避免讓頂部 <header> 地標內部塞滿與導覽無關的表單欄位。

六、 上線前 5 點自我檢驗清單 (Checklist)

在完成頁首與文章標頭切版後,請依序核對以下 5 項指標:

  • ☐ 1. 無非法後代與祖先元素:確認頁面執行 document.querySelectorAll('header header, header footer, footer header, address header').length 結果為 0。
  • ☐ 2. 內頁唯一 <h1> 未被全站 Logo 佔用:在文章內頁與產品內頁中,全站 <header> 內的品牌 Logo 僅使用 <a href="/"> 包裹,確保全頁唯一的 <h1> 留給正文主標題。
  • ☐ 3. 副標題正確採用 <hgroup> + <p>:若 <header> 內有專欄小標(Eyebrow)或副標題,已使用 <hgroup> 包裹 <h1> 與 <p>,而非誤用 <h2> 破壞標題階層。
  • ☐ 4. 搜尋區具備 <search> 地標與完整無障礙命名:<header> 內的搜尋表單已使用 <search>(或 <form role="search">)包裹,且 <input type="search"> 與純圖示提交 <button> 皆具備明確的無障礙名稱。
  • ☐ 5. 導覽列標註 aria-label 與當前頁面狀態:<header> 內的 <nav> 已加上精準的 aria-label="主選單"(不重複寫「導覽」二字),且當前所在頁面的 <a> 已標註 aria-current="page"。

參考資料與延伸閱讀

發表迴響