モダンブラウザでサポートされているCSSの疑似クラスを使えば、JavaScriptでフォーム状態を監視することなく、CSSで状態を検知できるようになりました。また、HTMLのpattern属性を使えば入力バリデーション機能(※)もつけられます。これらを活用することで以前よりも手軽にインタラクティブなフォームを実現できます。
※あくまでform
要素への入力バリデーションなので、送信される値に対して保証はありません。送信される値をチェックするにはサーバー側のバリデーションが別途必要になります。
さまざまな状態を選択できる疑似クラス
疑似クラス(Pseudo-classes)はCSSで使えるセレクターの一種で特定の状態の要素を指定できます。:hover
も疑似クラスの1つです。::after
は「疑似要素」と呼ばれ、表記は似ていますが呼び名が違うのでご注意ください。
:hover
はホバーした状態の要素を選択できますが、フォームの状態に関する疑似クラスもあります。ここでは今回使用する疑似クラス・疑似要素を紹介します。
:valid, :invalid疑似クラス
:valid
:invalid
疑似クラスはフォームに入力された値が妥当か、あるいは妥当ではない状態を選択できます。たとえば、
<input type="email">
という要素に対して次のようなCSSを設定した場合
input:valid {
border: 1px solid green;
}
input:invalid {
border: 1px solid red;
}
メールアドレスの形式にそったもの入力すると緑色の枠線が、そうでない場合は赤色の枠線で表示されます。
::placeholder疑似要素
::placeholder
を使うとDOM要素ではないプレースホルダーへCSSを適用できます。こちらの分類は疑似要素です。あまり違いを意識しなくても実装上は問題ありませんが、呼称するときは注意してください。
<input type="email" placeholder="Your Email Address">
input::placeholder {
color: green;
}
input::placeholder
とすることで入力の文字色ではなく、プレースホルダーの文字色のみを変更できます。
::placeholder
疑似要素はDOM要素ではないのでdisplay: block
やwidth: 300px
のようなスタイルは適用されません。適用できるのはフォントに関するプロパティや背景に関するプロパティが基本です。実際に利用可能なプロパティは下記に詳しく書かれています。
::first-line (:first-line) - CSS: カスケーディングスタイルシート | MDN
:placeholder-shown疑似クラス
:placeholder-shown
はプレースホルダーが表示されている状態の要素を選択できます。たとえば次のようなコードの時、
<input type="email" placeholder="Your Email Address">
input {
background-color: lightblue;
}
input:placeholder-shown {
background-color: pink;
}
プレースホルダーが表示されている時はピンク色に、入力してプレースホルダーが表示されなくなると水色に背景色が変化します。
:focus-within疑似クラス
:focus-within
疑似クラスは昔からある:focus
疑似クラスと似ていますがちょっと違います。:focus
は自身にフォーカスがあたっている時にしか有効になりません。対して:focus-within
は子要素にフォーカスがあたっている時に有効になります。
<p>
<input type="text">
</p>
p:focus-within {
background-color: pink;
}
<input>
タグを持っている<p>
タグの背景がピンク色に変わります。
:not()疑似クラス
:not()
は引数に指定したものではない、要素を選択します。
<p class="abc">あいうえお</p>
<p class="def">かきくけこ</p>
<p class="abc">さしすせそ</p>
P {
color: blue;
}
p:not(.abc) {
color: red;
}
これはabc
というクラスを持たない<p>
タグという意味のセレクターになります。上記のHTMLの場合、まんなかの「かきくけこ」だけ赤色に変化します。状態をクラス名で管理するときも使えます。
:disabled疑似クラス
disabled
属性のついている要素を選択できます。
<input type="text" disabled>
input:disabled {
border-color: red;
}
disabled
属性がついているときはボーダーの色が赤になります。
インタラクティブなフォームを作る
これらの疑似要素を活用してデモのインタラクティブなフォームを作っていきます。
フォーカスすると小さくなるラベル
要素にフォーカスすると、ラベルが小さくなる部分について解説します。CSSは解説に必要な部分のみを抜き出しています。
▼HTML
<p>
<input type="text" id="name" class="input" placeholder="お名前" required />
<label for="name" class="labelName">お名前 *</label>
<span class="errorMessage messageBox">必須項目です</span>
<span class="OKMessage messageBox">OKです!</span>
</p>
▼CSS
p {
display: flex;
flex-direction: column;
}
p:focus-within .labelName {
transform: translateY(0) scale(0.8);
}
input {
order: 2;
}
input::placeholder {
color: transparent;
}
.labelName {
display: block;
order: 1;
transition: transform 0.2s;
transform: translateY(2rem) scale(1);
transform-origin: 0 100%;
}
見た目ではフォームのラベルが前にありますが、HTML上ではinput
要素の後に記述しています。これは後述のCSSセレクターの都合でこのような構成になっています。並び順をdisplay: flex
のorder
プロパティで変更しています。
placeholder
属性を用いてプレースホルダーを設置していますが、見た目上は使用しないのでcolor: transparent
で透明にしています。
プレースホルダーの代わりにラベルをtransform
プロパティを使って移動させています。ここで:focus-within
疑似クラスを使ってp:focus-within .labelName
というセレクターを作ります。これは子要素にフォーカスが当たっている<p>
タグの.labelName
という要素です。このセレクターでフォーカス時に.labelName
要素をtransform: translateY(0) scale(0.8);
という値に変化させて、左上に小さくなるようにします。
しかし、このままだと入力を終えてフォーカスが外れた時に元に戻ってしまいます。つまり入力内容とラベルが重なってしまう状況です。入力後は戻ってほしくないので、その設定を追加します。
入力後の状態を考えると、プレースホルダーの表示有無で判別できます。これをコードにすると下記のようになります。
input:not(:placeholder-shown) ~ .labelName {
transform: translateY(0) scale(0.8);
}
:not()
疑似クラスと:placeholder-shown
疑似クラスの組み合わせ技です。:placeholder-shown
を:not()
することで入力後の状態を選択できます。
~
を使ったセレクターはあまり見かけないかもしれませんが、一般兄弟結合子とよばれる結合子です。同じ親要素内で、その要素の後にある要素を選択できます。上記例は<inputタグ>
のプレースホルダーが出ていない時、その後ろにある.labelNameという意味のセレクターになります。
これが<label>
タグを<input>
タグの後に配置した理由になります。こうすることで<input>
タグの状態に応じて後ろの.labelName
のスタイルを変更できます。
バリデーションメッセージ
pattern
属性やtype
属性のバリデーションは送信ボタンを押した時に間違っていることをお知らせします。裏を返せば送信するまでユーザーは間違っていることに気づきません。これはあまり良いユーザー体験にならないでしょう。
:invalid
疑似クラスを用いて、インタラクティブにバリデーションメッセージを通知するようにしてみます。
▼HTML
<input
type="Password"
id="password"
class="input"
required
placeholder="パスワード"
pattern="^[0-9A-Za-z]+$"
minlength="6"
maxlength="18"
/>
<label for="password" class="labelName"
>パスワード(半角英数6文字以上18文字以下)*</label
>
<span class="errorMessage messageBox">正しい形式で入力してください</span>
<span class="OKMessage messageBox">OKです!</span>
▼CSS
input:invalid ~ .errorMessage {
display: block;
}
input:invalid ~ .OKMessage {
display: none;
}
input:valid ~ .errorMessage {
display: none;
}
input:valid ~ .OKMessage {
display: block;
}
input:placeholder-shown ~ .messageBox {
display: none;
}
ラベルのときと同じように一般兄弟結合子を用いてinput
タグの状態に応じて.errorMessage
.OKMessage
の表示を切り替えています。未入力時にはメッセージそのものを非表示にしています。
フォーム全体の妥当性を検知する
フォームのどれかに入力内容に不備があると、デフォルトでも送信ボタンを押した時に送信を止めてくれます。しかし、不備がある場合にはそもそも押せないようにした方がユーザー体験は良いでしょう。ボタンを非活性にするにはdisabled
属性を利用します。
disabled
属性の切り替えもCSSだけでできれば良かったのですが、さすがに属性の動的な切り替えはCSSではできません。切り替えはJavaScriptで行います。
フォーム全体の妥当性をチェックするのに、1つ1つのフォームをチェックしていくのも良いですが、実はform:valid
で全体の妥当性を検知できます!
ボタンの活性・非活性
送信ボタンの活性・非活性をform:valid
の取得有無で切り替えます。
const validForm = document.querySelector("form:valid");
validForm
はきちんとフォームに入力されている時に要素を取得できますが、invalid
な状態の時にはnull
になります。
const submitButton = document.querySelector("#submit");
submitButton.disabled = validForm === null;
validForm == null
の時はsubmitButton.disabled
が有効に、そうでないときは無効になります。これを関数としてまとめると、
const validate = () => {
const validForm = document.querySelector("form:valid");
const submitButton = document.querySelector("#submit");
submitButton.disabled = validForm === null;
};
となります。この関数を、初回読み込み時とフォームへの入力時に呼び出します。
// 初期読み込み時に実行
validate();
// フォームに入力されたら、validate関数を実行
document.querySelectorAll("input,textarea").forEach((input) => {
input.addEventListener("input", validate);
});
入力時にバリデーション関数を呼び出せばインタラクティブにボタンの活性・非活性を切り替えられます。
これでインタラクティブなフォームの完成です!
ブラウザサポート状況
今回使った疑似クラスの中でブラウザ対応状況が一番狭いのは:focus-within
です。こちらはChromium化以降のEdgeのみのサポートになります。
また:valid
疑似クラスの<form>
タグへの適用もChromium化以降のEdgeが必要になります。
ブラウザサポート状況
<form>
タグへの:valid
Can I Use | :valid: Applies to <form> elements::placeholder
Can I Use | ::placeholder CSS pseudo-element:placeholder-shown
Can I Use | :placeholder-shown CSS pseudo-element:focus-within
Can I Use | :focus-within CSS pseudo-class
まとめ
ここで挙げたのはその一例ですが、活用の仕方は無数にあります。今回はフォームのインタラクション部分に注目しましたが、より細かい<input>
タグの使い方はこちらの記事『今どきの入力フォームはこう書く! HTMLコーダーが抑えるべきinputタグの書き方まとめ』に詳しく書かれています。
疑似クラスを活用して、より質の高いフォームを、より手軽に作ってユーザー体験を高めていきましょう。