やあ、お疲れ。今日も元気にコード書いてるかい?
リポジトリのPull Requestを見ていて、ふと目にとまったんだよね。「あれ、なんでこのフォームの任意入力欄、わざわざ自前で `.is-optional` なんて冗長なクラスを付与して制御してるんだろう?」って。
君も経験ないかい? フォームのスタイリングを任されたとき、必須項目(`required`)にはドヤ顔で赤字の「必須」ラベルをつけるのに、その他大勢の「任意項目」にはクラス名をベタ貼りしたり、あるいは何もスタイリングせずに放置したり……。
でもね、現代のCSSには、ブラウザが標準で用意してくれている強力な武器があるんだ。それが今回焦点を当てる `:optional` 疑似クラス だ。
今回は、このあまりスポットライトを浴びないけれど、知っていればフォーム実装のイライラを半分に減らしてくれる `:optional` について、ブラウザの裏側の動きから実務で即座に使えるテクニックまで、徹底的に解説していこう。
—
1. `:optional` 疑似クラスの正体と、ブラウザの裏側の話
まず、仕様の定義から正確におさらいしておこう。
`:optional` 疑似クラスは、HTMLのフォーム要素(``, `
「え、それって単に何も指定してないデフォルトの状態ってこと?」
そう思った君、半分正解で半分不正解だ。
重要なのは、これが単なる「クラスなしセレクタの言い換え」ではなく、HTMLのバリデーション状態(Constraint Validation API)と密に連動している状態セレクタだという点だ。
ブラウザはどうやって処理しているのか?
ブラウザのレンダリングエンジン(BlinkやGeckoなど)は、DOMツリーが構築される際、各フォーム要素の属性を走査している。
ここで `required` 属性が存在しない、かつ `disabled` でない要素を見つけると、内部的にその要素へ「オプショナル(任意)」のフラグを立てる。
これの何が素晴らしいかって、JavaScriptで動的に `required` 属性が追加・削除された瞬間に、ブラウザが勝手にマッチ状態を切り替えてくれることだ。
もし自前で `.is-optional` のようなクラスを管理していたら、JS側でバリデーションの変更を監視して、classの付け替えをせっせと実装しなきゃいけない。そんなボイラープレートコード、もう書きたくないだろ? `:optional` を使えば、ブラウザのエンジンがその面倒な状態管理をタダで裏でやってくれるのさ。
—
2. 実務で使える! `:optional` の実践的コードパターン
百聞は一見に如かず。実務の現場でそのままコピペして使える、洗練されたCSSのコードスニペットを見ていこう。
今回は、SassやCSS Modulesを使っているモダンな開発現場を想定して、ネスト構造で記述するね。
/ ==========================================
フォーム全体のコンテナ
========================================== /
.form-group {
margin-bottom: 1.5rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
/ ==========================================
ベースの入力フィールド
========================================== /
.form-input {
padding: 0.75rem 1rem;
font-size: 1rem;
border: 1px solid #cbd5e1;
border-radius: 0.375rem;
background-color: #ffffff;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
/ フォーマット時の共通挙動 /
&:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
}
/ ——————————————
★ ここが主役::optional 疑似クラスの活用
—————————————— /
/ パターン1: 任意項目であることがひと目でわかる控えめな背景色を付与 /
&:optional {
background-color: #f8fafc; / 少しグレーがかった背景で「入力しなくても怒られない安心感」を演出 /
}
/ パターン2: 任意項目であっても、ユーザーが文字を入力し始めたら(入力中)通常の白背景に戻す /
&:optional:not(:placeholder-shown) {
background-color: #ffffff;
border-color: #94a3b8;
}
}
/ ==========================================
ラベルのスタイリングと連動
========================================== /
.form-label {
font-size: 0.875rem;
font-weight: 600;
color: #334155;
/ 必須項目のラベルには赤色の「必須」バッジをつけるアプローチ /
&.is-required::after {
content: “必須”;
margin-left: 0.5rem;
padding: 0.125rem 0.375rem;
font-size: 0.75rem;
color: #ef4444;
background-color: #fee2e2;
border-radius: 0.25rem;
}
}
/
裏技的アプローチ:
HTML側にバッジ用のクラスを書き忘れた場合でも、
:optional の否定形(つまり :required)を利用してCSSだけで必須マークを出すことも可能!
※ただし、実務では明示的にHTML側で制御するほうが安全な場合が多いので参考までに。
/
このコードの何が優れているのか?
1. ユーザーへの心理的負荷の軽減: 必須項目が厳格な見た目(白背景・シャープなボーダー)であるのに対し、`:optional` を使った項目はほんのりグレーの背景にすることで、「ここは自分のペースで埋めていいんだな」という視覚的な手がかりを無意識のうちに与えられる。
2. `:not(:placeholder-shown)` とのコンボ: ユーザーが実際にテキストを入力し始めた瞬間、`:optional` のままでありながらも背景が白く変化する。これにより、「今、入力作業を行っているアクティブなフィールド」が際立ち、UIの洗練度がグッと増す。
—
3. 現場のシニアとして伝えておきたい「罠」と注意点
さて、ここまでベタ褒めしてきた `:optional` だけど、実務で使う際にはいくつか泥臭いハマりどころがある。ここを抑えておかないと、コードレビューで容赦なく突っ込まれるから注意してくれ。
ハマりどころ 1: 「何も属性がない要素」もターゲットになる
`:optional` は、「`required` がない要素すべて」にマッチする。
つまり、`` や ``、さらには `` のような、そもそもバリデーションの概念すらないボタン要素まで `:optional` の世界に巻き込まれてしまうことがある。
対策:
CSSセレクタを記述する際は、単に `input:optional` と書くのではなく、テキスト入力系に絞るか、明確にクラスを併用しよう。
/ 悪い例:ボタンやチェックボックスまで巻き込んでしまう /
input:optional { … }
/ 良い例:テキスト入力を受け付けるタグに絞り込む /
input[type=”text”]:optional,
input[type=”email”]:optional,
textarea:optional {
/ ここにスタイリングを書く /
}
ハマりどころ 2: デザイナーとの共通認識
「任意項目の背景色を変える」という仕様は、UI/UXの観点で好みが分かれる。デザイナーによっては「すべての入力欄は最初は白背景であるべきだ」と主張する人も多い。
だから、`:optional` を使うときは、単に技術的なロマンだけで実装するのではなく、「ユーザーがどの項目をスキップしていいか迷わないためのデザインシステムの一環」として、チームで合意を取ってから導入してほしい。
—
まとめ:ネイティブの機能に頼る美学
フロントエンドのトレンドはめまぐるしく変わる。CSSの機能も毎年新しいものが追加されている。
その中で、外部のJavaScriptライブラリや冗長なクラス名に頼らず、ブラウザが標準で提供している `:optional` のような疑似クラスを使いこなせるかどうかが、「ただ動くコードを書くエンジニア」と「保守性が高く軽量なコードを設計するプロのエンジニア」の分かれ道だ。
今日から君のプロジェクトにあるフォームを少し覗いてみてほしい。
「あ、ここ、無駄なクラスで任意項目を制御してるな」と思ったら、ぜひこの `:optional` に置き換えて、コードをすっきりとリファクタリングしてみてくれ。
君の書くCSSが、より美しく、よりロジカルになることを期待しているよ。それじゃ、また次のコードレビューで!

コメント