HTML
HTML living Standardに沿ってマークアップします。HTML5以前のコーディングが必要となる場合は本ガイドラインは対象外となります。
制作および納品
- コーディングスタイル全般も確認してください
- コーディング環境内でMarkuplintの設定に適合させてください
- CI(継続的インテグレーション)やnpm-scriptsとしてMarkuplintまたはそれに準ずる静的テストを設定しないことはNGです
- どうしてもゆるいルールでコーディングする必要がある場合はプロジェクト内でMarkuplintの設定を変更します
- テンプレートエンジンやフレームワークを利用する場合は開発ソースに対してMarkuplintやBiome(ESLint)で静的テストを行えるようにします
- システム組み込みなど納品した生のHTMLを第三者が触る可能性があるプロジェクトでは納品ファイルに対してフォーマッターを適用させます
- HTMLに対するフォーマッターは改行などの設定が煩雑にならない様、Prettierよりもjs-beautifyを推奨します
- フォーマッタの改行規則は記述コンテンツ(phrasing content)以外は改行するようにします
- コンポーネントベースでマークアップを管理します。複数回登場するものは適切に管理できるよう、コンポーネントのマークアップに似ているのに、ユニークになるスニペットを作らない様にします
- 閉じタグは省略しません
メタ情報(head内コンテンツ)
時流に沿った最適なhead内コンテンツを作成します。そのうえで、クライアントが必要とするOGPやcanonicalなどのSEO設定があれば反映させます。
参考リソース:html5-boilerplate
テンプレートエンジンやフレームワークを使用する際は、ページコーディング時のコピーアンドペーストによるミスなどを防げる様に、メタデータを一元管理し、ビルド時に挿入出来るようにします。
title要素
titleはページ名、親カテゴリ名、サイト名の順に、「半角スペース」+「|」(半角縦棒)+「半角スペース」で区切ります。
<!-- titleの例 --><title>採用について | 企業方針 | 株式会社モノサス</title>viewport
ユーザー自身でスケールを変更できるようにします。
<meta name="viewport" content="width=device-width">スタイルとスクリプト
以下の順序で記述します。
- カスケードレイヤーの順序を規定した
style要素 - CSSファイル
- JavaScriptファイル
CSSファイルは印刷用ではないものはmedia="screen"を付与します。
JavaScriptは原則type="module"を付与します。
<!-- スタイルとスクリプトの例 --><head> <!-- ... --> <style>@layer reset,tokens,base,layout,components,contents,page,operational;</style> <link rel="stylesheet" href="/assets/css/base.css" media="screen" /> <link rel="stylesheet" href="/assets/css/ui.css" media="screen" /> <link rel="stylesheet" href="/assets/css/operational.css" media="screen" /> <script src="/assets/js/entry.js" type="module"></script></head>コンテンツマークアップ
-
見出し、リンク、ボタン、リスト、順序リスト、テーブル、強調を正しく使用します
-
半角カタカナは使用しません
-
body内にメタデータは記述しません -
コンテンツ内の各セクションはアンカーリンク出来るように適切な
id属性を付与します- コンテンツ内の各セクション直下の見出しタグ(主にh2ないしh3)に
id属性を付与します- セレクターのイメージ→
main > section > h2 or h3
- セレクターのイメージ→
- セクション直下の見出しに付与する
id名は直下の見出しが短い場合はその英単語、複雑な見出しがある場合はsection01とします。アンカー用のid属性の命名パターンはサイト内で統一します section要素にaria-labelledbyを付与し、直下見出しのidを指定することでsection要素がランドマークとして登録され、コンテンツの中身がスクリーンリーダーなどでわかりやすくなります。タイトルの装飾がリッチなデザインであれば、情報の強度をスクリーンリーダーでも強めるために積極的に導入してください。本文のセクションが少ない場合は、ランドマークを余計に増やすことになるためこの対応は不要です
- コンテンツ内の各セクション直下の見出しタグ(主にh2ないしh3)に
-
キーボード操作で快適にコンテンツへ到達できるように、
main(本文コンテンツ)以外の複数のランドマークを使用する場合は、最初にフォーカスされるランドマーク(多くの場合headerと思われる)の先頭にコンテンツスキップのためのアンカーリンクを記述します<body><header><a class="visually-hidden" href="#main">本文へ</a><!-- headerのその他の要素を省略 --></header><main id="main"><!-- ... --></main><!-- ... --></body> -
改行について、スクリーンリーダーで不格好な読み上げとならないように、
<br>を使用する場合はaria-hidden="true"を付与します。段落として扱われる場合はp要素を複数使用することで再現します。デバイスによる改行位置の出し分けが必要となる場合は対象箇所をspan要素などで区切り、CSSで調整します<!-- ⛔Bad --><p>改行<br>されたテキスト</p><!-- 👍Good --><p>改行<br aria-hidden="true">されたテキスト</p><!-- または --><div><p>改行</p><p>されたテキスト</p></div> -
複数の
nav要素を使用する場合はaria-labelまたはaria-labelledbyを使用しそれぞれのナビゲーションを区別できるようにします。その場合aria-label内に「ナビゲーション」という単語は不要です- 例えばヘッダー内でナビゲーションとしてマークアップしているリンク集と同一のものがフッターにある場合はフッターでは
nav要素を使用する必要はありません
- 例えばヘッダー内でナビゲーションとしてマークアップしているリンク集と同一のものがフッターにある場合はフッターでは
-
サイト共通で使用されるページ下部の要素(著作権データ、リンク集など)は
footer要素に内包します -
タグ内の属性値は以下の順で記述します
id属性class属性data属性以外の各種属性data属性
-
デバイスによる出し分けのために同一コンテンツやブロックを複数記述することは開けてください。どうしてもデバイス出し分けの対応必要な場合、デザインの修正で考慮可能かデザイナーやクライアントに相談します
-
divスープ(連続した
div要素)は避けてください。フレックスボックスやグリッドレイアウトのためにマークアップを増やす必要はありません- divスープによってHTMLファイルの容量とDOMの深さが不必要に増えます
- divスープによってマークアップの可読性が下がります
- divスープによって杜撰なCSSが生み出されます
-
複雑なDOM構造とならないように、よく設計してシンプルで浅いマークアップを推奨します
⛔Bad
<footer class="mt-32 flex-none"> <div class="sm:px-8"> <div class="mx-auto w-full max-w-7xl lg:px-8"> <div class="border-t border-zinc-100 pb-16 pt-10 dark:border-zinc-700/40"> <div class="relative px-4 sm:px-8 lg:px-12"> <div class="mx-auto max-w-2xl lg:max-w-5xl"> <div class="flex flex-col items-center justify-between gap-6 sm:flex-row"> <div class="flex flex-wrap justify-center gap-x-6 gap-y-1 text-sm font-medium text-zinc-800 dark:text-zinc-200"> <a class="transition hover:text-teal-500 dark:hover:text-teal-400" href="/about">About</a> <a class="transition hover:text-teal-500 dark:hover:text-teal-400" href="/projects">Projects</a> <a class="transition hover:text-teal-500 dark:hover:text-teal-400" href="/speaking">Speaking</a> <a class="transition hover:text-teal-500 dark:hover:text-teal-400" href="/uses">Uses</a> </div> <p class="text-sm text-zinc-400 dark:text-zinc-500">© © somebody, All Rights Reserved </p> </div> </div> </div> </div> </div> </div> </footer>👍Do this!
<footer> <ul> <li><a href="/about">About</a></li> <li><a href="/projects">Projects</a></li> <li><a href="/speaking">Speaking</a></li> <li><a href="/uses">Uses</a></li> </ul> <p> © somebody, All Rights Reserved </p></footer>img、picture、画像メディア
imgタグには以下の属性を付与します。
src:ルート相対パスを推奨、CDNやどうしても外部リソースで記述する場合はhttps:~のフルパスalt:後述width:CSSよりも先におおよその画角をブラウザへ伝えるために使用しますheight:CSSよりも先におおよその画角をブラウザへ伝えるために使用しますdecoding="async":非同期でのデコーディングとします。loading:ファーストビューに含まれる場合はeagerそれ以外はlazyとします
data-srcなどの方法で遅延読み込みをさせないでください。パフォーマンスに影響がでます。現在のブラウザに対して画像遅延読み込みのための追加のJavaScriptは不要です。
ページ内に膨大に画像を挿入する必要がある場合、decoding属性やloading属性がうまく機能せず、読み込みやスクロールに不具合がでる可能性があります。その場合は、属性を削除するなど調整をしつつ実機でよく確認してください。
画像のalt(代替テキスト)
前提としてコンテンツ所有者がaltの決定権を持ちます。ただしマークアップのプロは多様な現場でaltテキストを入力してきています。その経験と以下のリソースをもとにコンテンツ所有者をサポートしましょう。
代替テキスト考慮のための参考リソース
可能な場合は生成AIを使用し画像説明テキストを出力することも推奨します。必ずあなたの認識とずれていないか、画像内テキストはすべて網羅されているかチェックしてください。
alt=""は実際的には稀なケースです。意味をなさない(見いだせない)画像であっても情報の平等性からはスクリーンリーダーでも「そこになにがあるか」を伝えることが望ましいです。
画像のアートディレクション
デバイスごとに画像を出し分ける場合。pictureタグを使用することを検討できます。
<picture> <source media="(min-width: 40.01rem)" srcset="/path/top/large-device/image.webp" type="image/webp" width="1280" height="568"> <img loading="eager" src="/path/top/small-device/image.webp" alt="メインビジュアルの代替テキストが入力される" decoding="async" width="495" height="238"></picture>もし表示のラグが出る場合はpictureタグによってHTMLのパースが停止され、その他のリソースのダウンロードなど適切なレンダリングの流れになっていない可能性があります。ブラウザの検証ツールを確認してダウンロード、パース、ペイントの流れがおかしくなっていないか確認します。
参考リソース: Webブラウザのもう一つのパーサ: Preload Scanner | PerfData
table、表
データの集合などを表として表す場合はする場合はtableタグを使用します。
tableの注意点
- 原則
caption要素を用意し表がどのようなものであるかをテキストで表現できるようにしましょう theadやtbodyを使用しコード上から表の構造のまとまりが把握しやすくしましょうtheadやtbodyの閉じタグは省略しません
- CSSの
display要素はinitialのものを使用してください(表として表示させる場合にdisplay:gridなどに変更しないでください) thにはscope属性を付与します
tableを実装するすべての開発者がMDNのHTML tablesを読むべきです。
- HTML 表 - ウェブ開発を学ぶ | MDN
- HTML の表の基本 - ウェブ開発を学ぶ | MDN
- HTML 表の高度な機能とアクセシビリティ - ウェブ開発を学ぶ | MDN
- 惑星データの構造化 - ウェブ開発を学ぶ | MDN
またHTML Living Standardの4.9 テーブルデータも確認しましょう。
インタラクションを含む(または誘発する)UI(ユーザーインターフェース)
popover属性やdialog,detailsなどモダンHTMLで投入されている要素を積極的に使用しJavaScriptに依存しないインタラクションを実現します。(少なくとも開閉や出現がJavaScriptレスで行われるようにします。)
また、メガメニューなど複雑化されたUIをコーディングする場合は、Patterns | APG | WAI | W3Cやアクセシビリティを考慮しているフレームワークライブラリなどを参考にマークアップします。
本ガイドラインのUI(準備中)にアクセシビリティや汎用性を考慮したUIの実装例と考え方を随時追加していきます。参考にしてください。
動的なUI
template要素を使用する
ニュース一覧などAPIから動的に挿入されるコンテンツは<template>を使用し、JavaScriptファイル内で複雑なDOMを組み立てることなく汎用性を担保します。
<template id="news-template"> <li class="news-item"> <a href="#" class="news-link"> <time class="news-date"></time> <span class="news-title"></span> </a> </li> </template>
<ul id="news-list" aria-busy="true"></ul>async function fetchNews() {try { const response = await fetch('news.json'); const newsData = await response.json();
const template = document.getElementById('news-template'); const newsList = document.getElementById('news-list');
newsData.forEach(news => { const clone = template.content.cloneNode(true); clone.querySelector('.news-title').textContent = news.title; clone.querySelector('.news-date').textContent = news.date; clone.querySelector('.news-link').href = news.link; newsList.appendChild(clone); });
// ニュースの読み込みが完了したら、aria-busyをfalseに設定 newsList.setAttribute('aria-busy', 'false');
} catch (error) { console.error('ニュースの取得に失敗しました:', error);}}
fetchNews(); [ { "title": "ニュース1", "date": "2023-10-01", "link": "#", "content": "これはニュース1の内容です。" }, { "title": "ニュース2", "date": "2023-10-02", "link": "#", "content": "これはニュース2の内容です。" }, { "title": "ニュース3", "date": "2023-10-03", "link": "#", "content": "これはニュース3の内容です。" } ]フォーム
form要素を用いたHTMLマークアップについて記載しています。
labelのfor属性とinput、selectなどのid属性を関連付けますid属性はtext-first-name,check-emailなどと{入力形式}-{内容}という形にします
- 入力箇所についての説明(入力ルールやエラー文言)については
id属性を設定し、入力要素側のaria-describedby属性に対象のidを設定します- 設定が複数の場合は半角スペースで区切ります
- 自動補完を使わない場合のみ
autocomplete="off"を設定します(例:パスワード変更の新パスワード入力部分など) name属性を正しく使用し自動補完が機能するようにします
参考リソース:HTML 属性: autocomplete - HTML: ハイパーテキストマークアップ言語 | MDNselectタグ内の最初のoptionのvalueは空白ではなく”未選択”または”選択されていません”と明示しますcheckboxやradioは<fieldset>で囲いグループ名を<legend>でマークアップします- 必須項目には
required属性を付与し、ラベルなどについているテキストとしての「必須」などの情報は<span aria-hidden="true">必須</span>とします - バリデーションの要約は
formの上部に記述し、role="status"を付与します - Eメール、URL、電話番号などの入力要素には
inputmode="email"など正しいinputmode属性を使用します
参考リソース:inputmode - HTML: ハイパーテキストマークアップ言語 | MDN - 送信ボタンは
formの中に内包してください。マークアップ順は最後になるようにします
フォームの参考
<section id="section-validation" role="status"> 以下の0件のエラーを修正してください:</section><form action="#" method="post"> <div class="input-group"> <label for="text-first-name"> 名 <span aria-hidden="true">(必須)</span> </label> <div class="help-text" id="help-first-name"> 最低2文字 </div> <div class="error-message" id="error-first-name"> (名のエラーメッセージ) </div> <input type="text" id="text-first-name" aria-describedby="error-first-name help-first-name" name="given-name" required > </div>
<div class="input-group"> <label for="text-email"> メール <span aria-hidden="true">(必須)</span> </label> <div class="help-text" id="help-email"> 例: my@email.com </div> <div class="error-message" id="error-email"> (メールのエラーメッセージ) </div> <input type="email" id="text-email" aria-describedby="error-email help-email" name="email" inputmode="email" required > </div>
<div class="input-group"> <label for="text-password" > パスワード <span aria-hidden="true">(必須)</span> </label> <div class="help-text" id="help-password"> 8文字以上で、少なくとも1つの数字、1つの大文字、1つの特殊文字を含む必要があります </div> <div class="error-message" id="error-password"> (パスワードのエラーメッセージ) </div> <input type="password" id="text-password" aria-describedby="error-password help-password" name="new-password" autocomplete="off" required> </div>
<div class="input-group"> <label for="text-confirm-password" > パスワードの確認 <span aria-hidden="true">(必須)</span> </label> <div class="help-text" id="help-confirm-password"> パスワードと一致する必要があります </div> <div class="error-message" id="error-confirm-password"> (パスワード確認のエラーメッセージ) </div> <input type="password" id="text-confirm-password" aria-describedby="error-confirm-password help-confirm-password" name="new-confirm-password" autocomplete="off" required> </div>
<div class="input-group"> <label for="select-favorite-color">好きな色</label> <div class="error-message" id="error-favorite-color"> (好きな色のエラーメッセージ) </div> <select id="select-favorite-color" name="select-favorite-color" aria-describedby="error-favorite-color" required> <option value="">[未選択]</option> <option value="option-red">赤</option> <option value="option-blue">青</option> <option value="option-yellow">黄</option> <option value="option-green">緑</option> <option value="option-orange">オレンジ</option> <option value="option-brown">茶色</option> <option value="option-purple">紫</option> <option value="option-black">黒</option> <option value="option-other">その他</option> </select> </div>
<div class="input-group"> <fieldset> <legend>通知の設定 <span aria-hidden="true">(任意)</span></legend> <label><input type="checkbox" id="check-email" name="notification[]" value="email" required> メール</label> <label><input type="checkbox" id="check-telegram" name="notification[]" value="telegram"> テレグラム</label> <label><input type="checkbox" id="check-carrier-pigeon" name="notification[]" value="pigeon"> 鳩便</label> </fieldset> </div>
<div class="input-group"> <fieldset aria-describedby="error-marital-status"> <legend>婚姻状況 <span aria-hidden="true">(必須)</span></legend> <div class="error-message" id="error-marital-status"> (婚姻状況のエラーメッセージ) </div> <label><input type="radio" name="color" value="single" required> 独身</label> <label><input type="radio" name="color" value="married" aria-describedby="error-marital-status"> 既婚</label> <label><input type="radio" name="color" value="divorced"> 離婚</label> <label><input type="radio" name="color" value="widowed"> 死別</label> </fieldset> </div>
<button id="button-create-account">アカウント作成</button>
</form>参考リソース: