document outline algorithm と h1 要素

HTML, Accessibility

没ネタの供養。

TL;DR

  • h1 を複数置けるわけじゃない
  • アウトラインを生成するアルゴリズムはブラウザで実装されてない
  • 見出しレベルを自動調整するように動いていたが頓挫した
  • section お前は何者だ

document outline algorithm について

"document outline algorithm" とは、HTML のアウトラインを生成する都市伝説のこと。

アウトラインとは、見出しを持ったセクションで構成される枠を指す。
HTML5 より前は、このアウトラインを表現する方法がなく、広義な wrapper の div が採用されていた。
見出し要素もセクションごと、というよりは文書の階層にかかるもので、兄弟としてのフラットな構造だった。

HTML5 から、 sectionnav などで囲みアウトラインを生成する謎のアルゴリズム "document outline algorithm" が登場した。
詳細は WHATWG の spec にある。
4.3.11 Headings and sections, 4.3.11.1 Creating an outline - HTML Standard

"sectioning content" は sectionnav などのセクションを構成する要素、"sectioning root" はそれらのルートに当たる body などの要素を指す。
これらを起点に、暗黙のセクション・サブセクションを生成する。
セクション・サブセクションは、1 つの見出しを持ち、ネスト可能なアウトラインのパーツのようなもの。

例えば、下記のようなマークアップで、section を起点とした暗黙のアウトラインが生成される。
"sectioning" とか呼ばれることもある。

html

<body>
  <h1>ページのタイトルだよ</h1>
  <main>
    <section>
      <h1>セクション 1 だよ</h1>
      <p>セクションの文章だよ</p>
    </section>
    <section>
      <h1>セクション 2 だよ</h1>
      <p>セクションの文章だよ</p>
      <section>
        <h1>セクション 2 だよ</h1>
        <p>セクションの文章だよ</p>
      </section>
    </section>
    <section>
      <h1>セクション 3 だよ</h1>
      <p>セクション 3 の文章だよ</p>
      <h2>セクション 3 サブタイトルだよ</h2>
      <p>セクション 3 の文章その 2 だよ</p>
      <h3>セクション 3 サブタイトルだよ</h2>
      <p>セクション 3 の文章だよ</p>
    </section>
  </main>
</body>

これによって section 内では h1 から始めても良く、むしろ h1 だけでも OK などのが伝承が広まった。

Sections may contain headings of any rank, but authors are strongly encouraged to either use only h1 elements, or to use elements of the appropriate rank for the section's nesting level. (訳: セクション内には任意のランクを含めることができますが、著者は h1 要素のみを使用するか、セクションのネストに応じたランクの見出しを記述することを推奨します。) - 4.3.11 Headings and sections on HTML Standard

現実

2020 年 10 月時点、このアルゴリズムは全てのブラウザや支援技術で実装されていない。
実際にアウトラインなるものは確認できず、見出しはフラットな構造を保ったまま。

VoiceOverの画面。h1要素が複数あり、どの見出しがメインとなるのかわかりづらい。アウトラインも確認できない

W3C ではこのアルゴリズムに従わないよう警告が記されていたけど、WHATWG から消えてしまった。
4.3.9.1. Creating an outline - HTML 5.2

それに対して Steve Faulkner 氏が 2015 年に Issue を立て、現在も議論が続いている。
Suggest adding a warning about outline algorithm · Issue #83 · whatwg/html

仕様と矛盾した警告を載せるのではなく、アルゴリズム自体を消すか実装するかの問題に変わり、今も掲載されてない。
MDN には掲載されているので、それなりに周知されているのかもしれない。
Using HTML sections and outlines - Developer guides | MDN

実装しようとしてもアルゴリズム自体が複雑で、これといった対応がないまま 10 年以上は停滞している様子。

近年の動き

近年、Anne van Kesteren(annevk)氏を中心に少し動きがあり、「アウトラインを生成すること」ではなく「"sectioning content" 内の見出しレベルを調整すること」に焦点が当てられた。
annevk 氏は James Craig 氏の提案をベースに、見出しの調整に関する Polyfill を作成した。
実装は非常にシンプルで、h1hgroup(後で説明) の "sectioning content | root" をたどり、ネストされたぶんレベルを加算して aria-level 属性に設定するもの。

html-heading-level-polyfillより

function determineLevel(el) {
  let level = 1;
  // Arguably the parentNode being null check can be removed as it will never be null when this and
  // the function below are run on a document, as is the case.
  //
  // This is "sectioning content" and "sectioning roots"
  while (el.parentNode && (el = el.parentNode.closest("article,aside,nav,section,blockquote,details,dialog,fieldset,figure,td"))) {
    level += 1;
  }
  return level;
}

スクリーンリーダーユーザーを対象にした WebAIM の調査によれば、全体の 68.8% のユーザーが「見出しをナビゲートに使用する」と回答し、計 86.1% のユーザーが見出しレベルを「役に立つ」か「やや役に立つ」と回答していて、見出しが重要な要素を占めていることがわかる。(どのページにも見出しはあるから、それはそうだけど)
机上の空論を掲載し続けるより、今あるナビゲートの最適化を選ぶのは筋が通ってる。

そこから事が進み、sectioning の概念を廃止する PR が提出された。
:heading 擬似クラスを追加、文書のルートとなる h1 を持たなければならないなどの特徴がある。
議論では headinglevelstart という「見出しレベル調整の基準となる値」を設定する闇の属性も提案されている。
Add heading-focused outlines and :heading by annevk · Pull Request #3499 · whatwg/html
Consider adding a headinglevelstart attribute · Issue #5033 · whatwg/html

悲しいことに、この構想自体は後方互換の問題があったようで、実装が頓挫してしまった。
Heading levels — Anne’s Blog
Intent to prototype: heading levels

ただ、見出しをベースとしたアルゴリズム再構築の試みとして、まだ何らかの動きがありそう。
Add heading-focused outlines and :heading by annevk · Pull Request #3499 · whatwg/html

長年滞っていた問題にメスが入り sectioning 廃止の PR が出たりして、今後の動向に少し期待してる。
一方、これまで h1 だった見出しが 3 にも 4 にもなりそうで、破綻に慣れてしまったユーザーにとってこれが良いのか悪いのかはわからない。

h要素について

h 要素というのもしばしば言及される。
これは section 要素と組み合わせ、見出しレベルをいい感じにするため提案される要素。

html

<section>
  <h>見出しだよ</h>
  <p>こんにちは</p>
</section>

h1-6 というレベルは HTML の祖先にあたる GML から存在していて、それがそのまま引き継がれている。
(実際には GML は h0-6 で、今のレベルになったのは後継の SGML から)

後方互換性を捨てた XHTML2.0 ではこれらのレベルを deprecated とし、かわりに section 要素と h 要素を用いて、見出しレベルの設定を UA 側に委ねる提案があった。
しかし、 h1-6 要素は残されたままで、XHTML 2.0 自体も放置され、謎だらけのまま終わってしまった。
XHTML Block Text Module

HTML5 でも、document outline algorithm の問題を解決するために h 要素が提案された。
Do not recommend using nested sections with h1 · Issue #169 · w3c/html
Add h element · Issue #774 · w3c/html

ニーズが現代 Web 事情に合致している。顧客が本当に求めているもの。
やるとしても新しい要素になるので、UA 側の対応・マッピングや、後方互換性の問題が懸念されていたりする。
あと「悔しかったら暗黙のセクション生成してみろよ」のようなマークアップで対応ができないこともある。

html

<aside>
 <!-- 実は h5 かもしれない -->
  <h>タイトル</h>
  <p>こんにちは</p>
  <h>タイトルですよね</h>
  <p>そうですよね</p>
  <!-- h6 かも -->
  <h>タイトルですよね</h>
  <p>そうですよね</p>
</aside>

ちなみに h 要素が初めて言及されたのは 30 年近くも前のことになる。時代が早すぎた。
Re: status. Re: X11 BROWSER for WWW - Tim Berners-Lee

section 要素について

h 要素の wrapper として機能する section だけど、今の section 要素は一体何者なのか。

この要素は HTML5 から追加された要素で、"sectioning content" のひとつ。
また、region ランドマークを持つことがある。
"generic section of a document or application(ドキュメントまたはアプリケーションの一般的なセクション)" というあまりにも曖昧な何かを示す時に使う。

HTML5 より前から section 自体は構想として出てきたものの、汎用的な div 要素に代わられていた。
document outline algorithm と sectioning の概念ができることで、ようやく人権を得た。
section には見出しを入れるべき」という教えは、sectioning の概念から来ているのかもしれない。
しかし、その恩恵を受けられるアルゴリズムが実装されていない現状、役割は闇に覆われている。

WHATWG の section 要素の説明では、中に h1-6 要素を追加することについて "typically" と表現していて、 "must" や "should" はない(言葉の綾というか揚げ足)。
ARIA 1.2 にある region の説明では、 "SHOULD" の表現が用いられているだけで、何の役にも立たない。
HTML-AAM だと、section 要素は accessible name が存在しないと role を持たない、とある。
section - HTML Accessibility API Mappings 1.0

accessible name については、下記が参考になる。
WCAG に出てくる「名前」とは? | Accessible & Usable

実際に、下記のマークアップを試してみる。

html

<section aria-label="section要素が本当にsectionなのかをテストするsection">
  sectionだよ
</section>

すると、section 要素は識別可能な「名前」を持った region(日本語だと「地域」)role となる。
これはランドマークとして支援技術にも追加され、ナビゲート可能なもの。

section要素がランドマークとして登録されている。

注意点は、section が必ずしも role を抱えるわけではないということ。
なぜなら、sectionregion かどうか以前に、セクションを構成する要素だから。
識別可能なランドマークとしての section と、意味論としての section は、役割が別となる。
しかし現実は「見出しを入れて、識別可能にするべき」のようなごちゃ混ぜ説明が多く、混乱しがち。
The Generic Section element - HTML: HyperText Markup Language | MDN

そしてアルゴリズムがない現状は、section 内に見出しがあっても嬉しいことはない(意味がないわけではなくて、あるけど何もしてくれない)。
それどころか、ネストされた section に入る h1-6 要素には、芳しくない特徴がある。
例えば、section 内に section がネストされた場合。

html

<section>
  <h1>セクション 1-1 だよ</h1>
  <p>セクション 1-1 の文章だよ</p>
  <section>
    <h1>セクション 1-2 だよ</h1>
    <p>セクション 1-2 の文章だよ</p>
  </section>
</section>

Chrome で、上記のマークアップを確認してみる。
すると、同じ h1 であるにもかかわらず、「セクション 1-1」にネストされた「セクション 1-2」の見出しは、「セクション 1-1」のそれよりフォントサイズが小さくなる。
マークアップでは同じh1を使用しているのに、ネストされたセクションのh1の方がCSSによって小さくなっている

これは UA が下記のような style を持つため。

css

:-webkit-any(article,aside,nav,section) :-webkit-any(article,aside,nav,section) h1 {
    font-size: 1.17em; /* 元の h1 は 1.5em */
    margin-block-start: 1em;
    margin-block-end: 1em;
}

バグだと揶揄されることもあるけど、WHATWG の 15.3.6 Sections and headings をもとにしたもの。
もし、document outline algorithm があれば、この実装は適切だと言える。
でも実際は style が視覚的な変更のみを提供するのみで、あまり健全ではない。

現状、この要素は「ドキュメントまたはアプリケーションの一般的なセクション」を表す曖昧な何かでしかない。
section には見出しを入れるべき」というカーゴ・カルトなマークアップがすべてを隠蔽してきた。
役に立てなかったのは事実なようで、遠回しに section の使用をやめさせようとする、ロックな記事もある。
Why You Should Choose HTML5 article Over section — Smashing Magazine

ただ、アルゴリズム再考の試みや、h 要素の案などもあるので、見捨てるにはまだ早いかも。

hgroup 要素について

おまけに hgroup 要素について。
この要素は HTML 5.1 で廃止されたものの、WHATWG では廃止されていない。
そして、document outline algorithm のためにあるような要素なので、現在は使っても機能しない。

hgroup は、見出しのグループ化をする要素。

html

<hgroup>
  <!-- レベル 1 の見出しとして扱われる(はずだった) -->
  <h1>grgr-dkrkのブログ</h1>
  <h2>備忘録です</h2>
</hgroup>

これは小見出しなどによって、意図しないサブセクションが生成されるのを防ぐためにある。
サブセクションが存在しない今も、小見出しを表現する用途で使われることがある(大抵は div が使われる)。

現在は h1h2 が謎の wrapper に入ってるのとほとんど変わらない。
これが本来の役割を得るとなると、今まで 2 つ以上認識された見出しが 1 つとして扱われることになる。
つまり、見出しアルゴリズムによって、既存ページに影響を及ぼしやすい。
しかも、hgroup はスタイル当ての wrapper として使われることもあるので、余計ややこしい。

近年の動きで触れた Polyfill では hgrouph1 と同等に扱うアプローチだった。
要素に role="heading"aria-level="n" を追加し、h1 の見出しと同じように調整するもの。
しかし下記のような hgroup だと、見出しレベル 1 として扱われてしまう。

html

<h1>grgr-dkrkのブログ</h1>
<main>
  <h2>記事一覧</h2>
  <p></p>
  <!-- aria-level="1" になる -->
  <hgroup>
    <h2>おすすめ記事</h2>
    <h3>厳選しました</h3>
  </hgroup>
  <p></p>
</main>

h1 は sectioning の要で、それが hgroup 要素に入るケースがある以上、h1 相応の扱いをすることは肯けるものの、それだけだとすでにあるユースケースと互換性が保てない。
見出しアルゴリズムにおける hgroup の扱いについては、様々な意見が交わされた。
「小見出しをどう解釈するか」が論点となっている様子。

  1. 子に h1 があれば hgroup 要素そのものを h1 として扱い、それ以外は無視する。
  2. hgroup そのものを無視する。中の見出しと小見出しを含めてそのままにする。
  3. 子から最上位の見出しを aria-level で調整し、他の見出しに generic role を付ける。

特に最後の項目は、メインの見出しレベルのみに調整が及び、後方互換性のメリットはある。
ケースとしては下記のような怪文書が来た時など…。

html

<!-- これが丸ごと h1 になる -->
<hgroup>
  <h1>grgr-dkrkのブログ</h1>
  <h2>いろいろなことを書くブログ</h2>
  <div>
    <a href="https://www.dkrk-blog.net/">リンク</a>
  </div>
  <p>ブログの説明文ですブログの説明文ですブログの説明文ですブログの説明文ですブログの説明文ですブログの説明文ですブログの説明文ですブログの説明文ですブログの説明文ですブログの説明文です</p>
</hgroup>

hgroup をどうするのかはまだわかっていない。
今は使っても意味がないし、アルゴリズムの実装にも支障が出ていて、何かとかわいそうな要素。

終わり

この記事は h1 の乱用や section 要素の使用を批判するわけではない。
意味論というより UA の問題なので、むしろ一概に警鐘を鳴らすものではないと思う。
問題に対しての取り組みがしっかり行われていることや、少しでもダークネスめいた仕様が伝われば、と思ってここに供養した。

資料(ページ名順)