パスワードの入力文字列を任意で表示する

フォームのパスワード入力欄 (<input type="password">) は基本的に、入力された文字列がマスキングされ、代わりに文字数の分だけ「黒い点」が提示されるようになっています。他人によるパスワードの盗み見を防ぐためですが、ユーザーは、大半のケースでは背後から覗く人が誰もいない状況でパスワードを入力するでしょうし、入力した文字列が視認できないと正しくタイプできているか不安になったり、タイプミスしたときの修正がしづらい (どの箇所でミスしたか確信が持てず、はじめから入力し直さざるを得なくなる) といった問題もあるでしょう。こうしたことを考慮に入れると、パスワードのマスキングはユーザーの任意で解除できる (入力した文字列を表示させる) ようにしたほうが、ユーザビリティの観点では望ましいと考えることができます。

よくある実装 (input 要素の type 属性を切り替える)

そして実際、そのようなフォームも増えてきている印象があります。そのほとんどは、パスワード入力欄に隣接したチェックボックスやボタンをクリック (タップ) すると、JavaScript によって <input> 要素の type 属性が "password" から "text" に切り替わり、それによってパスワード入力欄の中で文字列が見えるようになるというものです。私が監訳した書籍「Form Design Patterns」でも、第1章「登録フォーム」の「パスワードフィールド」の節で、そのようなテクニックが紹介されています。

書籍「Form Design Patterns」で紹介されているパスワード入力欄の例。「表示する」ボタンを押すことでマスキングが解除され、パスワードの文字列が表示される。
パスワード入力欄の例。テキストボックスの右隣に「表示する」ボタンがある。

この実装は一見、期待通りの挙動をして問題なさそうですが、英語環境ならまだしも日本語環境の場合、<input type="password"><input type="text"> とでは入力可能な文字の種類が異なるため (後者では全角文字が入力できてしまう)、入力したパスワードを送信してもシステム側で受け入れられないということが生じ得ます。また、<input> 要素の type 属性を "password" 以外に変更してしまうことで、パスワード入力の自動補完に関する挙動が意図せず変わってしまう可能性もあります。

さらにスクリーンリーダーを介して利用している場合、<input type="password"> にフォーカスが当たったときに得られる音声フィードバック (たとえば NVDA では「保護付き」、macOS VoiceOver では「保護されたテキスト」、iOS VoiceOver では「セキュリティ保護されたテキストフィールド」という具合に読み上げられます) が、type="text" に変わると得られなくなる、という問題もあります。

パスワード入力欄の type 属性を password のまま維持してマスキングを解除する

こうした懸念を考慮に入れるならば、パスワードのマスキングをユーザーの任意で解除できるようにするにしても、 <input> 要素の type 属性は "password" のまま維持したいところです。しかしながら現状ではほとんどのブラウザにおいて、type="password" ではパスワード入力欄の中で文字列を見えるようにすることができないので、せいぜいのところ、入力欄に隣接した所に、パスワードの文字列を取得したものを表示させる、というのが次善策になるでしょうか。これが実用的と言えるかどうか、試しに CodePen で簡単なデモを作ってみました。

See the Pen パスワードの表示 by @caztcha (@caztcha) on CodePen.

パスワード入力欄に隣接する「表示する」ボタンを押すクリック (タップ) すると、入力したパスワードの文字列が、入力欄の直下に表示されます。パスワードの文字列が表示された状態では、ボタンのラベルは「隠す」となり、これをクリックするとパスワードの文字列が隠されます。

なお、この機能は、Edge 以外のブラウザで有効になるようにしてあります。Edge にはもともと、パスワードのマスキングを解除できる機能が標準で備わっているからです。

ユーザーの認知負荷を軽減する実装

このデモは基本的なアクセシビリティを担保していますが (キーボード操作、ボタンのサイズ、配色のコントラスト、テキストラベルの使用、など)、このようなインタラクションはほとんどのユーザーにとってなじみのないものであることから、ユーザーの認知負荷を軽減するために、以下も考慮に入れて実装しています。

フリップフロップ問題の回避

機能を切り替えるボタンの表現が「現在の状態」「ボタンを押した後の状態」のどちらを表わしているのかわかりにくい現象 (フリップフロップ問題といいます) を避けるため、ボタンを押した後の状態、つまりボタンを押すことで何が起こるかを明示すべく、ボタンのラベルは動詞 (「表示する」「隠す」) で表現しています。

この手のユーザーインターフェースでは、テキストラベルの代わりに目の形をしたアイコン (斜めの打ち消し線の有無が切り替わるもの) を採用したくなる向きもあるかもしれませんが、アイコン単体だと、特にパスワードの文字列が未入力の状態において、フリップフロップ問題が生じやすくなるので、アイコンだけでなく動詞のラベルを併記するのがよいと思います。

スクリーンリーダーのユーザーに向けた補足説明

目が見えるユーザーであれば、「表示する」または「隠す」ボタンを押してどんな状態になったのか、比較的容易に視認することができますが、目が見えないユーザーにとっては、補足説明がないと「現在の状態」を知ることが難しくなります。特にここではパスワードというセンシティブな情報を扱うので、ユーザーの不安を払拭するために十分な状況説明が必要でしょう。

このデモでは、視覚的に隠された (いわゆる visually-hidden の) スクリーンリーダー向けのテキスト領域を設置し、以下のように「現在の状態」を言語化してユーザーに伝達するようにしています。

このテキスト領域はライブリージョンになっており (role="status")、「表示する」(または「隠す」) ボタンが押されて説明文が差し替わるたびに、新たに挿入されたテキストがスクリーンリーダーで読み上げられるようになっています。ボタン部分も aria-live="polite" にしてあるので、ボタンを押すとまずボタンの更新されたラベルが読み上げられ、少し間を置いて説明文が読み上げられます。たとえば「表示する」ボタンにフォーカスを当て、Enter キーで実行したら、「隠す。パスワードの文字が、パスワード入力欄と『隠す』ボタンのすぐ下に、展開表示された状態です。」と読み上げられるという具合です。

説明文の書きかたとしては、画面が視認できなくても「何が」「どこに」「どのように」を具体的にイメージできるように書き出すことがポイントです。また、状況説明の語順を揃える (「パスワードの文字が、●●●された状態です。」というパターンにする) ことによって、ふたつの説明文を音声で聞き比べたときに、両者の差異 (●●●の部分) が識別しやすくなります。

課題

このデモの課題はやはり、表示されたパスワードの文字列を直接編集することができないことでしょう。マスキングされたパスワード入力欄で何らかの修正 (文字の追加や削除) が行われると、それが直下の文字列表示に即座に反映されるようにはなっているものの、パスワードの文字数が長くなればなるほど、パスワード入力欄のどこにカーソル (キャレット) を当てれば意図通りの修正ができるか、見当をつけるのが難しくなります。もっとも、人間が記憶できる以上の長さのパスワードであれば、手入力ではなくパスワードマネージャーなどからコピー&ペーストすることが多くなるでしょうから、実用上さほど問題ではないのかもしれませんが、ユーザーインターフェースとしてはいまひとつスマートではない気がします。

理想はブラウザの標準機能でマスキングの解除が可能になること

ここまで、<input> 要素で type="password" を維持しつつ、パスワードの文字列を表示させることを試みてみましたが、表示された文字列に対する直接編集ができないという課題があることを考えると、既に Edge で可能になっているように、ブラウザ側にこうした機能が標準で備わっているのが理想的かと思います。

Edge でパスワード入力欄 (<input type="password">) を表示すると、パスワードのマスキングを解除する機能が標準装備されている。
Edge におけるパスワード入力欄の表示。目の形をしたアイコンがあり、パスワードのマスキング解除が可能であることがわかる。

もっとも Edge の標準機能は、以下の点で必ずしも使いやすいものではありません。

このあたりが改善されて、つまりパスワードのマスキング解除が可能であることが常に明示的で、かつ一般的なキーボード操作での利用 (Tab キーでフォーカスを当てて Enter キーで実行) が可能で、さらには「現在の状態」(パスワードの文字列が表示されているのか隠されているのか) がスクリーンリーダーを介して伝わる格好で、各ブラウザの標準機能として備わるようになるとよいなと思います。

将来、実際にそうなるかはわかりませんが、とりあえずのつなぎとしては、この記事でご紹介したデモのような実装も、ありなのかもしれません。