アクセシブルなフォーム
明確でガイド付きの体験ですべての人が使用できるフォームを作成する
ラベルと関連付け
悪い例
<!-- ❌ 悪い例:ラベルがない -->
<form>
<h4>ログイン</h4>
<!-- 関連するラベルがない -->
<input
type="email"
placeholder="あなたのメールアドレス">
<!-- 関連するラベルがない、オートコンプリートなし -->
<input
type="password"
placeholder="パスワード">
<!-- チェックボックスが関連付けられていない -->
<input type="checkbox">
<span>ログイン状態を保持</span>
<button type="submit">ログイン</button>
</form>良い例
<!-- ✅ 良い例:明示的で関連付けられたラベル -->
<form>
<h4>ログイン</h4>
<!-- for/idで関連付けられたラベル -->
<label for="email-good">メールアドレス</label>
<input
type="email"
id="email-good"
placeholder="example@domain.com"
required
aria-describedby="email-help"
autocomplete="email">
<div id="email-help">
メールアドレスを共有することはありません
</div>
<!-- 指示を含むラベル -->
<label for="password-good">パスワード</label>
<input
type="password"
id="password-good"
required
aria-describedby="password-help"
autocomplete="current-password">
<div id="password-help">
大文字と数字を含む8文字以上
</div>
<!-- 正しく関連付けられたチェックボックス -->
<input type="checkbox" id="remember-good">
<label for="remember-good">ログイン状態を保持</label>
<button type="submit">ログイン</button>
</form>説明: 各フォームフィールドには、明示的で適切に関連付けられたラベルが必要です。プレースホルダーはラベルを置き換えません。
検証とエラーメッセージ
悪い例
<!-- ❌ 悪い例:エラーのアナウンスが不十分 -->
<form @submit.prevent="submitBadForm">
<h4>ニュースレター登録</h4>
<!-- ラベルと入力の関連付けがない -->
<label>名前</label>
<input
type="text"
v-model="badForm.name"
:class="{ 'error': submitted && !badForm.name }">
<!-- 視覚的なエラーのみ -->
<label>メール</label>
<input
type="email"
v-model="badForm.email"
:class="{ 'error': submitted && !isValidEmail(badForm.email) }">
<!-- エラー情報がない -->
<label>年齢</label>
<input
type="number"
v-model="badForm.age"
:class="{ 'error': submitted && badForm.age < 16 }">
<!-- 一般的で役に立たないエラーメッセージ -->
<div v-if="submitted && badFormErrors.length">
❌ エラーを修正してください
</div>
<button type="submit">登録する</button>
</form>良い例
<!-- ✅ 良い例:明確に関連付けられたエラー -->
<form @submit.prevent="submitGoodForm" novalidate>
<h4>ニュースレター登録</h4>
<!-- ナビゲーション付きエラー要約 -->
<div
v-if="submitted && goodFormErrors.length"
role="alert"
aria-labelledby="error-summary-title">
<h5 id="error-summary-title">修正すべきエラー:</h5>
<ul>
<li v-for="error in goodFormErrors" :key="error.field">
<a :href="`#${error.field}-good-form`">
</a>
</li>
</ul>
</div>
<!-- 特定の関連付けられたエラーを持つフィールド -->
<label for="name-good-form">氏名 *</label>
<input
type="text"
id="name-good-form"
v-model="goodForm.name"
required
aria-invalid="submitted && !goodForm.name"
aria-describedby="name-error">
<div
v-if="submitted && !goodForm.name"
id="name-error"
role="alert">
❌ 名前は必須です
</div>
<button type="submit">登録する</button>
</form>説明: エラーは明確にアナウンスされ、関連するフィールドに関連付けられ、修正のための正確な指示を提供する必要があります。
aria-labelledbyを使用したフィールドグループ
悪い例
<!-- ❌ 悪い例:整理されていない構造 -->
<form>
<h4>設定</h4>
<!-- ロジックなしで混在した要素 -->
<div>
<input type="radio" name="contact-bad"
id="email-contact-bad" value="email">
<label for="email-contact-bad">メール</label>
<input type="checkbox" id="newsletter-bad"
value="newsletter">
<label for="newsletter-bad">ニュースレター</label>
</div>
<div>
<input type="radio" name="contact-bad"
id="phone-contact-bad" value="phone">
<label for="phone-contact-bad">電話</label>
<!-- コンテキストのないテキストフィールド -->
<input type="text" placeholder="名前">
</div>
<div>
<input type="checkbox" id="offers-bad" value="offers">
<label for="offers-bad">オファー</label>
<input type="radio" name="contact-bad"
id="sms-contact-bad" value="sms">
<label for="sms-contact-bad">SMS</label>
</div>
<!-- コンテキストのない選択 -->
<select>
<option>選択...</option>
<option>毎日</option>
<option>毎週</option>
</select>
<button type="submit">OK</button>
</form>良い例
<!-- ✅ 良い例:aria-labelledbyを使用した論理的なグループ -->
<form>
<h4>連絡先の設定</h4>
<!-- 個人情報セクション -->
<div role="group" aria-labelledby="personal-info-title">
<h5 id="personal-info-title">
個人情報
</h5>
<label for="name-good">氏名 *</label>
<input
type="text"
id="name-good"
name="name"
required
aria-required="true"
aria-describedby="name-desc">
<span id="name-desc">
通信に表示される氏名
</span>
</div>
<!-- ラジオボタングループ -->
<div
role="radiogroup"
aria-labelledby="contact-method-title"
aria-required="true">
<h5 id="contact-method-title">
希望する連絡方法 *
</h5>
<input
type="radio"
name="contact-good"
id="email-contact-good"
value="email"
required>
<label for="email-contact-good">メール</label>
<input
type="radio"
name="contact-good"
id="phone-contact-good"
value="phone"
required>
<label for="phone-contact-good">電話</label>
</div>
<!-- チェックボックスグループ -->
<div role="group" aria-labelledby="comm-types-title">
<h5 id="comm-types-title">通信タイプ</h5>
<p id="comm-types-desc">
受け取りたい通信のタイプを選択してください
</p>
<input
type="checkbox"
id="newsletter-good"
value="newsletter"
aria-describedby="comm-types-desc">
<label for="newsletter-good">ニュースレター</label>
</div>
<button type="submit">
設定を保存
</button>
</form>説明: 関連するフィールドのグループは、aria-labelledbyを介してタイトルと関連付けて、明確なセマンティック関係を作成する必要があります。特にラジオボタンとチェックボックスの場合。
リアルタイム検証を含む複雑なフォーム
悪い例
<!-- ❌ 悪い例:邪魔な検証 -->
<form>
<h4>アカウント作成</h4>
<!-- 各キーストロークでの即座の検証 -->
<label>ユーザー名</label>
<input
type="text"
v-model="complexBadForm.username"
@input="validateUsernameBad"
:class="{ 'error': usernameBadError }">
<!-- 常に表示/非表示されるエラー -->
<div v-if="usernameBadError">
</div>
<!-- aria-describedbyとの関連付けなし -->
<label>メール</label>
<input
type="email"
v-model="complexBadForm.email"
@input="validateEmailBad"
:class="{ 'error': emailBadError }">
<div v-if="emailBadError"></div>
<!-- 短すぎて役に立たないエラーメッセージ -->
<!-- パスワードマネージャーを支援するオートコンプリートなし -->
<label>パスワード</label>
<input
type="password"
v-model="complexBadForm.password"
@input="validatePasswordBad"
:class="{ 'error': passwordBadError }">
<div v-if="passwordBadError"></div>
<button type="submit">アカウント作成</button>
</form>良い例
<!-- ✅ 良い例:フィードバック付きの配慮された検証 -->
<form>
<h4>アカウント作成</h4>
<!-- 入力ではなくblurでの検証 -->
<label for="username-complex">ユーザー名 *</label>
<input
type="text"
id="username-complex"
v-model="complexGoodForm.username"
@blur="validateUsernameGood"
:class="{ error: usernameGoodError,
success: usernameGoodValid }"
aria-describedby="username-requirements username-feedback"
aria-invalid="!!usernameGoodError"
required>
<!-- 明確な指示 -->
<div id="username-requirements">
<h6>要件:</h6>
<ul>
<li :class="{ valid: complexGoodForm.username.length >= 3 }">
3文字以上
</li>
<li :class="{ valid: /^[a-zA-Z0-9_]+$/.test(username) }">
文字、数字、アンダースコアのみ
</li>
</ul>
</div>
<!-- アクセシブルなフィードバック -->
<div
v-if="usernameGoodError"
id="username-feedback"
role="alert"
aria-live="polite">
❌
</div>
<div
v-else-if="usernameGoodValid"
id="username-feedback"
role="status"
aria-live="polite">
✅ ユーザー名は利用可能です
</div>
<!-- パスワード強度インジケーター -->
<label for="password-complex">パスワード *</label>
<input
type="password"
id="password-complex"
v-model="complexGoodForm.password"
@input="validatePasswordGood"
aria-describedby="password-strength"
autocomplete="new-password"
required>
<div id="password-strength">
<h6>パスワードの強度:</h6>
<div class="strength-indicator">
<div
class="strength-bar"
:class="`strength-${passwordStrength}`"
:style="{ width: `${passwordStrength * 25}%` }">
</div>
</div>
</div>
<button type="submit" :disabled="!isFormValid">
アカウント作成
</button>
</form>説明: 複雑なフォームの場合、リアルタイム検証が役立ちますが、邪魔にならず、アクセシブルな方法で実装する必要があります。