Chienomi

HTML h1要素に関する誤謬と現状

技術::web

ものすごくよく見かける 「h1要素は複数使ってはならない」 という主張であるが、少なくとも仕様上はこのような事実はなく、誤謬である。

技術的な説明に関しては、誰がいい出したのか不明だが世に広く流通している誤謬、というのは割とある。 これもそのひとつであり、様々なところで様々な人が様々な主張をしているために、その検証は極めて困難なものになっている。

本記事では、この事象について、仕様を読み解き解説すると共に、どのようなことが起きており、実際どうすべきであるのか

定義

HTML4 DTDでは次のようになっている。

<!ENTITY % heading "H1|H2|H3|H4|H5|H6">

実際に使われているところは次の通りだ。

<!--=================== Headings =========================================-->

<!--
  There are six levels of headings from H1 (the most important)
  to H6 (the least important).
-->

<!ELEMENT (%heading;)  - - (%inline;)* -- heading -->
<!ATTLIST (%heading;)
  %attrs;                              -- %coreattrs, %i18n, %events --
  >

つまり、 そもそも定義としてはH1からH6までで何ら区別されていないので、いくつ使おうが、どの順番で使おうが、スキップしようが、これらの見出し要素を置いて良い場所に置く限り自由である。

XHTML1 Strict DTD (XHTML1.1相当。XMLのDTDなのでSGMLのDTDとは書き方に違いがあることに注意)では次のようになっている。少しだけ異なる。

<!--================== Block level elements ==============================-->

<!ENTITY % heading "h1|h2|h3|h4|h5|h6">
<!ENTITY % lists "ul | ol | dl">
<!ENTITY % blocktext "pre | hr | blockquote | address">

<!ENTITY % block
     "p | %heading; | div | %lists; | %blocktext; | fieldset | table">

<!ENTITY % Block "(%block; | form | %misc;)*">

<!-- %Flow; mixes block and inline and is used for list items etc. -->
<!ENTITY % Flow "(#PCDATA | %block; | form | %inline; | %misc;)*">

使われているところは次の通り

<!--=================== Headings =========================================-->

<!--
  There are six levels of headings from h1 (the most important)
  to h6 (the least important).
-->

<!ELEMENT h1  %Inline;>
<!ATTLIST h1
   %attrs;
   >

<!ELEMENT h2 %Inline;>
<!ATTLIST h2
   %attrs;
   >

<!ELEMENT h3 %Inline;>
<!ATTLIST h3
   %attrs;
   >

<!ELEMENT h4 %Inline;>
<!ATTLIST h4
   %attrs;
   >

<!ELEMENT h5 %Inline;>
<!ATTLIST h5
   %attrs;
   >

<!ELEMENT h6 %Inline;>
<!ATTLIST h6
   %attrs;
   >

headingとして定義しているのになぜ個別に書くのか。

ただ、こちらでも数に関しての制約や、順番などに関しては一切書かれていない。

ちなみに、個数や順序に指定があるtable要素の定義は次のようになっている。

<!ELEMENT table
     (caption?, (col*|colgroup*), thead?, tfoot?, (tbody+|tr+))>

一方、h1要素は%headingとしてまとめられ、さらに

<!ENTITY % block
     "p | %heading; | div | %lists; | %blocktext; | fieldset | table">

%blockにまとめられているので、h1だけを区別する要素はない。 %heading

<!ENTITY % button.content
   "(#PCDATA | p | %heading; | div | %lists; | %blocktext; |
    table | %special; | %fontstyle; | %phrase; | %misc;)*">

とボタンの中にも置ける仕様。これはあまり知られていない。

つまり、 元より規格としてはh1からh6要素は%headingとして一律に扱われており、これらを区別する箇所は全くなく、よってHTML5になって許されたという事実もない。

これはh1要素を複数置いてはならない、という言説だけでなく、順序を守らなければならない、スキップしてはならないという言説も(仕様上は)否定される。

HTML5.2でW3Cが言っていること

同じく謎言説なのだが、W3CがHTML5.2では明確にh1要素の複数使用を否定した、というものがある。

だが、そうではない。まず、W3CのHTML5.1や、WHATWGの仕様では次のように解釈される。

<article>
  <h1>First</h1> <!-- 純粋にh1相当 -->
  <section>
    <h1>Second</h1> <!-- h2相当 -->
    <section>
      <h1>Third</h1> <!-- h3相当 -->
    </section>
  </section>
</article>

つまり、入れ子になったセクショニングコンテンツは、見出しレベルがひとつ下がる。

W3CのHTML5.2ではこれをやめるということだ。つまり、

<article>
  <h1>First</h1> <!-- 純粋にh1相当 -->
  <section>
    <h1>Second</h1> <!-- これもh1 -->
    <section>
      <h1>Third</h1> <!-- これもh1 -->
    </section>
  </section>
</article>

として解釈する、という。

だが、そもそもこの解釈の話はどっちも最終的な仕様として含まれていないので、あまり意味のない話だ。

なお、そもそもの話としてsectionはドキュメントの埋め込みに配慮したものであり、セクショニングコンテンツにおいてどのヘッダーレベルから始まるかというのは問題ではない。 なぜならば、埋め込まれたドキュメントの最上位のヘッダーレベルをそのセクションにおける最上位ヘッダーとして解釈されるからだ。

そして、実際にはこのようにドキュメント構造を理解することはないので、現実的にはどこにいようが、ヘッダーレベルによって章立てする必要がある。これは、section要素に埋め込まれるコンテンツであっても、実際にはヘッダーレベルに配慮する必要があることを意味する。

それぞれの主張

h1を複数置くな、という主張をしている人、団体はかなり幅広い。

まず、多くの人が気にしているGoogleだが、Googleの主張は明確に「好きにしろ」である。

MDNはスキップするなという主張で、常にh1から始めろという主張をしている。 一方、h1をページ内で複数回使うことは避けろ、と書いてあるが、参照しているドキュメントに当該記述はなく、h1を複数使用するサンプルもあるためちぐはぐである。 そもそも、スキップするなというのであれば、セクショニングコンテンツは独立したhaeding scopeであるため必ずh1から始めなければならないことになる。

要約すると、

  • セクション内ではh1から始めろよ、スキップするな派
  • ページ内で複数のh1を使うな派

がいるのだが、どちらも明確な根拠はないので、不毛な聖戦になっている、ということだ。 これが統一的ならまだマシで、同一組織内でも派閥があって、結果的にちぐはぐになることがよくある。

繰り返すが、データフォーマット仕様上はどちらも特に必要はない。

有名なHTML5のvalid checkerは複数のh1を置くとエラーにする。 さらに、headingのないセクショニングコンテンツもエラーにする。

実際としては

まず、h1要素をいくつドキュメントに含むかというのは、好きにして良い。 これを制限する根拠がない。

一方、順番に使う、ということは配慮したほうが良い。 Googleは、「使ってはいないが、ユーザーフレンドリーではない」という立場だから、後に否定的な立場をとるかもしれない。実際、上位のヘッダーから順に使うのがドキュメント構造としては適切であるのは確かだ。 セクショニングコンテンツは独自のセクションセマンティクスを生成するので、

<section>
  <h1>記事タイトル<h1>
  <article>
    <h2>最初の章</h2>
    <!-- ... -->
  </article>
  <nav>
    <h2>ナビゲーションのタイトル</h2>
  </nav>
</section>

のようにするのは妥当である。 セクショニングコンテンツがセクショニングコンテンツのアウトラインアルゴリズムをサポートする場合、h2ではなくh1を採用しても良い。 ここでh1を採用すべきでない理由は、当該アルゴリズムがサポートされない場合、記事タイトルと同じレベルの見出しになってしまうからである。つまり、互換性のための安全な記法である、ということになる。

W3Cのドキュメントも参照すると良い。

セクショナルコンテンツはheadingを含んでいなければならない、という主張もよく見るのだが、WHATWGには次のように書かれている。

The section element represents a generic section of a document or application. A section, in this context, is a thematic grouping of content, typically with a heading.

通常headingを伴って、と言っているだけなので、別にrequireでもmustでもない。

仮に最上位ヘッダーから始めることを意識したとして、ヘッダーは空要素にすることが許されている(<h1></h1>のこと)ので、例えヘッダーとして書くべきことがなかったとしてもヘッダーレベルの順序は保つことができる。

HTMLでは次の通り、0個以上のインライン要素を含むことができる。

<!ELEMENT (%heading;)  - - (%inline;)* -- heading -->

XHTMLではヘッダー要素の定義で許されているわけではないが

<!ELEMENT h1  %Inline;>

%Inline自体が全体を省略可能にしている。

<!ENTITY % Inline "(#PCDATA | %inline; | %misc.inline;)*">

まとめよう。

  • ページ中にh1をひとつだけにし、高いヘッダーレベルから順にスキップすることなく使用し、セクショニングコンテンツ内にheading要素を置くことは無用な諍いを避ける 平和的な振る舞いである
  • しかし、それを強要する人に対しては拒否できるし、そうしないことを無知だと罵る人に対しては反撃できる
  • セクショニングコンテンツがネストしたセクションを生成することにはあまり期待しないほうが良い。スコープは分かれるがアウトラインはそれに従って生成されない
  • CSS的には、セクショニングコンテンツ内のヘッダー要素がシフトされて適用されるわけではない。そのような場合はarticle h3, article section h2, article section section > h1のようにセレクタを書く必要がある。
  • 同一セクションセマンティクス内の同一レベルのheading要素は見出しとしてのレベルが揃うように設定すべきである
  • 文書構造としてヘッダーレベルの順序が保たれていることは望ましく、そのような構造にすべきである。もしも複数の見出しが後続する見出しよりも小さなセクションとして存在するが、現在のセクションに対して適切な見出しコンテンツを設定することができないのであれば、内容が空のheading要素を設定すれば良い
  • いずれにせよユーザーフレンドリーに書くべきであり、それを念頭に置いて望むように書けば良い。そして少なくとも、Googleはそれを妨げない

ちなみにChienomiは?

つい先日まで常にh1から始めるスタイルだったが、環境によってつかみづらい(セクションセマンティクスを理解しないユーザーエージェントがh1を過剰に強調してしまう)という問題が発生したため、h2からはじめるように改めている。 そのほうがユーザーフレンドリーだという判断からだ。

さらなるヒント: Pandocにおけるsection

Pandoc Markdownでは-t html5のとき、

::: {.column}
とあるコラムの内容です
:::

<div class="column">
<p>とあるコラムの内容です</p>
</div>

と生成するが、

::: {.chap}
# コラム {#Column1}
:::

である場合、

<section id="Column1" class="chap">
<h1>コラム</h1>
</section>

と生成する(idの位置に注目)