【実務・中級編】 擬似クラス :valid と :invalid – CSS実践ガイド

フロントエンドの現場で日々フォームと格闘している君なら、JavaScriptでちまちま正規表現を書いて、`input`イベントのたびにエラー判定して……という泥臭い実装に、一度は嫌気が差したことがあるはずだ。

「これ、本当にJSでやらなきゃいけないんだっけ?」

そう思った君へ朗報だ。モダンブラウザのネイティブ機能であるCSSの `:valid` と `:invalid` 擬似クラスを使いこなせば、HTML5のバリデーション属性(`required`, `pattern`, `minlength` など)と連動して、ブラウザが勝手に状態を判定し、スタイリングまで完結させてくれる。JSのボイラープレートコードをごっそり削ぎ落とせる、まさに現場の秘密兵器だ。

今回は、このネイティブバリデーション連動の極意を、実務で即座に使えるレベルの知見とともに叩き込んでいこう。

—

1. 裏側で何が起きているか? ブラウザのレンダリングメカニズム

まず、ブラウザが裏側でどう動いているかを知る必要がある。なんとなく使っていると「あれ、ページを開いた瞬間にエラーの赤枠が出ちゃったんだけど!」という、デザイナーからの恐怖のフィードバックを受けることになるからだ。

状態の判定アルゴリズム

ブラウザは、フォーム要素(`` や `
3文字以上で入力してください。


※ `novalidate` はブラウザデフォルトのポップアップツールチップを抑制するためにつけている。デザインされたUIでエラー表現を統一したい場合は必須のテクニックだ。

CSS

/ — ベースのスタイル — /
.p-form {
max-width: 400px;
margin: 40px auto;
font-family: sans-serif;
}

.p-form__field {
display: flex;
flex-direction: column;
margin-bottom: 24px;
}

.p-form__label {
font-size: 14px;
font-weight: bold;
margin-bottom: 8px;
color: #333;
}

.p-form__input {
padding: 12px;
font-size: 16px;
border: 2px solid #ccc;
border-radius: 6px;
outline: none;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

/ — エラーメッセージの初期状態(隠しておく) — /
.p-form__error-message {
display: none;
font-size: 12px;
color: #e53e3e;
margin-top: 6px;
}

/
==========================================================================
ここからが本題::user-invalid との組み合わせの妙
==========================================================================
/

/
【アンチパターン】
input:invalid とだけ書くと、ページ読み込み直後から赤枠になるので絶対に避けること!
/

/
【ベストプラクティス】
:user-invalid (または :placeholder-shown との組み合わせ) を使う。
今回は広く普及している `:placeholder-shown` の否定形を用いた実践テクニックを使う。
ユーザーが「一度でもフォーカスして、値を入力し始めた(=プレースホルダーが消えた)」かつ「不正な値」の時だけ発火させる。
/
.p-form__input:not(:placeholder-shown):invalid {
border-color: #e53e3e;
background-color: #fff5f5;
}

/ 正常に入力された瞬間 /
.p-form__input:not(:placeholder-shown):valid {
border-color: #38a169;
background-color: #f0fff4;
}

/ 不正な値の時だけエラーメッセージをスライドイン的に表示 /
.p-form__input:not(:placeholder-shown):invalid ~ .p-form__error-message {
display: block;
}

—

3. シニアが教える、現場のハマり所とハック

上記のコードで使った `:not(:placeholder-shown)` というセレクタのコンビネーション、これが実務では非常に強力なハックになる。

ハックの解説:なぜ `:not(:placeholder-shown)` なのか?

先ほど「ページを開いた瞬間から `:invalid` が発火する」という問題に触れた。
これを回避するモダンな標準仕様として `:user-invalid` という擬似クラスも策定されているが、ブラウザのサポート状況や挙動の微調整(ユーザーがフォーカスを外した瞬間か、入力中かなど)において、現場ではまだコントロールしきれない部分がある。

そこで、「プレースホルダーが表示されている=まだユーザーが入力していない」という事実を利用する。

  • 初期状態:プレースホルダーが表示されている → `:not(:placeholder-shown)` は偽。したがって `:invalid` のスタイルは適用されない。
  • 入力開始:文字を打つとプレースホルダーが消える → `:not(:placeholder-shown)` が真になり、かつ条件を満たしていなければ `:invalid` が適用される!

この一手間を加えるだけで、「最初から赤らんだ不親切なフォーム」を完璧に防ぐことができる。

—

4. まとめ:JSによるDOM操作の呪縛から解放されよう

フォームのバリデーション制御をすべてJSに頼り切っていた時代はもう終わった。
ブラウザのネイティブな仕組みである `:valid` と `:invalid` をCSSのセレクタで適切に制御できれば、以下のメリットが手に入る。

1. パフォーマンスの向上: JSのイベントリスナー(`input`, `blur`など)を毎度仕込む必要がなくなり、メインスレッドが軽くなる。
2. コード量の削減: DOMのクラス付け替え(`element.classList.add(‘is-error’)`など)を記述するボイラープレートから解放される。
3. 宣言的なスタイリング: 「状態」をCSSに任せることで、マークアップとデザインの関心の分離が美しく保たれる。

もちろん、サーバーサイドとの複雑なバリデーション連携や、非同期の重複チェック(「このメールアドレスは既に使われています」など)はJSの領域だ。しかし、「文字数制限」や「必須チェック」「正規表現によるフォーマット確認」といったクライアント側の一次判定は、CSSに任せるのがこれからのモダンなフロントエンドの作法と言える。

さあ、次のスプリントのタスクで、ボッチのJSコードを消し去り、この洗練されたCSSに置き換えてみせよう。君のコードレビューを受ける後輩たちも、きっとそのスマートさに唸るはずだ。

コメント

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