やあ。最近、フォーム周りの実装で「未入力項目のバリデーション表示、またJavaScriptで無駄に判定書いてない?」と後輩のコードを覗き見しては、そっと肩を叩くのが日課になっているシニアエンジニアの私だ。
君も経験があるはずだ。バックエンドから渡された要件定義書に「必須項目には赤字で『必須』と大きく表示してください。未入力のまま送信ボタンを押したらエラー枠を出してください」と平然と書かれているあの絶望感を。昔なら、要素に `required` 属性を付与した上で、JavaScriptのイベントリスナーをゴリゴリ書き、`class` を付けたり外したりしてスタイリングを制御していたはずだ。
だが、現代のCSSをナメてもらっては困る。
ブラウザのネイティブ機能とCSSの擬似クラスを正しく手懐ければ、JavaScriptの力を借りずとも、宣言的に、かつ圧倒的に美しくフォームの状態を制御できる。その代表格が、今回スポットを当てる `:required` 擬似クラス だ。
今日は、この `:required` がブラウザの裏側でどう動いているのか、そして実務の現場でどう使い倒すべきか、私の知見をすべて授けよう。
—
1. `:required` 擬似クラスとは何か?(仕様の核心)
一言で言えば、`:required` は HTMLの `required` 属性を持つフォーム要素(``、` だ。
仕様自体は非常にシンプルで、以下のように書くだけで「必須入力のフィールド」すべてにスタイルを適用できる。
input:required {
/ 必須入力のフィールドに対するスタイル /
}
しかし、ここで中級のエンジニアたちがよくハマる「落とし穴」がある。
「ページを開いた瞬間から、すべての必須フィールドが真っ赤に染まってしまう問題」 だ。
デザイナーの意図は違う。「最初からエラーを出せ」と言っているわけではない。「ユーザーがまだ入力していない状態、あるいはこれから入力するフィールドがどれなのかを優しく示せ」という意味であることがほとんどだ。ページ読み込み直後に全必須項目がエラーカラーでブチギレているフォームなど、ユーザービリティの観点から言えば悪夢でしかない。
ここで、ブラウザが裏側でどう動いているのかを理解する必要がある。
—
2. ブラウザの裏側の話:状態管理とステート系擬似クラスの連携
ブラウザは、HTMLフォーム要素の状態(State)を常に監視している。
ユーザーが触っていない(`(:placeholder-shown)` や `(:unuserd)`)のか、フォーカスしているのか、値が正しいのか(`(:valid)` / `(:invalid)`)、そして必須なのか(`:required`)を、DOMのツリー上で動的にフラグ管理しているんだ。
ここで重要なのは、`:required` 単体は「属性の有無」を見ているだけであり、「ユーザーがまだ入力していない未入力エラー状態」を判定しているわけではないという点だ。
実務において、`:required` は単体で使うものではない。`:invalid` や `:placeholder-shown`、あるいはユーザーが操作した後に付与される `:user-invalid`(または親要素のクラスなど)と組み合わせることで真価を発揮する。
特に、最近のモダンブラウザでサポートが進んでいる `:user-invalid` と `:required` のコンボは、フロントエンド開発の常識を塗り替えるほどのポテンシャルを秘めている。ユーザーが一度そのフィールドを触り、かつバリデーションに失敗した瞬間(=インタラクション後)にだけスタイルが発火する。これぞ、私たちが求めていた挙動だ。
—
3. 【実践】コピペで使えるプロダクション・レディなフォームCSS
理屈はこれくらいにして、現場のコードを見せよう。
以下のコードは、私が実際のプロジェクトでベースとして使っている、アクセシビリティとUXを極限まで高めたフォームスタイリングの断片だ。そのままコピペして検証環境で動かしてみてほしい。
/ ==========================================
1. ベースの必須フィールド表示
========================================== /
/
ここでは「初期表示からエラーを出すな」の鉄則を守り、
あくまで「ここが必須項目である」というインフォメーションに留める。
/
input:required {
border: 1px solid #cbd5e1; / 落ち着いたグレーのボーダー /
background-color: #f8fafc;
}
/ 必須マークのスタイリング /
.badge-required {
font-size: 0.75rem;
background-color: #fee2e2;
color: #991b1b;
padding: 2px 6px;
border-radius: 4px;
font-weight: bold;
}
/ ==========================================
2. インタラクション後の動的バリデーション
========================================== /
/
:user-invalid が使えるモダンブラウザ向け:
ユーザーが入力し始めたのにバリデーションに違反している場合のみ発火
/
input:required:user-invalid {
border-color: #ef4444; / 鮮やかなエラーカラー /
background-color: #fef2f2;
}
/
エラーメッセージの制御:
初期状態では非表示にし、invalidかつ必須条件を満たさない場合にのみ表示する
/
.error-message {
display: none;
color: #ef4444;
font-size: 0.875rem;
margin-top: 4px;
}
/ ユーザーが操作した結果、無効な値が入っている場合にエラーメッセージを出す /
input:required:user-invalid ~ .error-message {
display: block;
}
/ ==========================================
3. 入力成功時のフィードバック
========================================== /
/
正しく入力された場合のスタイリングもCSSだけで完結させる
/
input:required:user-valid {
border-color: #10b981;
background-color: #f0fdf4;
}
—
4. シニアから後輩へ贈る、現場の知見とTips
このコードを実装に組み込むにあたって、いくつか現場特有の「罠」と対策を伝えておこう。
トラブルシューティング 1: 古いブラウザ(Safariの一部バージョンなど)対策
`:user-invalid` は非常に強力だが、すべてのレガシー環境で完璧に動くわけではない。その場合は、フォールバックとして従来の `:invalid` を組み合わせるか、JavaScriptでフォーム送信時に親フォームに `was-validated` などのクラスを付与する手法とハイブリッドで運用するのが実務の定石だ。
しかし、モダンブラウザのシェアを考えれば、`:required` を軸にしたCSS設計に移行するコストは十分にリターンがある。
トラブルシューティング 2: `novalidate` 属性の重要性
HTMLの `

コメント