🏷️

正しいHTMLを書こう!

に公開

今日の目標

  • 【MUST】HTML要素を適切に使い分けることの重要性を理解する
  • 【SHOULD】これからのフロント実装の際に、適切にHTML要素を使い分けながら実装できる
    • デザイナーにおかれては、文書構造や要素の役割の違いを意識することでデザインに役立ててもらえると嬉しいです

前半講義、後半で演習のスタイルで行きます!

改めて…HTMLとは?

HyperText Markup Language =ハイパーテキストのマークアップ言語。

ハイパーテキスト…については今回は割愛。
今回は 「マークアップ言語」 が大事なポイントです。

「マークアップ」とは、テキストにマーク、印を付けること。それによってコンピュータ、特にブラウザが意味を理解できるようにすること。

例えばこういうマークアップされていないテキスト(プレーンテキスト)。

プレーンテキスト
りんご
赤いです。
バナナ
黄色いです。

人間が読めば「りんご」に対して「赤い」、「バナナ」に対して「黄色い」と言ってるのがわかりますが、それを機械的に読み取ることはできません。4つ並列かもしれないし、「りんご」に対して「赤いですバナナ黄色いです」かもしれない。

(※自然言語処理技術が発達しているので機械にはできないと言いづらくなってきてますが…、一般化するのはもう少し先だと思うし、どちらにせよマークアップされているデータから読み取る方が確実かつ低コストですよね)

これにマークアップを施すとどうでしょう。

HTMLでマークアップした場合
<h1>りんご</h1>
<p>赤いです。</p>
<h1>バナナ</h1>
<p>黄色いです。</p>

見出しと文章の関係性が機械的に理解できるようになりました! これが「マークアップ」です。

機械で読み取れることを 「機械可読性」「マシンリーダビリティ」 と言います。

HTMLの「正しさ」とは?

主に3つの観点があります。(『HTML解体新書』の分類を引用)

  1. 字句的ルール(構文が正しい)
  2. 語彙的ルール(入れ子構造が正しい)
  3. 意味論的ルール(適切に意味を表している)←今日のメイン

1. 字句的ルール(構文が正しい)

以下の例では、 strong を閉じる前に p タグが閉じてしまっています。

こういうのはそもそもパースに失敗します。

<!--  字句的ルールに違反 -->
<p>ああああ<strong>いいいい</p><p>いいい</strong>ううう</p>

まあ、React(JSX)を使っている限りはこんなミスが発生することはないので、コーディングの際に特に気にする必要はありません。

2. 語彙的ルール(入れ子構造が正しい)

それぞれのHTML要素は仕様書において、入れ子のルール(中に入れていい要素・親にしていい要素)が決まっています。

わかりやすいところで言えば、

  • ul の中には li 以外を入れてはいけない

は代表的な例です。他にも

  • button の中に div を入れてはいけない
  • h1 の中に p を入れてはいけない
  • p の中に p を入れてはいけない

などなど…。

間違った入れ子構造にしている場合、崩れが発生してもおかしくないので気をつけるようにしましょう。
ただし、間違えていたとしても、ブラウザがよしなに表示してくれているので実用上は問題にならないことが多いです。

(参考まで)入れ子構造の機械的チェック — Markuplint

入れ子構造の違反は、ものによってはReactがコンソールエラーを出してくれることもありますが、Reactでバリデーションされているのはごく一部のルールのようで、完全ではありません。

HTML構造が正しいかをチェックするツールとして、MarkuplintというHTMLのリンターが存在します。

https://markuplint.dev/ja/

※Reactも対応しているので導入したいですが、カスタムコンポーネントをたくさん作っていると設定が大変(例えば、MyButtonはbutton要素ですよ、みたいな設定をいちいち書かないといけない)なので、導入に踏み切れていません。

3. 意味論的ルール(適切に意味を表している)

本日のメインです。

HTMLの仕様書ではそれぞれのHTML要素の「意味」「使い方」を定めています。
(単に動作するかどうかだけでなく、人間が読み取る意味に関しても定めがあるのが、プログラミング言語とは大きく違う部分ですね)

以下の例では、見出しであるはずのテキストがp、本文がh1でマークアップされており意味論的に正しくありません。

<!-- 字句的にも語彙的にも正しいが、意味論的に間違っているHTML -->
<p>りんご</p>
<h1>赤いです。</h1>

特定の意味を表すHTML要素を 「セマンティック要素」、適切な意味を表したマークアップを 「セマンティックなマークアップ」 と呼んだりします。

https://developer.mozilla.org/ja/docs/Glossary/Semantics#html_でのセマンティクス

なんで意味論的ルールを守るのが大事?

  1. (元も子もないことを言っちゃうと、)HTMLの仕様書に書いてあるんだから、守るのが当たり前。ルールは守ろう。
  2. アクセシビリティ・ユーザビリティを高められるから。しかもお手軽に。

HTML要素の例と、適切に使うと嬉しいこと 10連発

段落 p 要素

p 要素はparagraphのp、つまり段落、ひとまとまりのテキストを表す要素です。

10:00 〜 12:00 みたいなテキストを表示したいときに、
Chakraで書いていると、単語を Text 、それを横並びにしたいから Flex と書いてないでしょうか?

{/* ありがち例 */}
<Flex>
  <Text>10:00</Text>
  <Text>〜</Text>
  <Text>12:00</Text>
</Flex>

出力後はこういうHTML構造になります。これ、HTMLとしてはNGパターンなのわかるでしょうか?

<!-- NG例 -->
<div style="display: flex;">
  <p>10:00</p>
  <p>〜</p>
  <p>12:00</p>
</div>

「10:00 〜 12:00」でひとまとまりの文です。なので、全体をp、それぞれはspanで囲むべきです。

<!-- OK例 -->
<p>
  <span>10:00</span>
  <span>〜</span>
  <span>12:00</span>
</p>

見た目一緒なんだからいいじゃん、と思うかもしれませんが、
コピーしたときに、1行になるか複数行に分かれてしまうかが変わってきます。

リンク a 要素

a はリンクを表す要素です。

a要素を使わなくても、「クリックして別ページに移動する機能」という意味ではNextだとこう書けてしまいます。

<div onClick={() => router.push("/")}>リンクのような挙動をするdiv</div>

でもaタグにするほうが正しいです。

<a href="/">適切なリンク</a>

a要素だとこんなに嬉しいことが…

  • ホバーしたときにブラウザでリンク先URLを表示できる
  • 右クリックでコンテキストメニューが開ける
    • 新しいタブで開く
    • 新しいウィンドウで開く
    • リンク先アドレスをコピー
  • cmd押しながらクリックで新しいタブで開ける
  • iOSならリンク長押しでプレビューできる
  • キーボードでフォーカスできる・開ける(※divの場合はキーボード操作できないので大問題)
  • スクリーンリーダー(音声読み上げ)で「リンク」と読み上げられる
  • スクリーンリーダーのリンク一覧に表示される
  • 検索エンジンにリンクとして認識され、リンク先のページもインデックスされる

ボタン button 要素

リンク同様、クリックしてなにかが起こるだけなら onClick で書けちゃいます。

しかし button 要素にすることで、以下のメリットがあります。

  • キーボードでフォーカス・実行できる
  • スクリーンリーダー(音声読み上げ)で「ボタン」と読み上げられる

【コラム】cursor="pointer" はほぼ全部間違い

クリック可能な要素をユーザーに識別させるための手がかりとして、 cursor: pointer; (ホバー時に指差しカーソルに変更するCSS)を適用することは有用です。

しかし、実はこれを明示的に指定することは普通ありえません。

というのも、ブラウザのデフォルト設定(ユーザーエージェントスタイルシート)では、(リンクのある) a 要素には cursor: pointer が当たっています。また、 button 要素に対しては、Chakraがデフォルトで cursor: pointer; を指定しています(※v2の場合。v3以降はコンポーネント単位での指定に変わり、素の button 要素には指定されなくなりました)。

そのため、これらのHTML要素を適切に使っていれば、cursor="pointer" という指定は必要ないはずなんです。この記述を見つけたら改善のチャンスかもしれません。

見出し h1-h6 要素で文書構造を表す

<h1>乗り物</h1>
  <h2>陸の乗り物</h2>
    <h3>車</h3>
    <h3>自転車</h3>
  <h2>海の乗り物</h2>
    <h3>船</h3>
  <h2>空の乗り物</h2>
    <h3>飛行機</h3>
  • 検索エンジンに構造が伝わる
    • 特に最近のGoogleは、title要素よりも見出し要素から検索結果に出力する内容を決めていることも多いです。
  • スクリーンリーダーで見出しレベルが読み上げられる
  • スクリーンリーダーで見出しジャンプが使用できる
    • スクリーンリーダーでページを操作する際には、見出しの情報は重要です。というのも、目で見ていれば、スクロールしてページ全体の構成を眺めることができますが、耳からの情報ではそれができません。見出しがあることで、ページ全体の構成を効率よく理解し、目的の情報を探すことができます。
      • 本を読むときに目次を見て目的のページを探すのと同じです。
  • リーダーモード(リーディングモード)でも適切にスタイルが設定されるので、リーダーモードでもまともに読める

コーディングやデザインの際には、「このテキストがhいくつか?」を考えるだけでなく、「ここに見出しが足りないのでは?」も意識できるようになるとベスト!

リストul, ol

ulはun-ordered list(順序なしリスト)、olはordered list(順序つきリスト)。

<ul>
  <li>ひとつめ</li>
  <li>ふたつめ</li>
  <li>みっつめ</li>
</ul>

スクリーンリーダーで項目数が読み上げられます。

h1-h6 のところでも書いた通り、目で見ていればパッと見で3項目が並んでるなーとわかりますが、読み上げを聞いている場合、全部読み上げられるまで何項目あるのかわかりません。

それがul, olで伝わります。

スクリーンショット。リストがフォーカスされており、画面右下に読み上げ内容が表示されている。「リスト3項目」と読み上げられている。

スクリーンショット。リスト内の2個目がフォーカスされており、画面右下に読み上げ内容が表示されている。「ふたつめ、3の2」と読み上げられている。

ulol の使い分けは、「順番が入れ替わっても成立するかどうか」です。

<!-- 時間割は順番に意味があるので ol -->
<h1>今日の時間割</h1>
<ol>
    <li>算数</li>
    <li>国語</li>
    <li>理科</li>
    <li>社会</li>
</ol>

<!-- 単なる羅列で、入れ替わっても意味が変わらないなら ul -->
<h1>今日の宿題</h1>
<ul>
    <li>漢字書き取り</li>
    <li>算数ドリル</li>
    <li>理科のプリント</li>
</ul>

見出し語と内容の対応を表す dl

個人的にはあんまり使うことはない…けど一例として紹介。

単語とそれに対する内容や、キーと値のペアなどを表せる。

<dl>
  <dt>出身地</dt>
  <dd>東京</dd>
  <dt>職業</dt>
  <dd>エンジニア</dd>
  <dt>年齢</dt>
  <dd>20代</dd>
</dl>

ただし、こういう情報は表の見た目にすることが多く、検索エンジンでもtable要素のほうがリッチリザルトになりやすいので個人的には table を優先して使ってあげたい。

スクリーンリーダーのサポートも万全ではない。(改善されるらしいけど何年かかるか… 参考記事

table 要素

  • スクリーンリーダーで構造が読み上げられる
    • 項目を読み上げる時に列見出しまで読んでくれる

      スクリーンショット。表内のセルがフォーカスされており、画面右下に読み上げ内容が表示されている。「行 4/4 図鑑番号 007、列 1/3」と読み上げられている。

    • しかもスクリーンリーダーで「2列目の上下に移動する」などExcelみたいな操作もできるようになる

  • コピペしたときに表の構造を保ってコピペできる。

section 要素

その名の通りセクションを表す。

<section>
  <h2>問題文</h2>
  <p>........</p>
  <p>........</p>
</section>
<section>
  <h2>解答欄</h2>
  <form>....</form>
</section>

実は今のところ具体的なメリットはない…。けど、「開発者リーダビリティ」 を上げるのには多少役立つ。

div ばかりがネストされていると、どの要素がどのコンポーネントで定義されているか分かりにくくなってくるので、セクションの単位でsection要素(など別の要素)になっていると、コードの中で見つけやすくなる。

ランドマークロールを持つ要素たち

header main footer などはデフォルトでランドマークロール(ロールの詳しい話は別の機会に…ここでは割愛)を持つ要素です。

<header>...ヘッダーメニュー...</header>
<main>
  <h1>...</h1>
  <p>...本文...</p>
</main>
<footer>...フッター...</footer>
  • スクリーンリーダーでランドマークロール一覧が表示でき、特定の場所にジャンプできます
    • これがないと、ページを移動するごとに毎回ヘッダーメニューのようなどのページでも表示される要素を読み上げる必要があります。そのページの本文が main になっていると直接飛べます。

スクリーンショット。ランドマークロール一覧が表示されている。

なんの意味も持たない! divspan

ここまで紹介してきた要素たちがテキストの意味を示す役割を持つのに対して、divspan は全く意味を持ちません。

div, span はCSSを当てるためだけ、もしくはなんらかの属性をつけるためだけに使うものであり、意味論的な役割のある要素には使いません。

ただし、「間違ったHTML要素を使うぐらいなら、divにしておいたほうがマシ」 です。
間違った意味を伝えるよりは、何も意味を持たせないほうが、誤解は避けられます。

Chakra UIとHTML

Chakraコンポーネントでは、良くも悪くも、HTMLを意識しなくていいようにできてます。

それぞれのコンポーネントがどの要素で出力されるかが決まっており、デフォルトのコンポーネントでページを作っていれば、基本的には役割に合ったHTML要素で出力されます。

  • Linka
  • Textp
  • Headingh2
  • Boxdiv
  • Griddiv
  • Flexdiv
  • Stackdiv

as props

HTML要素を変更したい場合、 as propsで変更できます。

<Button as="a">ボタンの見た目だけどリンクにしたい</Button>

逆に、見た目が必要ないケースはどうでしょう。

「ボタンだけど、見た目は独自に設定したい…。 Button コンポーネントを使っちゃうと調整しづらいから Box を使っちゃえ!」みたいなケースとか。

<Box as="button"> としてもいいのですが、asを付け忘れたり、付けたとしてもパッと見でなんの要素になっているか認識しにくくなります。

Chakra Factory

そういうときはChakra Factorychakra.button みたいな書き方)を使いましょう。

https://chakra-ui.com/docs/styling/chakra-factory

<chakra.button borderRadius="md" bg="gray.100">
  Chakraのスタイリングが使えるbutton要素
</chakra.button>

HTML要素の使い分けを意識する上では、 chakra.<element> の書き方に寄せていく方針もありかもしれないですね。

Box などを使ってしまうと、 div 要素を使っていることを意識しなくなってしまうので。Boxと書いてもいいところもあえて chakra.div と書くことで、div要素で表示している!ということが明示的になる。)

ここまでのまとめ

  • 適切にHTML要素を使うことで、ブラウザやスクリーンリーダーのサポートを受けやすくなるという具体的なメリットがある。それにより、アクセシビリティやユーザビリティが向上する!
  • Chakra で書く場合、
    • コンポーネントの見た目を活かしたままHTML要素を変えたい場合は as props
    • 見た目要らなくて特定のHTML要素を使いたい場合 chakra.<element> 形式 を使うといい

演習

以下のページをHTML要素を適切に使ってマークアップしてください。

前提として、このHTMLじゃないとダメ!といった絶対の正解はないです!
ただし、妥当な解は存在しますし、これは不適切という間違いも存在します。

お題となるページの画像

内容は、 基本のカレーライス|レシピ|エスビー食品株式会社 を元にしつつ、出題用に調整。

解答例(筆者の想定解)

想定解のページの画像。各エリア・要素に対して、使用するタグが示されている

解説:

  • この例だと、ヘッダーやフッターに当たるものはないので全体を main にしてよさそう。
  • また、レシピがまとまったサイトの1記事なので、 article にしてよさそう。
  • ページのトップレベルの見出しが h1。(h1はページのtitleと一致するイメージ)それ以降は階層が深くなるごとに1つずつ数字を下げる。
  • 通常のテキストは p 。2文あるが、まとめて1段落としてよさそう。
  • 「材料」はuldlなどでもいいが、個人的にはtable派。
    • tableにしようとすると、thead(見出し行)がないことに気づく→見出し行の追加を検討する余地があるかも?(想定解の画像では、「名前」「数量」という見出し行を補っています)
  • sectionは具体的なメリットがないので優先度低め。あってもなくてもよし。
  • 「作り方」は順序つきのリスト!(入れ替わると意味が変わってしまうので順序つき)olを使う。
  • 同じ見た目だとしても、ページ内でアクションが起こる追加ボタンとトップへのリンク(ページ遷移)は別の要素! それぞれbuttonaを使う。
    • もっと区別しやすい見た目・配置になるように、デザインを検討する余地があるかも?

参考書籍

GitHubで編集を提案
株式会社キカガク

Discussion