おい、調子はどうだい?
最近、社内のデザインレビューやコードレビューをしていて「おっ」と感心させられることが増えた反面、フォーム周りの実装を見ると「あぁ、もったいないな……」と感じる場面にまだまだ遭遇するんだよね。
例えば、入力必須のフィールドにわざわざHTMLへ `required` 属性をつけているのに、CSS側ではご丁寧にわざわざ `.is-required` なんていうカスタムクラスを親要素やinputにつけてスタイリングしているコード。あれを見るたびに、「いやいや、ブラウザのネイティブ機能をもっと信じてやろうぜ!」って喉まで出かかっているんだ。
今回は、フロントエンドの中級からもう一歩先の「本当の意味でモダンなCSS設計」へステップアップしたいキミに向けて、`:required` と `:optional` という強力な擬似クラスについて徹底的に解説しようと思う。これらを使いこなせるようになると、JavaScriptのバリデーションライブラリに頼り切らなくても、ブラウザのステートと完全に同期した美しいUIが驚くほど少ないコード量で書けるようになる。
現場のリアルな泥臭いノウハウも含めて話していくから、最後までコーヒーでも飲みながら付き合ってくれ。
—
なぜ今、`:required` と `:optional` なのか?
実務の現場でフォームを実装するとき、UX(ユーザー体験)の観点から「どの項目が必須で、どの項目が任意なのか」をユーザーに明確に伝えるのは鉄則中の鉄則だ。昔の僕らは、次のようなアプローチをよく取っていた。
HTMLの属性に `required` を書きつつ、CSS側でも `.is-required` を付与する。これ、一見すると普通に見えるかもしれない。でも、「情報が二重管理されている」ということに気づくだろうか? HTMLの書き忘れや、JS動的生成時にクラスのつけ忘れが起きる温床になるし、何よりコードが汚れる。
ここで思い出してほしいのが、ブラウザのネイティブ機能だ。ブラウザはすでに、HTMLの `required` 属性の有無を内部でしっかりと把握している。だったら、CSS側からそのステートを直接覗き見してやればいい。それを行ってくれるのが `:required` と `:optional` 擬似クラスというわけだ。
ブラウザの裏側で何が起きているのか?
CSSの擬似クラスの多くは、DOMの状態(State)の変化をリアルタイムで監視している。`:required` も例外ではない。
ブラウザはHTMLパーサーが ``、`
ここで重要なのは、これらの擬似クラスは「属性セレクタ(`[required]`)」とは似て非なるものだという点。
属性セレクタは「その属性がHTML上に存在するかどうか」の静的なマッチングに過ぎない。しかし、`:required` や `:optional` は、ブラウザのフォームバリデーションエンジンと深く結びついており、動的なステート変化に対してシームレスに反応する。
さらに、これらは後述する `:invalid` や `:valid`、そしてユーザーが実際に操作したあとに発火する `:user-invalid` などの状態系擬似クラスと組み合わせることで、真価を発揮する。
—
現場ですぐに使える!実践的コードスニペット
百聞は一見にしかずだ。実際のプロジェクトにそのまま組み込める、クリーンでアクセシブルなフォームのサンプルコードを用意した。コピペして、ローカルのブラウザで動きを確認してみてほしい。
アカウント登録フォーム
このコードの何が美しいって、HTMLに `required` を書くだけで、親要素側の `:has(:required)` や `:has(:optional)` がそれを検知し、自動的に「必須」「任意」のラベル(バッジ)を生成してくれている点だ。
CSSの `:has()` 疑似クラス(リレーショナル疑似クラス)とのコンボ技を使うことで、「親要素にスタイルをつけたい」という実務上の強い要望を、JSを1行も書かずにスマートに解決できる。これぞ現代のフロントエンドの醍醐味だよ。
—
現場でハマりがちな罠とシニアからのアドバイス
さて、ここからが実務経験を積んだ僕からの、ちょっとした「現場の知恵袋」の共有だ。理論がわかっていても、実務では思わぬ落とし穴がある。
1. 「最初から赤く光るフォーム」の悲劇を回避せよ
`:required` を使ったときに、一番やってはいけないアンチパターンがこれだ。
/ やりがちだけどUX最悪なコード /
input:required:invalid {
border-color: red;
}
これをやるとどうなるか? ページを開いた瞬間、ユーザーがまだ何も入力していない状態(空欄の状態)なのに、必須のインプット枠が赤く染まるんだ。これを見たユーザーは「えっ、まだ何も書いてないのにエラーが出てるんだけど……このサイト壊れてる?」とパニックを起こして離脱してしまう。
【解決策】`:user-invalid` の導入
モダンブラウザ(Safari含む)では、ユーザーがそのフィールドを操作し、フォーカスを外した(blurされた)あとのような「ユーザーが入力意図を示したタイミング」で初めてバリデーションエラーを適用する `:user-invalid` や `:user-valid` という、神がかった疑似クラスがサポートされている。
/ ユーザーがインタラクションした後にのみエラーカラーを適用する /
input:required:user-invalid {
border-color: #ef4444;
background-color: #fef2f2;
}
input:required:user-valid {
border-color: #10b981;
}
これを使えば、「まだ入力していない初期状態は通常ボーダー、入力途中で不備があれば赤くなる」という、極めて洗練されたUXを実現できる。レガシーブラウザ(IE11など……もう絶滅したけどね)を気にする必要が薄れた今、積極的に取り入れるべきベストプラクティスだ。
2. スコープとセレクタの肥大化に気をつける
`:has()` との組み合わせは強力無比だけれど、ネストが深すぎたり、DOM構造に強く依存しすぎたりすると、CSSの保守性が逆に落ちることがある。
「コンポーネント指向(BEMなど)」で設計しているなら、モジュール単位で `:required` をスコープ内に閉じ込める意識を持とう。グローバルなセレクタで闇雲に `.form-group:has(:required)` を叩くと、思わぬモーダルの中の細かいフォームまで巻き込んでスタイリングが崩れる原因になる。
—
まとめ
今回は `:required` と `:optional` という、一見地味ながらフォームUIの質を劇的に引き上げる擬似クラスについて解説した。
- HTMLの真実(属性)とCSSの表現を完全に同期させ、二重管理をなくす。
- `:has()` 疑似クラスと組み合わせることで、ラベルや親要素のスタイリングを自動化する。
- `:user-invalid` を組み合わせて、ユーザーに優しい(最初から赤くならない)思いやりのあるバリデーションUXを作る。
フロントエンドの仕事は、動的なJavaScriptを書くだけじゃない。ブラウザが本来持っているネイティブのポテンシャルを最大限に引き出し、軽量でアクセシブルなコードベースを構築することこそ、僕たちプロの腕の見せ所だ。
次のスプリントでのフォーム実装の際、ぜひこの知識を試してみてくれ。コードレビューで後輩から「おお、カッコいい書き方ですね!」って言われること請け合いだよ。それじゃ、また次の現場でお会いしよう!

コメント