話說在前頭:文中的行為我在 Chromium 141 和 153 上實測過,兩個版本都已經是新的行為。其他瀏覽器的推出時間請以 caniuse 或 MDN 為準。有講錯的地方歡迎指正。

前言

CSS 與 HTML 技巧清單裡有一項不太起眼的變更:「<h1> 巢狀尺寸修正」。

以前把 <h1> 放進 <section>、<article>、<aside>、<nav> 裡,瀏覽器會自動把它縮小,看起來像 <h2>;包兩層,就像 <h3>。現在瀏覽器把這段內建樣式拿掉了,不管包幾層,<h1> 都是一樣大。

這是瀏覽器的預設樣式變更,不用寫任何程式。但我順著這個變更檢查自己網站的標題時,發現了一個更大的問題,所以這篇的後半段是在改我自己的網站。

一、新舊樣式比一比

下面左右兩邊是同一段 HTML:一個 <h1>,外面一層層包著 <section>、<article>、<aside>。左邊套用以前的內建樣式,右邊是現在的:

互動範例:同樣的 HTML,左邊套舊的瀏覽器預設樣式,右邊是現在的預設樣式

舊的預設樣式(模擬)
現在的預設樣式
每一層 h1 的畫面字級,和報讀軟體唸出的層級
位置舊的預設字級現在的預設字級報讀軟體唸
最外層——標題 1
section 裡——標題 1
section > article 裡——標題 1
再加一層 aside——標題 1

此處為互動範例,請在網頁上操作。

可以看到:

  • 左邊(以前):每多包一層就縮小一級,從 32px 一路變成 16px。
  • 右邊(現在):每一層都是 32px。
  • 下方表格最右欄:不管哪一層,報讀軟體唸的都是「標題 1」。

再勾選「模擬網站自己寫了 h1 的字級」,兩邊會變得一模一樣。這是第四節的重點。

二、以前為什麼要縮小?

這要從 HTML5 當初設計的「文件大綱演算法」(outline algorithm)說起。

它的構想是:每個 <section>、<article> 都是一個獨立的區塊,裡面一律從 <h1> 開始寫,瀏覽器會依照巢狀的深度,自動算出真正的層級。<section> 裡的 <h1> 會被當成第二層,再包一層就是第三層。這樣一段內容搬到哪裡,標題都不用改。

瀏覽器內建的縮小樣式,就是為了配合這個構想:既然 <section> 裡的 <h1> 其實是第二層,畫面上就讓它看起來像 <h2>。

問題是,這個演算法從來沒有被瀏覽器或報讀軟體實作過。HTML 規格在 2022 年把它移除了,只剩下「看起來縮小」的樣式還留著。

我實測 Chromium 141 和 153 的無障礙樹,<section>、<article>、<aside> 裡的 <h1>,層級全部都是 1,跟最外層的 <h1> 一樣。

三、這對無障礙有什麼影響?

以前的狀況是:畫面上看起來是第二層、第三層,報讀軟體唸的卻都是「標題 1」。

  • 看得到畫面的開發者,會以為標題層級是對的,因為字級一層層變小,看起來很有條理。
  • 報讀軟體使用者用標題導覽(例如按 H 鍵逐一跳過標題,或叫出標題清單)時,聽到的是一整排「標題 1」,完全分不出哪個是大標、哪個是小節。

這跟我在 attr() 那篇提到的觀念一樣:看到的和報讀軟體讀到的,應該只有一個來源。舊的內建樣式剛好相反,讓畫面「假裝」有層級。

現在拿掉縮小的樣式,畫面就跟真正的層級一致了:寫錯的話,開發時一眼就看得出來,一頁上好幾個一樣大的標題,很明顯不對。

正確的做法還是一樣:依照真正的層級選 <h1> 到 <h6>,不要靠 <section> 幫你降級。

四、會不會影響我的網站?

大部分網站不會。

只要你的 CSS 自己寫了 h1 的字級,就不受影響,因為網站自己寫的樣式本來就比瀏覽器的內建樣式優先。範例裡勾選「模擬網站自己寫了 h1 的字級」,兩邊變得一樣,就是這個原因。用了 reset、normalize 或設計系統的網站,通常都已經寫了。

會受影響的,是沒有自己設定 h1 字級、又把 <h1> 放在 <section> 這類元素裡的頁面:原本縮小的標題,現在會變大。

想確認的話,可以在開發者工具的主控台貼上這行,列出所有被包在區塊裡的 <h1>:

document.querySelectorAll(":is(article, aside, nav, section) h1");

找到的話,與其補上字級把它「縮回去」,不如直接把它改成正確層級的 <h2>、<h3>,畫面和報讀軟體就都對了。

五、順便檢查我的網站:h1 一直是網站名稱

我用 Playwright 把這個網站的 52 個頁面跑過一次,列出每一頁的 <h1>。巢狀的部分沒有問題:每頁只有一個 <h1>,也沒有包在 <section> 裡。

但結果讓我愣了一下:每一頁的 <h1> 都是一樣的,是隱藏起來的網站名稱「Neil 的作品集」。文章標題、頁面名稱(例如「專案」)都是 <h2>。

這是我以前刻意這樣做的。我在無障礙網頁設計學習與簡易要點裡就寫過:網站 Logo 用 <h1>、頁面標題用 <h2>。那是當時很常見的寫法(那篇現在已經更新了)。

但現在比較常見的建議是:<h1> 應該描述這一頁的主要內容。原因是:

  • 報讀軟體使用者常常一進到頁面,就直接跳到 <h1> 確認「我現在在哪一頁」。如果每一頁的 <h1> 都是網站名稱,這個動作就沒有意義了。
  • 網站名稱在每一頁的 <title> 裡已經有了(例如「專案 - Neil 的作品集」),報讀軟體進到頁面時就會唸出來,不需要再佔用標題。

所以我把整個網站改了:

位置改之前改之後
頁首的網站名稱<h1>(隱藏)拿掉,網站名稱已經在 <title> 裡
文章標題、頁面名稱<h2><h1>
首頁<h2>「首頁」(隱藏)<h1>「Neil 的作品集」(隱藏)
文章裡的小節<h3>、<h4>…<h2>、<h3>…

改的時候要注意幾件事:

  • 全部往上一級:頁面標題變成 <h1> 之後,下面所有標題都要跟著往上一級,不然會從 <h1> 直接跳到 <h3>。這包括頁面元件裡的標題、互動範例裡的標題,以及所有文章的 Markdown:54 篇文章、共 636 個標題,### 改成 ##、#### 改成 ###,以此類推。程式碼區塊裡的 # 不能動,所以我用的是會跳過程式碼區塊的腳本。
  • 外觀不要變:CSS 的標題字級也跟著往上挪,例如新的 h1 用原本 h2 的大小。我在改之前和改之後,各拍了 11 個頁面、桌機和手機兩種寬度共 22 張截圖,逐一比對:每個標題的字級、間距、位置都一樣,像素有差異的只有延遲載入的圖片和輸入框游標。
  • 改完再檢查一次:重新跑一次,這次連英文頁面一起,共 83 個頁面:每頁都只有一個 <h1>,而且是這一頁的標題;沒有跳級的標題。axe 也沒有新的問題。

以後寫文章,我的小節就從 ## 開始了。

結論

整理成幾句話:

  • 瀏覽器拿掉了內建樣式:<h1> 放在 <section>、<article>、<aside>、<nav> 裡,不再自動縮小。
  • 以前的縮小是配合「文件大綱演算法」,但它從來沒被實作,報讀軟體一直都唸「標題 1」。
  • 舊樣式讓畫面假裝有層級;新樣式讓畫面和真正的層級一致。
  • 自己有寫 h1 字級的網站不受影響;用 :is(article, aside, nav, section) h1 找找看有沒有巢狀的 <h1>,有的話改成正確的層級。
  • <h1> 應該是這一頁的標題,不是每頁都一樣的網站名稱。

一個瀏覽器預設樣式的小變更,讓我回頭修正了一個用了好幾年的寫法。你的網站,每一頁的 <h1> 是什麼?

延伸閱讀

相關連結