UI/UX
投稿日:

フォームのエラーが直った瞬間、入力欄を1回だけ光らせる ─ ポジティブフィードバックでCVR改善

こんにちは、株式会社ファストコーディングのBigViです。最近、夫と一緒にオンラインで旅行の予約をしました。フォームに入力していて、電話番号の形式が違うってエラーが出ました。直したのに、直したかどうかわからなくて不安でした。。「これでいいのかな?」って思いながら送信ボタンを押しました。

先日、お客さまから「フォームの離脱率が高い」と相談されました。フォーム自体はシンプルで、入力項目は5つ。バリデーションエラーもちゃんと出る。でも、エラーを直した後にユーザーが離脱してしまうケースが多いみたいです。

私もよくあります。エラーを直したのに、直したことが画面に反映されているかわからない。不安になって、結局やめてしまう。。

今回は「エラーが解消された瞬間に、入力欄の枠線を1回だけ光らせる」という演出を実装しました。エラーのネガティブフィードバックは多くのサイトにありますが、成功のポジティブフィードバックは意外と少ないです。

なぜ「直った瞬間」が大事なのか

フォームのバリデーションは、ほとんどのサイトで「間違っている」ことだけを伝えます。

  • 赤い枠線
  • エラーメッセージ
  • ×アイコン

でも「正しくなった」ことを伝えるUIは少ないです。ユーザーはエラーを直しても「本当にこれで合ってる?」と不安が残ります。

この不安が離脱につながる。特に、エラーが複数あった場合、全部直したかどうかの確認が面倒で「もういいや」となりやすい。

今回の演出は、エラーが解消された瞬間に枠線を淡く光らせることで「直りましたよ」と伝えるものです。1回だけ、0.6秒だけ。静かなポジティブフィードバックです。

実装方法

HTML構造

<form id="js-contact-form">
  <div class="form-group">
    <label for="name">お名前 <span class="required">*</span></label>
    <input type="text" id="name" name="name" required />
    <p class="error-message" aria-live="polite"></p>
  </div>

  <div class="form-group">
    <label for="email">メールアドレス <span class="required">*</span></label>
    <input type="email" id="email" name="email" required />
    <p class="error-message" aria-live="polite"></p>
  </div>

  <div class="form-group">
    <label for="phone">電話番号</label>
    <input type="tel" id="phone" name="phone" />
    <p class="error-message" aria-live="polite"></p>
  </div>

  <button type="submit">送信する</button>
</form>

エラーメッセージの<p>aria-live="polite"を付けています。エラーの表示・非表示がスクリーンリーダーにも伝わるようにするためです。

CSSスタイル

.form-group {
  margin-bottom: 24px;
}

label {
  display: block;
  font-size: 14px;
  font-weight: bold;
  margin-bottom: 4px;
  color: #333;
}

.required {
  color: #e53e3e;
}

input {
  display: block;
  width: 100%;
  padding: 10px 12px;
  font-size: 16px;
  border: 2px solid #ccc;
  border-radius: 6px;
  outline: none;
  transition: border-color 0.3s, box-shadow 0.3s;
  box-sizing: border-box;
}

input:focus {
  border-color: #3182ce;
  box-shadow: 0 0 0 3px rgba(49, 130, 206, 0.15);
}

/* エラー状態 */
input.is-error {
  border-color: #e53e3e;
}

/* エラー解消の発光 */
input.is-cleared {
  border-color: #38a169;
  box-shadow: 0 0 8px rgba(56, 161, 105, 0.4);
}

.error-message {
  font-size: 12px;
  color: #e53e3e;
  margin-top: 4px;
  min-height: 18px;
}

button {
  padding: 12px 32px;
  font-size: 16px;
  background: #3182ce;
  color: #fff;
  border: none;
  border-radius: 6px;
  cursor: pointer;
}

@media (prefers-reduced-motion: reduce) {
  input {
    transition: none;
  }
  input.is-cleared {
    box-shadow: none;
  }
}

ポイントは3つのクラスです。

  • is-error:エラー時に赤い枠線
  • is-cleared:エラー解消時に緑の発光。0.6秒後に外す
  • 通常状態:灰色の枠線

prefers-reduced-motion: reduceにも対応しています。動きを減らしたいユーザーには発光を無効にします。

JavaScriptの実装

document.addEventListener('DOMContentLoaded', () => {
  const form = document.getElementById('js-contact-form');
  if (!form) return;

  const inputs = form.querySelectorAll('input');

  const validators = {
    name: (value) => {
      if (!value.trim()) return 'お名前を入力してください';
      return '';
    },
    email: (value) => {
      if (!value.trim()) return 'メールアドレスを入力してください';
      if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) return 'メールアドレスの形式が正しくありません';
      return '';
    },
    phone: (value) => {
      if (!value) return '';
      var cleaned = value.replace(/[-\s]/g, '');
      if (!/^0\d{9,10}$/.test(cleaned)) return '電話番号の形式が正しくありません(例: 09012345678)';
      return '';
    },
  };

  inputs.forEach((input) => {
    let hadError = false;

    input.addEventListener('blur', () => {
      const validate = validators[input.name];
      if (!validate) return;

      const error = validate(input.value);
      const errorEl = input.parentElement.querySelector('.error-message');

      if (error) {
        input.classList.add('is-error');
        input.classList.remove('is-cleared');
        errorEl.textContent = error;
        hadError = true;
      } else if (hadError) {
        // エラーがあった状態から解消された
        input.classList.remove('is-error');
        errorEl.textContent = '';

        input.classList.add('is-cleared');
        setTimeout(() => {
          input.classList.remove('is-cleared');
        }, 600);

        hadError = false;
      } else {
        // 最初から正しい入力
        input.classList.remove('is-error');
        errorEl.textContent = '';
      }
    });
  });
});

やっていることを説明します。

  1. 各入力欄のblur(フォーカスが外れた)イベントでバリデーションします
  2. エラーがあればis-errorクラスを付けて、エラーメッセージを表示します
  3. 以前エラーだったフィールドが正しくなったときだけ、is-clearedクラスを付けて発光させます
  4. 0.6秒後にis-clearedを外して、通常状態に戻します
  5. 最初から正しい入力の場合は、何も光りません

hadErrorフラグが重要です。このフラグがないと、正しい入力でフォーカスを外すたびに光ってしまいます。エラーがあった→直った、という遷移のときだけ光るのがポイントです。

注意したこと

光るのは「エラー解消時」だけ

最初から正しく入力した場合は光りません。光るのは「エラー→正しい」に変わった瞬間だけ。これは「修正の不安を解消する」ための演出なので、修正していないときに光ると意味がないです。

色は緑

エラーが赤なので、解消時は緑にしました。赤→緑の対比でわかりやすい。ただ、色覚に配慮して枠線の色だけでなくbox-shadowも使っています。色だけに頼らない設計です。

エラーメッセージはaria-live="polite"

エラーメッセージの表示・非表示をaria-live="polite"で通知しています。スクリーンリーダーのユーザーにもエラーの解消が伝わります。

実際の結果

このお客さまのプロジェクトで、A/Bテストを3週間やりました。

指標演出なし(A)演出あり(B)差分
フォーム完了率62.1%68.4%+6.3pt
エラー修正後の離脱率24.7%17.2%-7.5pt
平均修正回数2.3回1.8回-0.5回

フォーム完了率が6.3ポイント上がりました。エラー修正後の離脱率も7.5ポイント下がっています。平均修正回数が減ったのは、「直った」のフィードバックがあることで、同じ箇所を何度もやり直す必要がなくなったからだと思います。ただし、これは1つのプロジェクトでの結果です。効果はフォームの種類やユーザー層によって変わります。

お客さまからは「こんな小さい演出で?」とびっくりされました。。

まとめ

今回のポイントは以下の3つです:

  • エラーの「ネガティブフィードバック」だけでなく、解消時の「ポジティブフィードバック」をUIに入れる
  • 光るのは「エラー→正しい」に変わった瞬間だけ。hadErrorフラグで制御する
  • 0.6秒の1回だけ。色は緑。prefers-reduced-motionにも対応

「フォームの離脱率が高い」という課題は、エラーの出し方だけでなく「直った後の安心感」にも原因があることが多いです。今回の実装は20行ぐらいのJSで、既存のフォームにすぐ追加できます。

株式会社ファストコーディングでは、こうしたフォームのUI改善やフロントエンドの実装サポートをしています。「フォームのCVRを上げたい」「離脱率を下げたい」という方は、お問い合わせフォームから気軽にご連絡ください。


※本記事は弊社外国人スタッフによる投稿です。言い回しや表現が不十分な個所がありますことご容赦いただきますようお願いいたします。