こんにちは。プロダクトのフロントエンドを支える君なら、フォームのUI設計で「プレースホルダーと入力済みテキストのスタイリング問題」に一度は頭を悩ませたことがあるはずだ。
「プレースホルダーの色を薄くしたい」という要件に対して、かつては専用のベンダープレフィックスだらけの非標準な疑似要素(`::-webkit-input-placeholder`など)を書き殴り、JavaScriptで入力値を監視してクラスを付け替える……なんて泥臭いハックを日常茶飯事のようにやってきた。
だが、もうそんな無駄なボイラープレートコードを書く必要はない。
現代のCSSには、HTMLの「状態」をスマートに捉える強力な武器がある。それが今回解説する `:placeholder-shown` 疑似クラス だ。
今日は、この `:placeholder-shown` の基本から、実務の現場で明日から使える「浮動ラベル(Floating Label)パターン」のような実践的かつ洗練されたテクニックまで、シニアの視点で徹底的に叩き込んでいこう。
—
1. `:placeholder-shown` とは何か?(仕様とブラウザの裏側の挙動)
まず大前提として、多くのエンジニアが混同しがちな点から整理しておこう。
- `::placeholder` は、プレースホルダーそのもの(テキストの見た目)を装飾する疑似要素。
- `:placeholder-shown` は、プレースホルダーが現在表示されている状態の `input` や `textarea` そのものを選択する疑似クラス。
この違いは極めて重要だ。
ブラウザの内部処理を覗いてみると、`:placeholder-shown` は「そのフォーム要素の内部テキストが空、かつ、プレースホルダー属性(`placeholder`)を持っていて、現在それが可視化されている状態」という DOM の状態をリアルタイムで監視し、マッチ判定を下している。
つまり、この疑似クラスを使えば、「いまユーザーが文字を入力している最中なのか、それとも何も入力されていない初期状態なのか」を、一文字もJavaScriptを書かずにCSSだけで完全に判別できるようになる。これこそが、このセレクタの真骨頂だ。
—
2. 実務で即戦力になるコード:浮かび上がるラベル(Floating Label)
百聞は一見にしかず。実務で一番よく遭遇する「おっ、これいいな」と思わせるUI、入力欄に文字を入るとプレースホルダーが上部にシュッとスライドしてラベルに変わる「フローティング・ラベル・パターン」を `:placeholder-shown` を使って実装してみよう。
余計なJSは一切なし。CSSのコンビネーションだけで美しく完結させる。
/ — フォーム全体のコンテナ — /
.input-group {
position: relative;
margin-top: 1.5rem;
font-family: sans-serif;
}
/ — 入力フィールドの基本設計 — /
.input-field {
width: 100%;
padding: 1rem 0.75rem 0.5rem;
font-size: 1rem;
border: 1px solid #ccc;
border-radius: 4px;
outline: none;
background-color: transparent;
transition: border-color 0.2s ease;
}
.input-field:focus {
border-color: #2563eb; / フォーカス時は鮮やかな青に /
}
/ — ラベルの初期状態(プレースホルダーが表示されている=入力が空の時) — /
.input-label {
position: absolute;
left: 0.75rem;
top: 1rem;
color: #6b7280;
font-size: 1rem;
pointer-events: none; / ラベルをクリックしてもinputにフォーカスが抜けないようにする配慮 /
transform-origin: left top;
transition: transform 0.2s ease, color 0.2s ease;
}
/
【ここが核心】
inputにプレースホルダーが表示されている(=文字が入力されていない)間は、
ラベルを元の位置(中央付近)に鎮座させておく。
/
.input-field:placeholder-shown + .input-label {
transform: translateY(0) scale(1);
}
/
【状態の反転】
inputからプレースホルダーが消えた(=文字が入力された、またはフォーカスされて入力待ちになった)瞬間、
隣接するラベルが上に浮き上がり、フォントサイズが縮小する。
/
.input-field:not(:placeholder-shown) + .input-label,
.input-field:focus + .input-label {
transform: translateY(-1.2rem) scale(0.75);
color: #2563eb;
}
このコードの美しポイント
1. `placeholder=” “(半角スペース)` のテクニック:
あえて空のスペースを指定することで、プレースホルダーの文字自体は非表示にしつつ、「プレースホルダーが存在し、かつ表示されている状態」というトリガーだけを安全に引いている。これがプロの現場の知恵だ。
2. `:not(:placeholder-shown)` と `:focus` の組み合わせ:
ユーザーが入力中でフォーカスが外れていても、文字が入っていればラベルが浮いた状態を維持できる。ここを `:focus` だけにしてしまうと、フォーカスを外した瞬間にラベルが文字に重なって沈み込んでしまい、目も当てられないUXになる。
—
3. 実務でハマりがちな「落とし穴」と回避策
シニアとして後輩によく注意するのが、この疑似クラスを扱う際の「ブラウザの気まぐれ」や「仕様の罠」だ。
落とし穴1: `placeholder` 属性が必須であること
`:placeholder-shown` は、文字通り `placeholder` 属性が存在する要素 に対してのみ機能する。属性そのものが抜けていると、いくらCSSを書いても微動だにしない。「動かない!」と騒ぐ前に、HTML側の属性を確認するのが鉄則だ。
落とし穴2: カスタムセレクトボックスやライブラリとの相性
ReactやVueなどのモダンなUIライブラリ(MUIやChakra UIなど)を自前で実装・拡張する際、サードパーティ製のカスタムコンポーネントの内部構造(Shadow DOMなど)に阻まれてスタイルが当たらないことがある。そんなときは、コンポーネントが最終的に吐き出す素の HTML構造(`input` や `textarea`)までしっかりデベロッパーツールで確認しよう。
—
まとめ:モダンCSSで「脱・無駄JS」を推し進めよう
フォームのちょっとしたインタラクションのために、イベントリスナーを登録して、Stateを監視して、クラスを付け替えて……というコードを書く時代は、もう終わりつつある。
ブラウザのネイティブな状態管理(DOMの状態)をCSS側で直接フックできる `:placeholder-shown` は、パフォーマンスの観点からも、コードの保守性の観点からも、現代のフロントエンド開発において最強の武器の一つだ。
次にフォームUIを実装する機会があれば、無駄なJSを書く前に、まずこのセレクタでスマートに解決できないか考えてみてほしい。君の書くコードベースが、より洗練されたものになるはずだ。

コメント