Reactでアクセスしやすく使いやすいフォームを作成する方法
ウェブアプリケーションを設計する際、「あなたのウェブサイトはどの程度アクセスしやすいですか?」や「最高のユーザーエクスペリエンスを提供していますか?」という問いによく直面します。これらの質問は非常に重要ですが、多くの場合、見た目が派手な機能に優先順位が置かれてしまい、サイトの対象ユーザーが減少してしまいます。
Reactでアクセスしやすく使いやすいフォームを作成する方法
ウェブアプリケーションを設計する際、「あなたのウェブサイトはどの程度アクセスしやすいですか?」や「最高のユーザーエクスペリエンスを提供していますか?」という問いによく直面します。これらの質問は非常に重要ですが、多くの場合、見た目が派手な機能に優先順位が置かれてしまい、サイトの対象ユーザーが減少してしまいます。
この記事では、React Hook Formライブラリ、HTML属性、および開発上の考慮事項について説明します。これにより、以下の点に焦点を当てて、すべてのユーザーがサイトを利用できるようにします。
- 視覚障碍者 (スクリーンリーダーを使用している可能性あり)
- ユーザーフィードバックの改善
- すべてのユーザーに対する視覚的な合図
- すべてのユーザーに対する設計上の考慮事項
前提条件
- Reactの知識
- TypeScriptとHTML / JSXの作成の知識
- Tailwind CSSの知識 (本チュートリアルに必須ではありません)
目次
- 初期の基本的なフォーム
- React-Hook-Formでのエラー処理
- useFormメソッドをフォームに接続する
- エラーメッセージの表示
- aria-requiredの追加
- fieldsetとlegendの追加
- ラベルの追加とhtmlForの使用
- プレースホルダーのみに頼らない!
- aria-describedByで追加情報を提供する
- 重要な情報にはツールチップを使用しない
- 重要な情報をお知らせください
- フォーカス状態と配色
- 説明的なボタンを作成する
- 最後に
初期の基本的なフォーム
現在の状態のフォームを見ると、問題ないと思われるかもしれません。しかし、実際にはアクセスしやすくなく、優れたユーザーエクスペリエンスを提供するものでもありません。
import { TvIcon } from "@heroicons/react/24/outline";
type FormData = {
fullName: string;
email: string;
password: string;
confirmPassword: string;
agreeToTerms: boolean;
};
export const RegistrationForm = () => {
const onSubmit = () => {
alert( `Form submitted`);
};
return (
<div className= "flex justify-center items-center w-screen h-screen bg-gray-900" >
<div className= "w-full max-w-md p-8 bg-black bg-opacity-75 rounded-lg" >
<div className= "flex flex-row justify-center items-center gap-x-4" >
<TvIcon className= "h-12 w-12 text-white" />
<h1 className= "text-7xl font-bold text-center text-red-600 mb-4" >Getflix</h1>
</div>
<h2 className= "text-3xl font-bold text-white mb-6 text-center" >
Sign Up
</h2>
<form onSubmit={onSubmit} className= "space-y-6" >
{ /* Full Name */}
<div>
<input
type = "text"
placeholder= "Full Name"
className= "w-full p-3 rounded bg-gray-700 text-white placeholder-gray-400 "
/>
</div>
{ /* Email */}
<div>
<input
type = "email"
placeholder= "Email Address"
className= "w-full p-3 rounded bg-gray-700 text-white placeholder-gray-400 "
/>
</div>
{ /* Password */}
<div>
<input
type = "password"
placeholder= "Password"
className= "w-full p-3 rounded bg-gray-700 text-white placeholder-gray-400"
/>
</div>
{ /* Confirm Password */}
<div>
<input
type = "password"
placeholder= "Confirm Password"
className= "w-full p-3 rounded bg-gray-700 text-white placeholder-gray-400 "
/>
</div>
{ /* Agree to Terms */}
<div className= "flex items-center text-gray-400 text-sm" >
<input
type = "checkbox"
id= "agreeToTerms"
className= "mr-2"
/>
<label htmlFor= "agreeToTerms" className= "select-none" >
I agree to the Terms and Conditions
</label>
</div>
{ /* Submit */}
<button
type = "submit"
className= "w-full py-3 bg-red-600 hover:bg-red-700 text-white rounded font-semibold transition"
>
Sign Up
</button>
</form>
</div>
</div>
);
};
フォームの何が問題なのか?
- アクションに対するフィードバックの欠如:ユーザーフィードバックがないということは、ユーザーがアクションが発生したかどうかについて混乱する可能性があることを意味します。エラーメッセージやフィードバックが表示されないため、ユーザーはフォームを修正するために何をする必要があるかを理解できません。
- フォーム入力のラベルがない:フォーム入力のラベルがないと、スクリーンリーダーはその目的を理解できません。一部のスクリーンリーダーはプレースホルダーを見逃す可能性があり、ユーザーが入力するとプレースホルダーが置き換えられ、コンテキストが失われ、誤りのある入力に戻ることが困難になります。
- アクセシビリティマークアップの欠如:フォームをスクリーンリーダーおよびアクセシビリティツールに合わせて最適化するためのアクセシビリティマークアップがありません。
では、どのように改善すればよいのでしょうか?早速見ていきましょう。
React-Hook-Formでのエラー処理
フォームのエラー処理は、フォーム送信フローの重要な側面です。エラー処理がないと、プロセスは混沌として、ユーザーにとって不満がたまるものになります。役立つエラーメッセージを追加することで、この不満を軽減できます。
Reactでフォームを扱うための一般的なライブラリは、React Hook Formライブラリです。GitHubの統計によると、140万人以上のユーザーが使用しています。まだインストールしていない場合は、インストールしてください。
npm install react-hook-form
次に、useForm()フックを使用して、react-hook-formパッケージから必要な基本的な関数を実装します。
// define our type structure to use within the form
type FormData = {
fullName: string;
email: string;
password: string;
confirmPassword: string;
agreeToTerms: boolean;
};
// basic usage of `useForm()`
const {
register,
handleSubmit,
watch,
formState: { errors },
} = useForm<Inputs>()
簡単な説明
- register: React Hook Formの重要な概念の1つは、コンポーネント/ HTML要素を「登録」することです。これは、フォームの検証時とフォームの送信時の両方で、要素の値にアクセスできることを意味します。
- handleSubmit: これは、フォームを送信し、検証を実行し、その他の構成済みチェックを実行するために必要な主要な関数です。最大2つの引数を取ることができます。
handleSubmit(onSuccess)
– フォームの送信が有効であり、正常に送信できる場合に呼び出されます。handleSubmit(onSuccess, onFail)
– ここでは、handleSubmit()メソッドに2つの関数を渡すことができます。最初に、React Hook Formがフォームが有効であると判断し、続行できる場合に実行されます。2番目に、フォームでエラーが発生した場合に呼び出されます。これは、検証または別の条件による可能性があります。
- watch: watchは、指定された要素の変更を監視し、その値を返す関数です。たとえば、入力要素を監視している場合に、ユーザーの入力をリアルタイムで出力したり、別の要素を定義済みの値に対して検証したりできます。良い例は、前のパスワードフィールドと一致する確認パスワードです。
- formState: これは、フォームに関する情報を保持するオブジェクトです。formStateオブジェクトは、フォームの状態(変更された入力があるかどうか、すべての検証に合格したかどうかなど)を追跡します。
isDirty
– ユーザーが何らかの入力を変更した場合にtrueisValid
– フォームがすべての検証に合格した場合にtrueerrors
– フィールドごとの検証エラーを保持するオブジェクトisSubmitting
– フォームの送信中にtrue(ローディングスピナーを表示するのに役立ちます)isSubmitted
– フォームの送信後にtruetouchedFields
– ユーザーが操作したフィールドdirtyFields
– ユーザーが変更したフィールド
formStateオブジェクトに含めることで、これらのプロパティのいずれかを使用できます。エラーをフォームで後で使用して、エラーメッセージを表示したり、ページにエラーがないことを確認したりできるように、errorsプロパティを分割しています。
useFormメソッドをフォームに接続する
useForm()メソッドとreact-hook-formについて理解できたので、これを既存の</form/>要素と統合する必要があります。これにより、これまで説明してきたすべてのreact-hook-form機能をフォームで使用できるようになります。
import { TvIcon } from "@heroicons/react/24/outline";
import { useState } from "react";
import { useForm } from "react-hook-form";
type FormData = {
fullName: string;
email: string;
password: string;
confirmPassword: string;
agreeToTerms: boolean;
};
export const RegistrationForm = () => {
const {
register,
handleSubmit,
formState: { errors },
watch,
} = useForm<FormData>();
const onSubmit = () => {
alert(`Form submitted`);
};
return (
<div className = "flex justify-center items-center w-screen h-screen bg-gray-900" >
<div className = "w-full max-w-md p-8 bg-black bg-opacity-75 rounded-lg" >
<div className = "flex flex-row justify-center items-center gap-x-4" >
<TvIcon className = "h-12 w-12 text-red-500" />
<h1 className = "text-7xl font-bold text-center text-white mb-4" > Getflix </h1>
</div>
<h2 className = "text-3xl font-bold text-white mb-6 text-center" >
Sign Up
</h2>
<form onSubmit = {handleSubmit(onSubmit)} className = "space-y-6" >
{/* Full Name */}
<div>
<input
{...register("fullName", {
required: "Full Name is required"
})}
aria-required
type = "text"
placeholder = "Full name"
className = "w-full p-3 rounded bg-gray-700 text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-red-500"
/>
{errors.fullName && (
<p className = "text-red-500 text-sm mt-1" > {errors.fullName.message} </p>
)}
</div>
{/* Email */}
<div>
<input
{...register("email", {
required: "Email is required",
pattern: {
value: /^\S+@\S+$/i,
message: "Invalid email address",
},
})}
type = "email"
placeholder = "Email Address"
className = "w-full p-3 rounded bg-gray-700 text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-red-500"
/>
{errors.email && (
<p className = "text-red-500 text-sm mt-1" > {errors.email.message} </p>
)}
</div>
{/* Password */}
<div>
<input
{...register("password", {
required: "Please enter your password"
})}
type = "password"
placeholder = "Password"
className = "w-full p-3 rounded bg-gray-700 text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-red-500"
/>
{errors.password && (
<p className = "text-red-500 text-sm mt-1" > {errors.password.message} </p>
)}
</div>
{/* Confirm Password */}
<div>
<input
{...register("confirmPassword", {
required: "Please enter your password",
validate: (value) =>
value === watch("password") || "Passwords do not match",
})}
type="password"
placeholder="Confirm Password"
className="w-full p-3 rounded bg-gray-700 text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-red-500"
/>
{errors.confirmPassword && (
<p className = "text-red-500 text-sm mt-1" > {errors.confirmPassword.message} </p>
)}
</div>
{/* Agree to Terms */}
<div className = "flex items-center text-gray-400 text-sm" >
<input
{...register("agreeToTerms", {
required: "You must agree to the terms and conditions"
})}
type = "checkbox"
id = "agreeToTerms"
className = "mr-2"
/>
<label className = "select-none" >
I agree to the Terms and Conditions
</label>
</div>
{errors.agreeToTerms && (
<p className = "text-red-500 text-sm mt-1" > {errors.agreeToTerms.message} </p>
)}
{/* Submit */}
<button
type = "submit"
className = "w-full py-3 bg-red-600 hover:bg-red-700 text-white rounded font-semibold transition"
>
Sign Up
</button>
{/* Already have account */}
<p className = "text-center text-gray-400 text-sm mt-4" >
Already have an account?{" "}
<a href = "#" className = "text-red-500 hover:underline" >
Sign In
</a>
</p>
</form>
</div>
</div>
);
};
更新されたフォームコードでは、いくつかの調整を行いました。
各要素の登録
各要素にregisterオブジェクトを追加し、いくつかのオーバーライドを構成しました。
すべての入力フィールドにrequiredプロパティを追加しました。これにより、要素に値があるかどうかがチェックされます。値がない場合は、指定された名前が記録され、エラーが誤りとマークされ、名前と指定された必須メッセージでerrorsオブジェクトが更新されます。
{...register( "fullName", {
required: "Full Name is required"
})}
メールのregisterオブジェクトにpatternプロパティを追加しました。これにより、入力の値の基準を指定できます。これは、パスワード、メールフィールド、および値の制限または要件がある可能性があるその他の入力に最適です。
// valid email pattern
pattern: {
value: /^\S+@\S+$/i,
message: "Invalid email address",
},
validateプロパティをconfirm password要素にも追加しました。これは、ユーザーが入力するときに実行される指定された関数です。
validate: ( value) => value === watch( "password") || "Passwords do not match"
register内のvalidate関数は、フィールドのvalidationMode設定に基づいて自動的に実行されます。
デフォルトでは(validationModeを指定しない場合)、React Hook FormはonChangeイベントとonBlurイベントで検証を実行します。これは、
- ユーザーが入力に入力すると→validateがトリガーされることを意味します。
- ユーザーが入力を離れる(ぼかし)と→validateが再びトリガーされます。
カスタム検証モードの更新
カスタム検証モードを更新する場合は、useForm()内でmode設定を使用してこれをオーバーライドできます。
const { register, handleSubmit, formState, trigger } = useForm({
mode: "onSubmit",
});
もう1つステップ進んで、要素ごとにモードを更新し、フォームにグローバルに設定したmode設定をオーバーライドする場合は、useFormからtrigger()メソッドを使用できます。
< input
{...register (" email ", { required: " Email is required " })}
onBlur = {() => trigger("email")} // validate this field onBlur manually
/>
これにより、modeを使用してonSubmit検証を設定し、emailもonBlur()を介してトリガーできます。
react-hook-formライブラリ内でこれらの単純な設定を追加するだけで、以前よりもはるかに優れたユーザーエクスペリエンスが得られますが、それがすべてではありません。アクセシビリティとユーザーエクスペリエンスを向上させるために追加できる、より多くの設定、HTML、および属性について調べてみましょう。
エラーメッセージの表示
フォームエラーは、前に説明したformStateオブジェクトに格納できますが、そこでは役に立ちません。ユーザーに表示する必要があります。これは、以下のように、分割されたerrorsオブジェクトにアクセスするだけで実現できます。
{errors.password && (
< p className = "text-red-500 text-sm mt-1" > {errors.password.message} </p>
)}
このコードでは、errors.passwordオブジェクトに値がある場合にのみタグを表示するために条件構文が使用されています。これは、useForm()チェックからのpasswordフィールドに関連付けられたエラーを示しています。次に、errors.password.messageからエラーメッセージを表示できます。これは、フォームの問題を強調するために、一般的に使用される誤った色(赤など)と組み合わされています。これは、上記のコードに従って他のすべての入力フィールドに適用できます。
aria-requiredの追加
特定の要素が必須であり、フォームの送信時にチェックする必要があることをフォームに通知しました。しかし、これだけでは視覚障碍のあるユーザーに要素が必須であることを通知できません。
スクリーンリーダーを支援するために、スクリーンリーダーによって読み取られるaria属性を要素に追加できます。このプロパティはaria-requiredプロパティです。つまり、スクリーンリーダーが要素に関する情報を読み上げるときに、ユーザーにこの値が正常な送信に必要であることを通知します。
< input
{...register (" fullName ", {
required: " Full Name is required "
})}
aria-required
type = "text"
placeholder = "Full name"
className = "w-full p-3 rounded bg-gray-700 text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-red-500"
/>
fieldsetとlegendの追加
Fieldset要素はコントロールをグループ化し、legend要素はグループ化されたコントロールの説明を提供します。
1つの大きなフォームがあるが、それが「セクション」にまたがるを想像してみてください。たとえば、ユーザー名、メール、パスワードの「ユーザー詳細」セクションと、配送先情報と請求先情報を尋ねる「住所詳細」セクションです。
このチュートリアルでは、TailwindCSSを使用しています。TailwindCSSは、sr-onlyというユーティリティクラスを提供します。