headingoffset
と headingreset
属性による見出しレベルの調整
HTML にグローバル属性として headingoffset
と headingreset
属性を追加する議論が進んでいます。これが実現すれば見出し要素だけでなく、その祖先要素でも見出しレベルを調整できることになります。
-
Consider adding a headinglevelstart attribute · Issue #5033 · whatwg/html
-
implement headingoffset & headingreset attributes by keithamus · Pull Request #11086 · whatwg/html
一方、そうなると現状のように <hn>
= 見出しレベル N とは限らなくなり、「見出しレベル N の要素を選択する」ことが困難になるため、CSS 側でも :heading
および :heading()
疑似クラスの導入が画策されています。
- これらの PR は2025年3月14日現在マージされておらず、内容は変更される可能性があります。とくに
headingreset
の属性名は混乱を引き起こす懸念も出ており、名称が変わるかもしれません。
現行の見出しレベルの問題点
現行の HTML では <h1>
~<h6>
要素を使って見出しレベルが決定されます。
<body>
<h1>見出しレベル 1</h1>
<h2>見出しレベル 2</h2>
<h3>見出しレベル 3</h3>
<h2>見出しレベル 2</h2>
</body>
しかしテンプレートエンジンやコンポーネント指向設計を採用したケースでは見出し要素の出力に条件分岐が必要になるなど、実装が複雑になってしまうことがあります。
<body>
<h1>見出しレベル 1</h1>
<%- include('component/XXX.html', { headingLevelStart: 2} ) -%>
<section>
<h2>見出しレベル 2</h2>
<%- include('component/XXX.html', { headingLevelStart: 3} ) -%>
</section>
</body>
<!-- component/XXX.html -->
<section>
<!-- 見出しが出現するごとに条件分岐が必要 -->
<%_ if (headingLevelStart === 2) { _%>
<h2>コンテンツの最上位見出し</h2>
<%_ } else if (headingLevel === 3) { _%>
<h3>コンテンツの最上位見出し</h3>
<%_ } _%>
<p>...</p>
</section>
このような状況に対して、HTML5 の当初の仕様ではアウトラインアルゴリズムによってセクショニングコンテンツと <h1>
要素だけを使ってアウトラインを作ることができるとされていたのですが、ブラウザや支援技術のサポートが行われていなかったため、長い議論の末2022年7月にアウトラインアルゴリズムが削除されることで一応の決着が付いた次第です。
一方で前述のとおり、HTML4 時代のように <h1>
~<h6>
の要素名によってのみ見出しレベルが決定される現状は不便であり、アウトラインアルゴリズムとは別の手法で解決を図る議論が以前から行われています。
headingoffset
と headingreset
属性の提案
今回の Pull Request の元になった Issue が立てられた当初の時点(2019年)では見出しレベルの絶対値を指定する
headinglevelstart
属性が提案されていました。特徴的なのはネストによる継承をしない方針とされていたことで[1]、これは <ol start>
と同じ挙動と言えます。
<body>
<h1>見出しレベル 1</h1>
<div headinglevelstart="2">
<h1>見出しレベル 2</h1>
<h2>見出しレベル 3</h2>
<div headinglevelstart="1">
<!-- ネストされていても継承しない -->
<h1>見出しレベル 1</h1>
<h2>見出しレベル 2</h2>
</div>
</div>
</body>
しかし議論が進む中で絶対値指定でなく相対値指定にすることとなり、属性名も headingoffset
へ変更されました。また同時に見出しレベルをリセットする headingreset
属性(真偽属性)も追加されています。
<body>
<h1>見出しレベル 1</h1>
<h1 headingoffset="1">見出しレベル 2(要素名の 1 + headingoffset 属性値 1 = 2)</h1>
<div headingoffset="1">
<h1>見出しレベル 2(要素名の 1 + headingoffset 属性値 1 = 2)</h1>
<h2>見出しレベル 3(要素名の 2 + headingoffset 属性値 1 = 3)</h2>
<div headinglevelstart="2">
<h1>見出しレベル 4(要素名の 1 + headingoffset 属性値の小計 3 = 4)</h1>
<h2>見出しレベル 5(要素名の 2 + headingoffset 属性値の小計 3 = 4)</h2>
</div>
<div headingreset>
<h1>見出しレベル 1</h1>
</div>
</div>
</body>
以下、特殊な例を提示します。
<body>
<div headingoffset="10">
<h1>見出しレベル 9(レベルは 9 より大きくはならない)</h1>
</div>
<div headingoffset="-1">
<h1>見出しレベル 1(headingoffset に指定できるのは正の整数のみ、負の値やその他無効な値が指定された場合は 0 の扱いになる)</h1>
</div>
<div headingoffset="2">
<h1 aria-level="3">見出しレベル 3(見出し要素の数字や headingoffset 属性値に関わらず aria-level が優先される)</h1>
</div>
<div headingoffset="2">
<dialog id="modal-dialog">
<h1>見出しレベル 1(showModal() されたダイアログは offset しない)</h1>
</dialog>
<script>
document.querySelector('#modal-dialog').showModal();
</script>
</div>
</body>
冒頭の注釈にも記したとおり、まだ議論は進行中であり今後内容や属性名が変更される可能性は充分に考えられるのですが、すでに Chromium には実装されており、Chrome canary 136.0.7067.2 で挙動を確認できます。
body > div[headingoffset=1] > h2
な見出し要素を選択した様子。アクセシビリティパネルの計算済みプロパティにはレベル: 3と表示されている。