おい、調子はどうだ?
今日も元気にフォームのバリデーション実装で頭を悩ませているところか。いや、分かるぞ。バックエンドから返ってきたエラーメッセージの出し分け、JavaScriptでのイベントリスナーの嵐、`touched` や `dirty` といった状態管理のフラグ……。気づけばフロントエンドのコードが、ただの入力チェックのボイラープレートで埋め尽くされている。
「もっとスマートに書けないのかよ」
そんな君の心の声を、俺はエンジニア席の後ろからずっと聞いていた。
結論から言おう。君が夜な夜な書いているそのJSのバリデーションロジック、実はCSSのネイティブ機能だけで、半分以上エレガントに置き換えられるとしたらどうする?
今回は、HTML5のネイティブバリデーションと強力に連携する、`:invalid` 疑似クラスについて徹底的に掘り下げていく。ブラウザの裏側の挙動から、実務で絶対にハマる「あの罠」の回避策まで、シニアの俺がすべて叩き込んでやるから、コーヒーでも飲みながらじっくり読んでくれ。
—
1. `:invalid` 疑似クラスの正体と、ブラウザの裏側の話
まず、`:invalid` とは何か。公式ドキュメント的な説明をサラッとしておくと、「HTMLのバリデーション属性(`required`, `type=”email”`, `pattern` など)のルールを満たしていないフォーム要素」にマッチする疑似クラスだ。
だが、ここで中級のエンジニアたちが最初に引っかかる罠がある。
「おい、ページを開いた瞬間、まだ何も入力していない空の必須フィールド(`required`)にいきなり `:invalid` が適用されて、画面中が真っ赤になったぞ!これじゃ使えねぇよ!」
そう、鋭いな。それこそが、ブラウザが裏側でどうDOMを評価しているかを知るべきポイントだ。
ブラウザはどうやって状態を判定しているのか?
ブラウザのエンジン(BlinkやGeckoなど)は、フォーム要素の「妥当性(Validity State)」を常時トラッキングしている。HTML5の制約検証API(Constraint Validation API)の内部ステータスだ。
要素が生成された瞬間、`required` がついているのに値が空であれば、ブラウザは心の中でこう叫ぶ。
「おい、ここ必須だぞ。空だからルール違反(`ValidityState.valueMissing = true`)だな!」
結果として、要素は `:invalid` の烙印を押される。
ここにスタイリングを直結させると、ユーザーがまだ入力すら始めていないロード直後から、エラーの赤い枠線で警告されるという最悪のUXが完成するわけだ。……おい、笑い事じゃないぞ。実務の現場でも、この罠に気づかずに実装してQAチームに差し戻されているやつを何人も見てきた。
じゃあ、どうするか?
ここで登場するのが、CSSとHTMLの「状態」を組み合わせるテクニックだ。
—
2. 現場で使える! `:invalid` のスマートな飼い慣らし方
実務で `:invalid` を使うときの鉄則は一つ。
「ユーザーが操作したあと(あるいは、送信を試みたあと)、かつ入力中のもの」に対してのみスタイルを適用することだ。
無闇矢鱈に `:invalid` 単体で書くのは素人のやること。プロは、疑似クラスのコンボ技でブラウザの機嫌を取る。
実務でそのまま使える!モダンなフォームスタイリング
以下のコードを見てほしい。コピペして手元の環境で動かしてみれば、その洗練された挙動に感動するはずだ。
/ CSS /
スコープを明確にするためにベースのスタイルを定義 /
.form-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
max-width: 400px;
font-family: sans-serif;
}
.form-group input {
padding: 0.75rem;
font-size: 1rem;
border: 1px solid #ccc;
border-radius: 4px;
outline: none;
transition: border-color 0.2s, box-shadow 0.2s;
}
/ 1. 通常時のフォーカス状態 /
.form-group input:focus {
border-color: #0066cc;
box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.15);
}
/
2. 【重要】ユーザーが実際に「入力中(user-invalid)」であり、
かつフォーカスが外れたタイミング(あるいは送信試行後)にエラー表示をする。
ここで :user-invalid を使うのが令和のフロントエンドの最適解だ。
/
.form-group input:user-invalid {
border-color: #d32f2f;
background-color: #fdf2f2;
}
/ 3. エラーメッセージはデフォルトで隠しておく /
.form-group .error-message {
display: none;
font-size: 0.875rem;
color: #d32f2f;
}
/ 4. ユーザーが不正な入力をしている最中だけ、メッセージをスッと出現させる /
.form-group input:user-invalid ~ .error-message {
display: block;
}
—
3. 次世代の切り札:`:user-invalid` との使い分け
お気づきだろうか。先ほどのコードで、俺はあえて `:invalid` ではなく `:user-invalid` という、一見すると見慣れない疑似クラスを使った。
ここが今回の記事で一番伝えたい、チーフアーキテクトからの最大のギフトだ。
`:invalid` と `:user-invalid` の決定的な違い
- `:invalid`:
要素のバリデーションルールが満たされていない「客観的な事実」だけでマッチする。ユーザーがその入力欄に触れていようがまい関係ない。
- `:user-invalid`:
`:invalid` の条件に加え、「ユーザーがその要素を操作した(インタラクションがあった)」という文脈を含めてマッチする。具体的には、一度フォーカスして値を変更し、そのバリデーションが破綻した時だ。
この `:user-invalid`(およびその対である `:user-valid`)は、Selectors Level 4で策定されたモダンな疑似クラスで、主要ブラウザでも完全に対応が進んでいる。
これによって何が嬉しいか?
「最初から赤枠が出ているストレスフルなUI」を、CSSの一行の書き換えだけで完璧に防げるようになる。 JavaScriptで「`is-touched` クラスをstate管理して……」なんて泥臭いコードを書く必要は、もう極めて少なくなったんだ。
—
4. チーフアーキテクトが教える、実務での注意点と落とし穴
さて、ここまで聞くと「じゃあ全部CSSに任せてJSのバリデーションは捨てようぜ!」と思うかもしれないが、焦るな。現場のシステムはそんなに単純じゃない。
最後に、実務で `:invalid` / `:user-invalid` を扱う上での注意点をいくつか共有しておこう。
1. フォーム全体の送信制御(`form:invalid`)の罠
実は、フォーム要素だけでなく `

コメント