【テクニカル・上級編】 :valid と :invalid 疑似クラス – CSS実践ガイド

CSSバリデーションの深淵:`:valid` と `:invalid` がもたらす「宣言的UI」の真価

フロントエンドの世界で、フォームのバリデーションをJavaScriptの責務だと思い込んでいるなら、それは少しばかり時代遅れかもしれない。ブラウザはすでに強力なバリデーションエンジンを搭載している。我々エンジニアがすべきは、DOMを監視してクラスを付け替える泥臭い作業ではなく、ブラウザの内部状態をCSSで「宣言的に」キャッチすることだ。

今回は、`:valid` と `:invalid` を用いた、堅牢かつパフォーマンスに優れたフォーム実装のアーキテクチャについて語ろうと思う。

—

ブラウザのエンジンを信頼せよ:状態管理の最適化

JavaScriptで`input`イベントを監視し、正規表現を回してDOMにエラーメッセージを挿入する……。このアプローチは、複雑なUIにおいては「レンダリングの競合」や「メモリリーク」の温床となりやすい。

一方、`:valid` / `:invalid` はブラウザのネイティブエンジンが直接状態を評価する。これは、ブラウザのレンダリングパイプラインの一部として処理されるため、JSのメインスレッドを一切汚さない。

しかし、ここで一つ重要な注意点がある。デフォルトの `:invalid` は、ユーザーが入力し始めた瞬間から「エラー状態」を返してしまうという点だ。これではユーザー体験(UX)として最悪だ。

「初期状態」の罠を回避するアーキテクチャ

ユーザーが何も入力していない空の状態(`:placeholder-shown`)と、入力完了後のエラー状態を分離するのは、現場で最もよく遭遇する課題だ。これらを組み合わせることで、堅牢なバリデーションUIが完成する。

/

  • 構造的なアプローチ:
  • :placeholder-shown が「未入力」を検知し、
  • :not(:placeholder-shown):invalid が「入力済みだが不正」を検知する。
  • これにより、初期表示で真っ赤な警告が出るのを防ぐ。

/

.field {
display: flex;
flex-direction: column;
margin-bottom: 1rem;
}

.input {
padding: 0.5rem;
border: 2px solid #ccc;
transition: border-color 0.2s ease;
}

/ 入力済みかつ不正な場合のみ警告色を出す /
.input:not(:placeholder-shown):invalid {
border-color: #ff4d4f;
background-color: #fff2f0;
}

/ 成功時は緑色でフィードバック /
.input:not(:placeholder-shown):valid {
border-color: #52c41a;
}

パフォーマンスとアクセシビリティの境界線

CSSによるバリデーションは強力だが、盲信してはいけない。特に「エラーメッセージの表示」をCSSの `content` プロパティだけで済ませようとするのは避けるべきだ。

1. アクセシビリティの欠落: CSSで表示された擬似要素は、スクリーンリーダーには存在しないものとして扱われることが多い。エラー情報はDOM(`aria-live`領域)に動的に流し込むのが、真に堅牢なUIの正解だ。
2. レンダリング負荷: CSSで大量の `display: none / block` を切り替えると、再レイアウト(Reflow)が発生する。特にフォーム要素が数千行あるような動的な管理画面では、CSSの計算コストを意識する必要がある。

現場で直面する「非同期バリデーション」のジレンマ

「メールアドレスの重複チェック」のようにサーバーサイドと通信が必要なケースはどうするか?

ここで、`:valid` への過度な依存は崩壊する。CSSはサーバーのレスポンスを直接読めないからだ。この場合、アーキテクチャは「ハイブリッド型」にするのが定石だ。

  • ブラウザのネイティブバリデーション: 型や文字数など、クライアントで完結するルールはCSSの `:invalid` に委ねる。
  • JSのカスタムバリデーション: サーバー由来の非同期バリデーションは、JSで `setCustomValidity()` を使い、意図的に `:invalid` 状態へ遷移させる。

// JSでサーバーの結果をネイティブバリデーションに反映させる
const input = document.querySelector(‘#email’);
input.addEventListener(‘blur’, async () => {
const isAvailable = await checkEmailAvailability(input.value);

if (!isAvailable) {
// これにより、CSSの :invalid が自動的に発火する
input.setCustomValidity(‘このメールアドレスは既に使用されています’);
} else {
input.setCustomValidity(”);
}
});

まとめ:なぜこの手法を選ぶのか

CSSの疑似クラスを使いこなすことは、単なるコーディングテクニックではない。それは「ブラウザに処理を返却する」という設計思想だ。

我々が書くべきJSは、ビジネスロジックと通信に集中させるべきであり、UIの状態管理といった「表示上の細部」は、ブラウザのエンジンに最適化されたCSSに任せる。この分業こそが、複雑なWebアプリケーションを破綻させないための、最も泥臭く、そして最も美しいアーキテクチャであると私は確信している。

さあ、あなたのフォームから不要なクラス切り替えのコードを削除し、ブラウザのネイティブな力を信じてみてはどうだろうか。驚くほど軽快で、堅牢なUIがそこには待っているはずだ。

コメント

タイトルとURLをコピーしました