フォームバリデーションのUXで、こんな経験はありませんか?
- ページを開いた瞬間から赤いエラー枠が表示されている
- **1文字入力しただけで「必須項目です」**と怒られる
- サイトごとにバリデーションタイミングがバラバラで学習コストが高い
これらの問題を解決する仕組みが、ブラウザに既に用意されています。
それが :user-invalid疑似クラス です。
:invalidが使いづらかった理由
従来のフォームバリデーションには、:invalid疑似クラスというものが存在していました。
input:invalid {
border-color: red;
}
<input type="email" required />
しかし、:invalidには以下のような使いづらさがありました。
- 即座にエラー表示: ページ読み込み時から赤枠
- ユーザーストレス: 何も入力していないのにエラー
良いユーザー体験ではありませんので、結果としてJavaScriptによる複雑な独自実装が必要になってしまいました。
UX比較::invalid vs :user-invalid
:invalidを使った場合のユーザー体験
1. ページ読み込み → 即座に赤枠表示
2. ユーザー困惑 → 「何も入力してないのに...」
3. 入力開始 → 入力中もずっと赤枠
4. 正しい値入力 → やっと赤枠が消える
:user-invalidを使った場合のユーザー体験
1. ページ読み込み → エラー表示なし
2. 入力開始 → 静かに見守る
3. フォーカスアウト → 必要に応じてエラー表示
4. 修正時 → リアルタイムでフィードバック
この違いは、ユーザーにとって大きなストレス軽減と集中力維持につながります。
:user-invalidによる革新
:user-invalidは「ユーザーが操作した後に無効な状態」を表すCSS疑似クラスです。
input:user-invalid {
border-color: #ef4444;
}
動作の違い
| 状況 | :invalid | :user-invalid |
|---|---|---|
| ページ読み込み時 | ❌ 即座に赤枠 | ✅ エラー表示なし |
| 初回入力中 | ❌ 入力中もエラー | ✅ エラー表示なし |
| フォーカスアウト後 | ✅ エラー表示継続 | ✅ 初回バリデーション |
| 修正時(入力中) | ✅ リアルタイム更新 | ✅ リアルタイム更新 |
理想的なバリデーションフロー
:user-invalidは、以下の3段階のユーザー体験を実現します:
1. 初回入力時は邪魔しない
ユーザー: メールアドレス入力中...
システム: 静かに見守る(エラー表示なし)
2. 完了時点で判定
ユーザー: 入力完了してフォーカス移動
システム: この時点で初回バリデーション
3. 修正時はリアルタイム
ユーザー: エラーを修正中...
システム: 入力に合わせてリアルタイム更新
ブラウザサポート状況
対応ブラウザ
- Chrome: 119+ (2023年10月)
- Firefox: 88+ (2021年4月)
- Safari: 16.5+ (2023年5月)
- Edge: 119+ (2023年10月)
実用性
- デスクトップ: 約95%のユーザーがサポート
- モバイル: 約90%のユーザーがサポート
現在では、多くの環境で十分実用的なレベルに達しています。
基本的な使用方法
最もシンプルな実装から始めることができます:
/* 基本実装 */
input:user-invalid {
border-color: #ef4444;
background-color: #fef2f2;
}
高度な活用(オプション)
:has()セレクターとの組み合わせ
より高度な機能として、:has()セレクターと組み合わせることで、親要素への状態反映やエラーメッセージの表示制御が可能です:
/* 親要素への状態反映 */
fieldset:has(input:user-invalid) {
background-color: #fef2f2;
}
/* エラーメッセージの表示制御 */
.error-message {
display: none;
}
fieldset:has(input:user-invalid) .error-message {
display: block;
}
Stimulusでの実装とフォールバック
JavaScript側では、:user-invalidの動作を再現しつつ、フォールバック対応も行います:
export default class extends Controller {
connect() {
this.hasUserInteracted = false;
this.hasDetectedInput = false;
}
#handleInput() {
if (this.element.value) {
this.hasDetectedInput = true;
}
// 初回入力中はバリデーションしない(:user-invalid準拠)
if (this.hasUserInteracted) {
this.#validate();
}
}
#handleBlur() {
if (this.hasDetectedInput) {
this.hasUserInteracted = true;
this.#validate();
}
}
#validate() {
const isValid = this.element.validity.valid;
// アクセシビリティ + フォールバック対応
this.element.setAttribute('aria-invalid', !isValid);
}
}
CSSでのフォールバック
/* 最新ブラウザ */
input:user-invalid {
border-color: #ef4444;
}
/* フォールバック */
input[aria-invalid='true'] {
border-color: #ef4444;
}
この実装により、古いブラウザでも同様のUXを提供しつつ、アクセシビリティにも配慮できます。
HTML側の使用例
実際のHTMLでの使用例は以下のようになります:
<fieldset>
<legend>連絡先</legend>
<div>
<label for="email">メールアドレス</label>
<input
id="email"
type="email"
required
data-controller="field-validator"
class="border border-gray-300 user-invalid:border-red-500 aria-invalid:border-red-500"
/>
</div>
</fieldset>
技術的価値
1. Web標準準拠
- ブラウザネイティブの動作を再現
- 長期的な安定性
2. 段階的拡張
- 基本実装から高度な機能まで
- 必要に応じてカスタマイズ
3. アクセシビリティファースト
- スクリーンリーダー対応
- 全ユーザーへの配慮
まとめ
:user-invalidの技術的価値
- Web標準準拠: 長期的な安定性
- UX向上: 邪魔しないバリデーション
- アクセシビリティ: 全ユーザーへの配慮
- 保守性: シンプルで理解しやすい実装
業界への影響
- 統一基準: 一貫したユーザー体験
- 学習コスト削減: 予測可能な動作
- 技術的負債解消: 複雑な独自実装からの脱却
最後に
ブラウザの日々のアップデートにより標準機能でできることが増えています。複雑なライブラリに頼る前に、まずはWeb標準で解決できないかを考えてみてはいかがでしょうか。
フロントエンド開発者として、「標準」の力を活かしていきましょう。
次回予告
今回は:user-invalidの価値とUX改善効果について解説しました。
次回は実装編として、:user-invalid準拠の実用的なフォームバリデーションシステムを構築していきます。