申込や登録のフォームでは、サーバーが正しい値を受け取るだけでは足りません。
利用者が項目の意味を理解し、入力し、誤りを見つけて修正し、最後まで操作できる状態まで設計する必要があります。
この記事では、LaravelのFormRequestとBladeコンポーネントを土台に、サーバー側の検証とアクセシブルな画面表示を結び付けます。
先にWebアクセシビリティの基本と改善の優先順位を確認しておくと、個々の属性を付ける理由も判断しやすくなります。
この記事で扱う範囲
- FormRequestに検証ルールと利用者向けの項目名を集約する方法
- ラベル、補足、エラーをBladeコンポーネントで関連付ける方法
- エラー一覧から修正対象へ迷わず戻れる導線
autocomplete、inputmode、ネイティブ要素を使った入力支援- マルチステップ、下書き保存、二重送信防止の設計判断
- Featureテスト、ブラウザテスト、手動確認の分担
フォームを支える三つの層
アクセシブルなフォームとは、特定の属性を並べたフォームではなく、利用者が目的を理解し、入力し、誤りを直して完了できるフォームです。
Laravelの検証だけでも、画面側のARIA属性だけでも、この一連の操作は成立しません。
| 層 | 担当すること | 不足すると起きること |
|---|---|---|
| 業務ルール | 何を必須とし、どの値を受け付けるかを決める | 画面と保存処理で判定が食い違う |
| HTMLの意味付け | ラベル、説明、項目のまとまりを支援技術へ伝える | 読み上げやキーボード操作で項目の意味を追いにくい |
| フィードバック | 誤りの場所、理由、修正方法、完了状態を伝える | 送信できない理由や次の操作が分からない |
実装では、この三層を別々に作ってから接続します。
FormRequestは業務ルール、BladeはHTMLの意味付け、エラー一覧とフォーカス制御はフィードバックを主に受け持ちます。
実装前に決めるフォームの契約
コードを書く前に、フォームが集める情報と、利用者へ返す応答を短い仕様にします。
会員登録であれば、次の項目を先に決めます。
- 登録を完了するために本当に必要な項目
- 必須項目と任意項目を画面でどう区別するか
- 入力形式をいつ確認し、どの言葉で修正方法を示すか
- 送信に失敗したとき、保持する値と保持しない値
- 中断と再開を許すか、下書きをいつ削除するか
- 成功後に表示する完了状態と次の操作
例えばメールアドレス欄の目的は「文字列を一つ受け取ること」ではなく、「確認連絡に使える形式のアドレスを受け取ること」です。
この目的が決まれば、type='email'、autocomplete='email'、サーバー側のemailルール、エラー文を同じ意図でそろえられます。
同意文や個人情報の保持期間は、画面だけで決めず、サービスの運用方針と必要な確認手続に合わせます。
FormRequestにサーバー側の検証を集約する
FormRequestは、入力の検証とリクエストの認可を一つのクラスにまとめるLaravelの仕組みです。
コントローラへ保存処理だけを残しやすくなり、同じ画面のルールとメッセージを追いやすくなります。
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class RegisterRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:80'],
'email' => ['required', 'email', 'max:255', 'unique:users,email'],
'password' => ['required', 'string', 'min:12', 'confirmed'],
'phone' => ['nullable', 'string', 'max:20'],
'agree' => ['accepted'],
'avatar' => ['nullable', 'file', 'mimes:jpg,jpeg,png,webp', 'max:2048'],
];
}
public function attributes(): array
{
return [
'name' => '氏名',
'email' => 'メールアドレス',
'password' => 'パスワード',
'password_confirmation' => 'パスワード(確認)',
'phone' => '電話番号',
'agree' => '利用規約への同意',
'avatar' => 'プロフィール画像',
];
}
public function messages(): array
{
return [
'password.min' => 'パスワードは:min文字以上で入力してください。',
'agree.accepted' => '利用規約への同意を確認してください。',
];
}
}
rules()は受け付ける値、attributes()はエラー文に表示する項目名、messages()は利用者に補足が必要な文言を担当します。
confirmedはpassword_confirmationとの一致を確認し、acceptedは同意欄として受け付ける値を確認します。
この例のauthorize()がtrueなのは、未ログインの利用者にも開く登録フォームを想定しているためです。
権限が必要な更新画面では、対象レコードと利用者の関係に合わせて認可を実装します。
Laravelは検証失敗時に前の画面へ戻し、エラーと直前の入力をセッションへ渡します。
ただし、パスワードやファイルなど再表示すべきでない値は、画面へ戻さない設計にします。
詳しい動作はLaravel公式のValidationで確認できます。
Bladeでラベル、補足、エラーを関連付ける
Bladeコンポーネントは、入力欄に必要なラベル、補足、エラー表示を再利用できる形にまとめる手段です。
共通化の目的は見た目をそろえることだけではありません。
ラベルのforと入力欄のid、補足とエラーのID参照を、どの画面でも同じ規則で出力することにあります。
@props([
'name',
'label',
'id' => null,
'type' => 'text',
'help' => null,
'required' => false,
'autocomplete' => null,
'inputmode' => null,
])
@php
$id = $id ?? $name;
$error = $errors->first($name);
$descriptionIds = collect([
$help ? $id.'-help' : null,
$error ? $id.'-error' : null,
])->filter()->implode(' ');
@endphp
<div class="mb-5">
<label for="{{ $id }}" class="block font-medium">
{{ $label }}
@if($required)<span>(必須)</span>@endif
</label>
<input
id="{{ $id }}"
name="{{ $name }}"
type="{{ $type }}"
@if($type !== 'password') value="{{ old($name) }}" @endif
@if($required) required @endif
@if($autocomplete) autocomplete="{{ $autocomplete }}" @endif
@if($inputmode) inputmode="{{ $inputmode }}" @endif
@if($descriptionIds) aria-describedby="{{ $descriptionIds }}" @endif
@if($error) aria-invalid="true" @endif
{{ $attributes->class(['w-full border rounded px-3 py-2']) }}
>
@if($help)
<p id="{{ $id }}-help" class="text-sm">{{ $help }}</p>
@endif
@if($error)
<p id="{{ $id }}-error" class="text-sm font-medium">{{ $error }}</p>
@endif
</div>
aria-describedbyには、補足とエラーのIDを空白区切りで指定します。
これにより、入力欄へフォーカスしたときに関連する説明を支援技術が参照できます。
aria-invalid='true'は検証後に誤りがある項目だけへ付け、画面上のエラー文も残します。
ARIA属性は、見えるラベルや具体的なエラー文の代わりにはなりません。
このコンポーネントは文字入力向けです。
ファイル入力やチェックボックス群は、値の復元方法とグループ構造が異なるため、別のコンポーネントに分けます。
コンポーネントの配置と属性の扱いはLaravel公式のBlade Componentsを参照してください。
エラー一覧から修正項目へ戻れるようにする
エラーは、色や枠線だけではなく、どの項目に何が起き、どう直せばよいかをテキストで示します。
長いフォームでは、各項目の直下にエラーを置く一方、フォームの先頭にも一覧を置くと全体を把握しやすくなります。
@php
$fieldIds = [
'name' => 'name',
'email' => 'email',
'password' => 'password',
'agree' => 'agree',
];
@endphp
@if($errors->any())
<div
data-error-summary
tabindex="-1"
aria-labelledby="error-summary-title"
class="mb-6 border p-4"
>
<h2 id="error-summary-title">入力内容を確認してください</h2>
<ul>
@foreach($errors->keys() as $key)
@continue(!isset($fieldIds[$key]))
<li>
<a href="#{{ $fieldIds[$key] }}">
{{ $errors->first($key) }}
</a>
</li>
@endforeach
</ul>
</div>
@endif
サーバーからエラー画面を返した後は、エラー一覧または最初のエラー項目へフォーカスを移します。
document.querySelector('[data-error-summary]')?.focus();
どちらへ移すかは画面内で統一し、ページ内リンクを選んだ後に対象の入力欄へ到達できることを確認します。
配列やネストした項目では、Laravelのエラーキーと実際のidが一致しないことがあります。
例の$fieldIdsのように対応表を用意し、存在しないアンカーを出力しないようにします。
非同期でエラーを差し替える場合は、一つの通知領域にaria-liveを使い、複数のエラーを同時に割り込ませない設計にします。
エラーの伝え方はW3Cのフォーム検証チュートリアルと通知パターンが判断材料になります。
入力支援の属性を目的に合わせる
autocompleteはブラウザに入力内容の目的を伝え、保存済み情報による入力補助を受けやすくします。
inputmodeはスマートフォンなどで表示するキーボードの種類を示すヒントであり、値を検証する機能ではありません。
| 項目 | type |
autocomplete |
inputmode |
|---|---|---|---|
| 氏名 | text |
name |
通常は指定不要 |
| メールアドレス | email |
email |
email |
| 電話番号 | tel |
tel |
tel |
| 郵便番号 | text |
postal-code |
numericを候補にする |
| 住所 | text |
address-line1など項目の役割に合う値 |
通常は指定不要 |
| 新しいパスワード | password |
new-password |
通常は指定不要 |
郵便番号のように記号を許す項目では、表示されたキーボードで必要な文字を入力できるかを実機で確かめます。
自動整形を使う場合も、入力中にカーソル位置を予期せず動かさず、サーバー側で正規化してから保存します。
入力の途中で毎回エラーを出すと、まだ完成していない値まで誤りとして通知されます。
送信時の検証を基本とし、途中で確認する必要がある項目はフォーカスが外れた後など、利用者が入力を終えたと判断できる時点を選びます。
novalidateを付けるとブラウザの対話的な制約検証は働きません。
独自のエラー表示とフォーカス制御を実装して検証できる場合に限って使い、サーバー側の検証は必ず残します。
属性の値と役割はMDNのautocomplete解説、inputmode解説、Constraint Validation APIの解説で確認できます。
ネイティブ要素と複雑な入力部品を使い分ける
日付、時刻、選択肢には、まずネイティブのinputやselectが要件を満たすかを確認します。
ネイティブ要素は名前、状態、キーボード操作をブラウザが提供しますが、表示や操作性は端末によって異なるため、対象環境での確認は残ります。
検索できるコンボボックスや独自のカレンダーが必要なら、role='combobox'を一つ付けるだけでは足りません。
開閉状態、現在の選択、矢印キーとEscキーの動作、フォーカスの移動をWAI-ARIA Authoring Practices Guideと照らして実装し、支援技術で確認します。
ラジオボタンやチェックボックスのまとまりにはfieldsetとlegendを使い、何について選ぶのかをグループ名として示します。
ファイル入力のacceptは選択画面のヒントです。
実際に受け付ける形式と容量はFormRequestでも検証し、失敗時には許可する形式と上限をテキストで示します。
マルチステップと下書き保存の境界を決める
マルチステップフォームは、入力項目を複数の画面に分ける形式です。
項目が多いだけで採用せず、内容を意味のある単位に分けられ、前後の移動や中断への対応が必要なときに使います。
<ol aria-label="登録の進み具合">
<li>1. 基本情報</li>
<li aria-current="step">2. 連絡先</li>
<li>3. 内容確認</li>
</ol>
<p>全3ステップのうち2番目です。</p>
現在の位置はaria-current='step'だけに頼らず、「全3ステップのうち2番目」のようなテキストでも示します。
ステップを切り替えた後は、新しい見出しへフォーカスを移し、戻ったときに以前の入力が残ることを確認します。
下書き保存はLaravelのFormRequestが自動で提供する機能ではありません。
保存先、利用者との対応、期限、削除条件をアプリケーション側で設計します。
| 扱い | 例 | 判断 |
|---|---|---|
| 保存候補 | 氏名、連絡先、選択済みのプラン | 再開に必要な範囲へ絞る |
| 保存しない | パスワード、カードのセキュリティコード | 再入力を求める |
| そのまま復元できない | ブラウザで選択したファイル | 再選択が必要だと説明する |
自動保存の間隔は固定値を先に置かず、通信量、入力の長さ、失われたときの影響を基に決めます。
保存中、保存済み、失敗の状態は、視覚表示と必要に応じた控えめなライブ通知で伝えます。
CSRF、スパム、二重送信を別の問題として扱う
CSRF対策、スパム対策、入力検証は目的が異なります。
一つを導入しても、ほかの対策を省けるわけではありません。
- CSRF対策:状態を変更するWebフォームに
@csrfを入れ、Laravelの保護機構でトークンを確認する - 入力検証:FormRequestで型、長さ、形式、ファイル条件を確認する
- スパム対策:レート制限やハニーポットを、対象フォームの攻撃と誤検知のリスクに合わせて組み合わせる
- 二重送信対策:送信中の状態を伝えてボタン操作を抑え、サーバー側でも同じ処理が重複しないようにする
レート制限の単位や上限を決める方法は、関連記事のLaravelでレート制限とクォータを使い分ける設計で詳しく扱っています。
ログインやパスワード再設定のエラーでは、アカウントの有無を不用意に明かさない文言も検討します。
CSRFトークンの組み込み方はLaravel公式のCSRF Protectionで確認してください。
自動テストと手動確認を分担する
フォームの使いやすさは、一種類のテストだけでは確認できません。
サーバーの規則、ブラウザ内の動作、支援技術での理解を分けて点検します。
| 確認方法 | 主な確認内容 |
|---|---|
| Featureテスト | 必須、形式、一致、同意、ファイル条件、正常時の保存と遷移 |
| Duskなどのブラウザテスト | エラー一覧へのフォーカス、項目リンク、戻る操作、送信中の表示 |
| Pa11yやaxeなどの自動検査 | ラベル不足、参照先のないARIA属性、機械的に判定できる構造上の問題 |
| キーボードによる手動確認 | 論理的な移動順、見えるフォーカス、すべての操作の完了 |
| 読み上げと実機確認 | 項目名、必須状態、補足、エラー、進捗、モバイル用キーボード |
自動検査は、文言が利用者に理解できるか、フォーカス移動が自然かまでは判断できません。
エラーを出した状態で、最初から修正して送信し直す一連の操作を手動でも確認します。
フォームに特化した確認観点は、WCAG 2.2で押さえる入力支援とエラー設計も参照してください。
実装レビューのチェックリスト
- ページ内の見出しと入力項目のまとまりが一致している
- すべての入力欄に見えるラベルがある
- 必須と任意の違いをテキストでも理解できる
- 項目の目的に合う
typeとautocompleteを選んでいる - エラーをテキストで示し、修正方法まで書いている
- エラー一覧のリンク先と入力欄のIDが一致している
- エラー後も再入力すべきでない値以外は保持される
- キーボードだけで入力、修正、送信を完了できる
- 色だけで正常、エラー、必須の状態を区別していない
- 下書きの保存対象、期限、削除条件を決めている
- Featureテストとブラウザテストの両方に失敗経路がある
実装の進め方
最初にFormRequestでサーバー側の規則を確定し、次にネイティブHTMLでラベルと項目のまとまりを作り、最後にエラー一覧とフォーカス制御を接続します。
マルチステップや自動保存は、単一画面で目的を達成できない場合に追加します。
アクセシビリティはARIA属性の数ではなく、エラーが起きても利用者が理由を理解し、自分で修正して完了できるかで評価します。
参考資料
- Laravel Validation
- Laravel Blade Components
- Laravel CSRF Protection
- W3C WAI Forms Tutorial
- W3C Validating Input
- W3C User Notification
- Web Content Accessibility Guidelines 2.2
- WAI-ARIA Authoring Practices Guide
- MDN autocomplete
- MDN inputmode
- MDN Constraint Validation API
- GOV.UK Error summary
- Nielsen Norman Group Error-Message Guidelines
この記事に関連する株式会社greedenの取り組み
フォームの使いにくさは、ラベル、エラー通知、キーボード操作を一つずつ検証すると改善点を見つけやすくなります。株式会社greedenは、Webアクセシビリティの診断から改善提案、実装まで支援しています。
