お疲れ。今、ちょうどフォームのバリデーション周りのスタイリングで頭を悩ませていたところか?
「入力値が正しい時に緑色の枠線を出したい」「でも、ページを開いた瞬間からエラーが出たり、全入力欄が真っ緑になったりしてダサい……」そんな泥臭い悩みを抱えている中級エンジニアは、僕のチームにもゴロゴロいる。
今回は、そんなフォームスタイリングのモヤモヤを鮮やかに解消してくれる `:valid` 擬似クラスについて、仕様の裏側から実務で絶対にハマる落とし穴まで、現場の知見を総動員して徹底的に解説してやろう。
—
1. `:valid` 擬似クラスとは何か?(基本のキと仕様の勘違い)
`:valid` は、一言で言えば「HTMLのバリデーションルールをクリアしたフォーム要素にマッチする擬似クラス」だ。
例えば、`` や `pattern` 属性、`min` / `max` 属性などを設定したとき、ユーザーが入力した値がそれらの制約に違反していなければ、この `:valid` が発動する。
だが、ここで中級のエンジニアすら盛大に勘違いしやすい罠がある。
「何も入力されていない状態(空の状態)」は、果たして `:valid` なのか?
答えは「Yes(ただし `required` がついていない場合)」だ。
ここがブラウザの内部処理を理解する上で最初の分水嶺になる。
ブラウザの裏側の動き:Constraint Validation API との状態管理
ブラウザは、裏側で Constraint Validation API という仕組みを使ってフォームの正当性を常に監視している。
フォーム要素は、フォーカスされ、入力され、あるいは検証が走った瞬間、以下のいずれかの状態フラグを持つ。
1. 制約を満たしている状態 = `:valid`
2. 制約に違反している状態 = `:invalid`
3. そもそも制約がない状態 = デフォルトで `:valid` 扱いになる(`required` が無い普通の `` など)
つまり、`required` が指定されていない入力欄は、最初から空であっても `:valid` に該当する。ここを履き違えると、「なんで何も入力してないのにサクセスカラー(緑色のボーダー)がつくんだ!?」という現場のデスマーチの始まりになる。
—
2. 現場で本当に使える実践コード:UXを殺さないスタイリング
では、実務において `:valid` をどう使うべきか。
「ユーザーが入力し始めた瞬間、あるいは入力し終わった瞬間(かつ正当な値のとき)」にだけスタイリングを適用したい。ページを開いた瞬間からフォームが緑だらけなのは、UI/UXの観点から最悪の悪手だ。
ここでは、`required` がある必須項目において、「ユーザーが実際に文字を入力し、かつそれが正しい時」にだけ緑のチェックマークやボーダーを出す、極めて実用的なコードを授けよう。
実務向けHTML & CSS スニペット
/ —————————————————————–
- フォーム全体のベーススタイル
- —————————————————————– /
.p-form {
max-width: 480px;
margin: 40px auto;
font-family: sans-serif;
}
.p-form__group {
margin-bottom: 24px;
display: flex;
flex-direction: column;
}
.p-form__label {
font-size: 14px;
font-weight: bold;
margin-bottom: 8px;
color: #333;
}
/ —————————————————————–
- デフォルトの入力欄スタイル(未入力・または初期状態)
- —————————————————————– /
.p-form__input {
padding: 12px 16px;
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__input:focus {
border-color: #0066cc;
box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.15);
}
/ エラーメッセージは初期状態では隠しておく /
.p-form__message {
font-size: 12px;
color: #d93838;
margin-top: 6px;
display: none;
}
/ —————————————————————–
- ここがキモ! :valid と :invalid の実務的アプローチ
- —————————————————————– /
/
- 1. ユーザーが一度でも触った(:placeholder-shownではない)、
- かつ required の条件を満たしている `:valid` の時だけ緑にする。
- ※ :placeholder-shown はプレースホルダーが表示されている(=未入力)の間だけ真になる擬似クラス。
- これ組み合わせることで「空のときから緑になる問題」を完璧にハックできる。
/
.p-form__input:not(:placeholder-shown):valid {
border-color: #2e8b57; / 落ち着いたグリーン /
background-image: url(“data:image/svg+xml,…”); / 必要ならチェックアイコンを入れる /
}
/
- 2. ユーザーが入力したものの、ルールに違反している(:invalid)かつ
- プレースホルダーが消えている(=何か打ち込んだ)状態の時だけエラー表示する。
/
.p-form__input:not(:placeholder-shown):invalid {
border-color: #d93838;
}
/ 入力が間違っていて、かつフォーカスが外れている、あるいは送信を試みた時などにメッセージを出す /
.p-form__input:not(:placeholder-shown):invalid + .p-form__message {
display: block;
}
—
3. シニアが教える、実務でのハマりどころと知見
上記のコードで使った `:not(:placeholder-shown)` との組み合わせは、僕らが実務で何度も修羅場をくぐり抜けてたどり着いた黄金のパターンだ。これを忘れると、次のようなバグ報告がQAチームから飛んできて金曜日の夜が潰れることになる。
ハマりどころ 1: 「初期状態が緑や赤になる」問題
先ほども言った通り、何もしなくても `` はプレースホルダーが消えていなければ(あるいは `required` がなければ)、ブラウザによっては初期状態から判定を持ったり持たなかったりする。
特に `:invalid` は、ページを開いた瞬間にすべての必須項目が赤くハイライトされるという、ユーザーに対する最高に不親切なUIを作り出してしまう。
対策として、必ず `:not(:placeholder-shown)` や、ユーザーがフォームを一度でも送信しようとしたフラグ(JS側で付与する `.is-submitted` クラスなど)と組み合わせるのが鉄則だ。
ハマりどころ 2: ブラウザごとのデフォルトスタイルの差異
SafariやFirefox、Chromeなどのモダンブラウザは、それぞれ独自に `:valid` や `:invalid` に対して微弱なシャドウや赤枠(主にFirefoxのフォーム送信時)を勝手につけたがる。
「変な赤枠が消えない!」と思ったら、大体こいつらのデフォルト挙動だ。
リセットCSSや、以下のような打ち消しを忘れないようにしよう。
/ ブラウザ独自の余計なバリデーションスタイルをリセット /
.p-form__input {
box-shadow: none;
}
.p-form__input:invalid {
box-shadow: none; / Firefoxの赤枠シャドウ対策 /
}
—
まとめ
`:valid` は単体で使うと「素朴すぎて使い物にならない子」に見えるが、`:not(:placeholder-shown)` や他の状態擬似クラスと組み合わせることで、JavaScriptを1行も書かずにリッチでアクセシブルなリアルタイム・クライアントサイドバリデーションのUIを構築できる強力な武器に化ける。
フロントエンドのアーキテクチャ設計において、「CSSだけで完結できることはCSSに任せ、複雑なステート管理はJSに逃がす」という境界線の引き方は、パフォーマンスの観点からも非常に美しい。
明日からの実装で、ぜひこのテクニックをコードベースに組み込んでみてくれ。コードレビューで後輩から「おっ」と言われること請け合いだ。さて、次のタスクに取り掛かるとしようか。

コメント