HTML: ログイン・ユーザー登録フォームの厳選ベストプラクティス11(翻訳)
はじめに
ログインフォームやユーザー登録(サインアップ)フォームは、ほとんどのWebサイトで使われていて、ビジネス上のコンバージョンにおける重要な役割を担っています。しかし有名Webサイトですら、本記事で解説している11のベストプラクティスを実装しきれておらず、いくつも手落ちが散見されます。本記事をよく読んで自分たちのフォームを改めてチェックし、HTML技術を本来の形で活用してユーザーエクスペリエンス(UX)を改善しましょう。
一般に見かけるサインインフォームやログインフォームは非常にシンプルで、ほとんどのWebサイトでは入力項目2個と送信ボタン1個だけになっています。しかしこれほどシンプルなHTML構造にもかかわらず、多くのWebサイトが小さな過ちをおかしています。
この種のフォームは、シンプルでありながら多種多様なエラーが発生する可能性があります。見方を変えれば、HTMLの新機能を学習する有用な環境でもあり、あらゆる種類のフォームで最高のユーザーエクスペリエンス(UX)を実現するのに必要なスキルを身につけるうえで有用な学習環境でもあります。
🔗 その前に:「メールアドレスとパスワード」方式よりも「パスワードレス」方式を検討しよう
HTMLフォームのペストプラクティスを解説する前に、ひとつ付け加えておきたいことがあります。
本記事では、あえて古典的な「メールアドレスとパスワード」方式のフォームを例に解説しています。ただし、実を言うとパスワード方式はセキュリティ面から言うと最悪であり、既知の弱点が山ほどあります。どんな弱点があるかを手短に見てみましょう。
VerisonVerizonによる2018年のデータ侵害レポートによると、全ユーザーの実に70%以上がパスワードを複数のWebサイトで使いまわしています。これでは、攻撃者が流出データから複数のWebサイトで共有されているパスワードを見つけて、他のWebサイトのアカウントを奪い取るのに利用される可能性もあります。いわゆる2要素認証(2FA)をソリューションとして実装する方法もありますが、以下で説明するパスワードレス方式に比べてユーザーエクスペリエンスが低下します。-
ユーザーはパスワードを忘れることが多く、パスワードのリセットはユーザーにとって時間も手間もかかる作業です。Transmit Securityのレポートによると、消費者の55%が「ログイン手順が複雑すぎる」という理由でWebサイトを使わなくなっています。
ログインフォームを改善したければ、最初にパスワードレス認証のオプションも検討しておきましょう。
以下は、現時点で私の好きなパスワードレス認証のオプションです。
- パスワードレス認証を手作り実装するのではなく、セキュアで、適切に設計され、メンテナンスされているサードパーティのソリューションを利用する(例: Auth0、Amazon Cognito)。
私たちは、セキュリティ要件が最も厳しいプロジェクトを含むいくつかの顧客プロジェクトで、このような認証方法のいずれかを利用しています。 -
AppleやGoogleが提案している新しいパスキー(passkey)標準を利用する。
パスキーのしくみやパスキーが優れている理由については、パスキーのデモやNew York Timesの記事で解説されています。 -
サインインリンクを含むメール送信を実装する(多くのユーザーは日常のログインで結局「パスワードを記憶する」機能を利用するのですが)。
これらのオプションは組み合わせることも可能です。たとえば、新しいブラウザを使っているユーザーはパスキーでログインさせ、ブラウザでパスキーが使えないユーザーにはサインインリンクをメール送信します。
前置きはこのぐらいにして、ログインフォームとユーザー登録(サインアップ)フォームの11のベストプラクティスの話に移りましょう(ベストプラクティスのほとんどは、他のあらゆるタイプのフォームでも有効なガイドラインとなることを念頭に置いてお読みください)。
🔗 1: autocomplete
属性を設定すること
- <input type="email">
+ <input type="email" autocomplete="username">
- <input type="password">
+ <input type="password" autocomplete="current-password">
<button>ログイン</button>
専用のパスワードマネージャソフトウェアは、メールやパスワードのフォームでセキュリティリスクを軽減できる唯一のオプションです(ただしリスクをすべて解決できるわけではありません)。だからこそ、パスワードマネージャの機能をフォームで支援することが重要です。
HTMLの<input>
タグでは、極めて有用なautocomplete
属性を利用できます。autocomplete
属性を使えば、ユーザー登録フォーム(new-password
)とログインフォーム(current-password
)をパスワードマネージャが自動的に区別できるようになります。
使い方を見てみましょう。
<label>
New password:
- <input type="password">
+ <input type="password" autocomplete="new-password">
</label>
<label>
Confirm password:
- <input type="password">
+ <input type="password" autocomplete="new-password">
</label>
<button>サインアップ</button>
自分がやっていることを十分理解するまでは、autocomplete="off"
でオートコンプリートを無効にしてはいけません。さもないとユーザーエクスペリエンスが損なわれる可能性があります。
autocomplete="off"
は、取り扱いに十分注意の必要なデータ(医療Webサイトの「症状」フィールドなど)でオートコンプリートが表示されないようにする場合にのみ設定すべきです。
autocomplete="off"
ハック は、disabled
を動的に設定すると画面リフレッシュ時にステートがリセットされない問題を修正するのに使います。このとき、フォーム全体ではなく、送信<button>
だけをoff
に設定すること。
🔗 2: メールのフィールドにはtype="email"
を設定すること
<label>
E-mail:
- <input type="text" autocomplete="username">
+ <input type="email" autocomplete="username">
</label>
ログインフォームでありがちなミスのひとつは、メールのフィールドにtype="email"
ではなくtype="text"
を設定してしまうことです。
この属性を正しく設定しておくことが重要な理由は、以下の通りです。
- ユーザーのメールアドレスがブラウザのオートコンプリートで表示されるようになる(そのWebサイトに初めてアクセスする場合にも表示されます)。
-
スマホやタブレットなどのタッチデバイスで、メールアドレスの入力に特化した使いやすいキーボードが表示されるようになる。
-
ブラウザ組み込みのメールアドレス検証機能が有効になる。
ブラウザ組み込みの検証(バリデーション)機能が気に入らない場合でも、type="text"
を設定してはいけません。検証機能を無効にしたい場合は代わりにnovalidate
を設定すること。
フォームで、同じフィールドにユーザー名とメールアドレスのどちらを入力してもよい場合であっても、inputmode="email"
を必ず設定しておくこと。そうすることでメール専用キーボードがそのフィールドで有効になるので、全体的な使い勝手がよくなります。
🔗 3: クリック可能な要素には <div>
や<span>
ではなく、必ず<button>
か<a>
を使うこと
- パスワードをお忘れですか? <span>パスワードをリセット</span>
+ パスワードをお忘れですか? <a href="/restore">パスワードをリセット</a>
- <div>ログイン</div>
+ <button>ログイン</button>
リンクは現在のページを変更するためのものです。リンクは常に<a>
タグで作るべきです。
ボタンは、現在のページのURLを変更せずに、現在のページのステートだけを変更するためのものです。ボタンは常に<button>
タグで作るべきです。
アクセシビリティについては以下の記事をどうぞ。
Accessible design from the get-go
<a>
タグでリンクを作る方が、<span onClick={...}>
を使うよりもずっと多くのメリットを得られます。たとえば、ユーザーが「リンクを新しいタブで開く」ことも、リンクをクリックする前にマウスオーバーでリンク先の新しいURLを確認することも可能になります。
<button>
タグでボタンを作る方が、<div>
タグで作るよりもWebサイトのアクセシビリティが向上し、キーボード入力のユーザーエクスペリエンスも向上します。スクリーンリーダーがボタンを認識して扱えるようになり、ボタンに:focus
ステートが効くようになるのでキーボードでの操作性も向上します。
また、パスワードの表示/非表示を切り替える機能も<button>
タグで作るべきです。文字のないアイコンだけのボタンを作る場合は、以下のようにaria-label
属性でボタン名も設定することをお忘れなく。
<button type="button" aria-label="パスワードを表示">
<div class="eye-icon">
</button>
送信と無関係のボタンをフォームに配置する場合は、type="button"
も設定すること(ただし「入力内容をクリア
」のような無意味なボタンは置かない方が身のためです)。ブラウザのボタンにtype="button"
が設定されていないと、クリックしたときにフォームが送信されてしまいます。
もうひとつおまけ。<a>
タグや<button>
タグは開発中にも有用です。インタラクティブな要素を常に<a>
タグや<button>
タグで作っておけば、インタラクティブな要素を対象とするCSSセレクタを以下のように簡単に作成できるようになります。
button, a {
cursor: pointer; /* カーソルをポインタに変えているのは単なる例(議論の余地あり) */
}
🔗 4: フィールドや送信<button>
は<form>
タグの内側に置くこと
- <div>
+ <form>
<label>Email: <input type="email" autocomplete="username"></label>
<label>Password: <input type="password" autocomplete="current-password"></label>
+ <button>Login</button>
+ <form>
- </div>
- <button>Login</button>
フォームの全フィールドと送信<button>
は、<form>
タグと</form>
タグの内側に置くこと。
Enterキーを押すことによるフォーム送信は、「フィールドが<form>
タグの内側に置かれている」および「<form>
タグの内側に単一の送信ボタンがある」形でのみ行うようにしましょう。
また、この条件が守られていないと、スクリーンリーダーを利用しているユーザーの使い勝手も損なわれてしまいます。
🔗 5: placeholder
属性を<label>
タグ代わりに使うのは避けること
- <input placeholder="E-mail" type="email" autocomplete="username">
+ <label>
+ E-mail:
+ <input type="email" autocomplete="username">
+ </label>
placeholder
属性が作られた目的は、入力フィールドにサンプル文字列を表示するためであり、入力フィールドの説明文をおしゃれに表示するためではありません。そういうわけで、placeholder
属性は<label>
タグ代わりに使わないことを強くおすすめします。
さらに、placeholder
属性の文字はユーザー入力中に非表示になるうえ、コントラストが弱くて読みにくくなる問題もあります。
日付のYYYY/MM/DD/
形式のようなサンプルであっても、できればplaceholder
属性で表示するのは避けて別行に表示することをおすすめします。
placeholder
属性は、label
代わりに使うべきではない。
HTML Living Standard
メールアドレスとパスワードを送信するだけの小さなフォームならそれほど重大でなくても、フォームの規模が大きくなればなるほどplaceholder
のさまざまな問題が顕在化します。
<label>
タグにfor
属性やid
属性を追加するだけが能ではありません。<label>
タグの内側に<input>
タグを置けば、for
属性もid
属性も不要になります。
🔗 6: チェックボックス入力は<label>
タグの内側に置くこと
- <input type="checkbox"> プライバシーポリシーに同意
+ <label>
+ <input type="checkbox"> プライバシーポリシーに同意
+ </label>
デフォルトのチェックボックスはとても小さいので、クリックで反応する領域も狭くなってしまいます。つまり、ユーザーがチェックボックスを操作しようとすると、カーソルを小さなチェックボックスに厳密に合わせなければならなくなり、ユーザーに余計な時間と神経を使わせてしまいます。
しかしチェックボックス入力を<label>
タグと</label>
タグの内側に配置しておけば、ラベルのテキストをクリックしてもチェックボックスの値が変更されるようになります。
チェックボックスが複数ある場合は、個別に<label>
タグで囲む必要があることにも注意しましょう(複数のチェックボックス全体を<label>
で囲んではいけません)。
このとき、CSSで:hover
エフェクトをわかりやすい色で追加しておくこともおすすめします。こうすることで、チェックボックスのテキストをクリックすればチェックボックスをオンオフ可能であることがユーザーに伝わります。
label:hover {
background: oklch(0 0 0 / 10%);
}
🔗 7: わかりやすい:focus
ステートを追加すること
- *:focus {
- outline: none;
- }
+ button:focus-visible, a:focus-visible, input:focus-visible {
+ outline: 5px solid oklch(60% 0.15 252);
+ }
ここではカラー指定にOKLCHを使っています。
OKLCH in CSS: why we moved from RGB and HSL
アプリケーションをキーボードで操作するときのユーザーエクスペリエンスは、ともすると見落としたり軽く考えがちです。しかし、フォームでは入力に必ずキーボードを使うのですから、キーボードで入力するときのUIが使いやすくなるよう考慮する必要があります。
最初のステップは、現在入力中のフィールドにわかりやすい:focus
ステートを追加して、そのフィールドが現在入力中であることを強調表示することです。こうしておけば、ユーザーは入力のフォーカスがどこに移動したかをフォーカスを意識的に追いかけなくても楽にわかるようになります。Sara Soueidanは、:focus
インジケータをわかりやすく表示する方法の解説を素晴らしいガイドにまとめてくれています。
入力フィールドやボタンに:focus
ステートをひととおり作成したら、 <a>
タグにも:focus
ステートを追加しておきましょう。これが、Webサイトのキーボード操作性を改善する最初の小さなステップです。
アプリの:focus
ステートは、くれぐれも無効にしないこと。
もうひとつの便利技: SPA(Single Page Application)を使っていて、メニュー項目をクリックしたら:focus
ステートを削除したい場合は、:focus-visible
が使えます。
🔗 8: スクリーンリーダーの入力エラー表示にも対応すること
<input type="email" autocomplete="username"
- class="invalid">
+ required aria-invalid="true" aria-errormessage="email-error">
<div id="email-error">有効なメールアドレスを入力してください</div>
aria-invalid
ステートとaria-errormessage
属性は、スクリーンリーダー向けの検証エラーを表示します。
さらにメモ: スクリーンリーダーでも、フィールド入力が必須であることをrequired
属性で警告するのはよい方法です。required
属性を指定したときに表示されるブラウザ組み込みの検証機能では満足できずに独自の検証機能を実装する場合は、必ずaria-required
属性を設定すること。
🔗 9: 入力が完了するまでは検証を発動させないこと
- input.addEventListener('keyup', () => {
- if (validate(input)) {
- markValid(input)
- } else {
- markInvalid(input)
- }
- })
+ input.addEventListener('input', () => {
+ if (validate(input)) {
+ markValid(input)
+ }
+ })
+ input.addEventListener('change', () => {
+ if (validate(input)) {
+ markValid(input)
+ } else {
+ markInvalid(input)
+ }
+ })
ユーザーがフォームにデータを入力し終わっていないのにエラーをアニメーション表示すると、ユーザーの集中を削いだり混乱させたりするので望ましくありません。ユーザーが入力を終えるまでは、「無効なメールアドレスです
」などのエラーを表示しないこと。
解決方法は、ユーザーが(他の制御に移動することで、またはフォームを送信することで)入力を完了したら、検証をトリガーするイベントをkeyup
ではなくchange
にすることです。もちろんinput
イベントやkeyup
イベントも引き続き利用できますが、これらのイベントは入力中のエラーを非表示にするためだけに使いましょう。
フォームの検証機能については以下の素晴らしいガイドがあります。
参考: A Complete Guide To Live Validation UX — Smashing Magazine
🔗 10: フォームが2回送信されないようにすること
form.addEventListener('submit', () => {
submitButton.disabled = true
// Firefox向けの修正。このハックを使わなくても
// 動的なdisabledステートは維持される。
submitButton.autocomplete = 'off'
// ここではページを再読み込みする送信でsetTimeoutを使っている。
// AJAXで送信ボタンを有効にするには、awaitとtry-finallyを使うこと
setTimeout(() => {
submitButton.disabled = false
}, 2000)
})
ユーザーがボタンをシングルクリックではなく誤ってダブルクリックしてしまうことはよくあります。フォームを2回送信してサーバーエラーが表示されないよう、フォーム送信と同時にボタンを無効にしておくことをおすすめします。
🔗 11: Ajaxでは「ネットワーク遅延」「サーバーエラー」「ネットワークエラー」に配慮すること
form.addEventListener('submit', async () => {
- await fetch(...)
+ try {
+ showLoader()
+ await fetch(...)
+ } catch (e) {
+ showError(e)
+ } finally {
+ hideLoader()
+ }
})
以下の2つは、どんなAjaxリクエストであっても配慮しておくべきです。
- 読み込み中であることをユーザーに表示すること
ローカル開発中のレイテンシは0ミリ秒でも、現実のユーザーはサーバーからのレスポンスを受け取るのに数秒かかります。送信ボタンをクリックした後は何らかの形でリアクションをユーザーに表示すべきです。 - ネットワークエラーやサーバーエラーを適切に処理すること
ローカル開発ではこれらのエラーが表示されませんが、production環境ではどんなユーザーでも「WiFiがダウンしています」「500 error」などのエラーをサーバーから受け取る可能性があります。常にエラーに備えて適切なメッセージをユーザーに表示すべきです。
メモ: ユーザー認証フォームは、Ajaxで送信するよりもページを再読み込みする通常の形でフォームを送信することをおすすめします。この方が、ユーザーのトークンがhttpOnly
cookieに保存され、Webアプリ内の全ストアも更新される点で優れています。
🔗 チェックリスト
本記事のベストプラクティスを手短にまとめておきました。今後、フォームを何らかの形で含むプルリクをレビューするときは、以下のチェックリストをお使いください。
- [ ] 入力フィールドに
autocomplete
属性を設定しているか - [ ] 入力フィールドに適切な
type
値を設定しているか - [ ] クリッカブルな要素に(
<div>
タグや<span>
タグではなく)<button>
タグか<a>
タグを使っているか - [ ] 入力フィールドや送信
<button>
を<form>
タグの内側に置いているか - [ ]
placeholder
に頼らずに<label>
タグで<input>
の入力例を示しているか - [ ] チェックボックスを
<label>
タグの内側に置いているか - [ ] UIで
:focus
ステートがわかりやすく表示されているか - [ ] 入力が無効なフィールドにスクリーンリーダー用の属性も追加しているか
- [ ] 入力中に検証機能が発動しないか
- [ ] フォームを2回送信しないようになっているか
- [ ] ネットワークレイテンシやサーバーエラー、ネットワークエラーに配慮しているか
自社アプリケーションにきめ細かな配慮と最高の仕上がりをお求めのお客様、Evil Martiansはいつでも参上いたします。フロントエンド、バックエンド、製品設計、DevOps、その他どのようなご相談でも元記事のフォームまでお気軽にお寄せください!
概要
元サイトの許諾を得て翻訳・公開いたします。
日本語タイトルは内容に即したものにしました。