サイトアイコン IT & ライフハックブログ|学びと実践のためのアイデア集

Laravelで作るアクセシブルなフォーム:FormRequest、エラー表示、入力支援、マルチステップ設計

php elephant sticker

Photo by RealToughCandy.com on Pexels.com

申込や登録のフォームでは、サーバーが正しい値を受け取るだけでは足りません。

利用者が項目の意味を理解し、入力し、誤りを見つけて修正し、最後まで操作できる状態まで設計する必要があります。

この記事では、LaravelのFormRequestとBladeコンポーネントを土台に、サーバー側の検証とアクセシブルな画面表示を結び付けます。

先にWebアクセシビリティの基本と改善の優先順位を確認しておくと、個々の属性を付ける理由も判断しやすくなります。

この記事で扱う範囲

フォームを支える三つの層

アクセシブルなフォームとは、特定の属性を並べたフォームではなく、利用者が目的を理解し、入力し、誤りを直して完了できるフォームです。

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対策、スパム対策、入力検証は目的が異なります。

一つを導入しても、ほかの対策を省けるわけではありません。

レート制限の単位や上限を決める方法は、関連記事のLaravelでレート制限とクォータを使い分ける設計で詳しく扱っています。

ログインやパスワード再設定のエラーでは、アカウントの有無を不用意に明かさない文言も検討します。

CSRFトークンの組み込み方はLaravel公式のCSRF Protectionで確認してください。

自動テストと手動確認を分担する

フォームの使いやすさは、一種類のテストだけでは確認できません。

サーバーの規則、ブラウザ内の動作、支援技術での理解を分けて点検します。

確認方法 主な確認内容
Featureテスト 必須、形式、一致、同意、ファイル条件、正常時の保存と遷移
Duskなどのブラウザテスト エラー一覧へのフォーカス、項目リンク、戻る操作、送信中の表示
Pa11yやaxeなどの自動検査 ラベル不足、参照先のないARIA属性、機械的に判定できる構造上の問題
キーボードによる手動確認 論理的な移動順、見えるフォーカス、すべての操作の完了
読み上げと実機確認 項目名、必須状態、補足、エラー、進捗、モバイル用キーボード

自動検査は、文言が利用者に理解できるか、フォーカス移動が自然かまでは判断できません。

エラーを出した状態で、最初から修正して送信し直す一連の操作を手動でも確認します。

フォームに特化した確認観点は、WCAG 2.2で押さえる入力支援とエラー設計も参照してください。

実装レビューのチェックリスト

実装の進め方

最初にFormRequestでサーバー側の規則を確定し、次にネイティブHTMLでラベルと項目のまとまりを作り、最後にエラー一覧とフォーカス制御を接続します。

マルチステップや自動保存は、単一画面で目的を達成できない場合に追加します。

アクセシビリティはARIA属性の数ではなく、エラーが起きても利用者が理由を理解し、自分で修正して完了できるかで評価します。

参考資料

フォームの使いにくさは、ラベル、エラー通知、キーボード操作を一つずつ検証すると改善点を見つけやすくなります。株式会社greedenは、Webアクセシビリティの診断から改善提案、実装まで支援しています。

モバイルバージョンを終了