話說在前頭:文中的行為我在 Chromium 141 和 153 上實測過,兩個版本都已經是新的行為。其他瀏覽器的推出時間請以 caniuse 或 MDN 為準。有講錯的地方歡迎指正。
前言
CSS 與 HTML 技巧清單裡有一項不太起眼的變更:「<h1> 巢狀尺寸修正」。
以前把 <h1> 放進 <section>、<article>、<aside>、<nav> 裡,瀏覽器會自動把它縮小,看起來像 <h2>;包兩層,就像 <h3>。現在瀏覽器把這段內建樣式拿掉了,不管包幾層,<h1> 都是一樣大。
這是瀏覽器的預設樣式變更,不用寫任何程式。但我順著這個變更檢查自己網站的標題時,發現了一個更大的問題,所以這篇的後半段是在改我自己的網站。
一、新舊樣式比一比
下面左右兩邊是同一段 HTML:一個 <h1>,外面一層層包著 <section>、<article>、<aside>。左邊套用以前的內建樣式,右邊是現在的:
互動範例:同樣的 HTML,左邊套舊的瀏覽器預設樣式,右邊是現在的預設樣式
| 位置 | 舊的預設字級 | 現在的預設字級 | 報讀軟體唸 |
|---|---|---|---|
| 最外層 | — | — | 標題 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> 是什麼?