CSSの:user-invalidで、間違えた欄だけ目立たせる方法
入力欄の枠を赤くするセレクターを :invalid から :user-invalid に変えると、まだ何も入れていない欄は赤くならず、利用者が入力した欄と、送信ボタンを押したときのフォームの欄のうち、間違っているものだけが赤くなります。JavaScript は使いません。HTML の required や type="email"、pattern で書いた入力のルールを、そのまま使います。
検証環境は Ubuntu 24.04、Node.js 22.23.4、Playwright 1.63.0、Chromium 153 です。HTML と CSS だけのページを Playwright で開き、マウスとキーボードで操作して、matches() と getComputedStyle() の結果を記録しました。Firefox と Safari、スマートフォンのブラウザー、スクリーンリーダーでの読み上げは試していません。各ブラウザーの対応バージョンは、互換性データ(@mdn/browser-compat-data 8.1.8)の値です。
:invalid だと、開いた時点で赤くなる
:invalid は、入力のルールに合っていない欄に当てはまります。空の required の欄も、ルールに合っていない欄です。そのため、:invalid で枠を赤くすると、利用者がまだ何もしていないうちから、必須の欄が赤くなります。
検証環境で、お名前(required)、メールアドレス(type="email" と required)、郵便番号(pattern だけ)の3つの欄があるフォームを開きました。:invalid で枠を赤くしたページでは、開いた直後にお名前とメールアドレスの2つが赤くなり、エラー文も出ました。同じフォームを :user-invalid で書いたページでは、開いた直後に赤い欄はありません。

コード全体
HTML では、入力のルールを属性で書きます。エラー文は、欄ごとに <p class="error"> で入れておきます。
<form action="done.html">
<div class="field">
<label for="name">お名前</label>
<input id="name" name="name" required placeholder="山田 花子">
<p class="error">お名前を入れてください</p>
</div>
<div class="field">
<label for="email">メールアドレス</label>
<input id="email" name="email" type="email" required placeholder="neko@example.com">
<p class="error">メールアドレスの形で入れてください</p>
</div>
<div class="field">
<label for="zip">郵便番号</label>
<input id="zip" name="zip" pattern="[0-9]{3}-[0-9]{4}" placeholder="123-4567">
<p class="error">「123-4567」の形で入れてください</p>
</div>
<button>登録する</button>
</form>
CSS は次のとおりです。枠の太さや余白など、見た目だけの CSS は省いています。
.field input:user-invalid {
border-color: #d85878;
background: #fff5f7;
}
.field .error {
display: none;
color: #c03050;
}
.field:has(input:user-invalid) .error {
display: block;
}
1つ目のルールで、間違っている欄の枠を赤(#d85878)にし、地を薄い赤にします。エラー文はふだん display: none で隠しておき、:has() で「:user-invalid の入力欄を中に持つ .field」を選んで、その中のエラー文だけを出します。
赤くなるのは、入力して欄を離れたとき
HTML の仕様では、入力欄は「user validity」という真偽値を持っていて、最初は false です。利用者が値を変えて確定すると true になります。:user-invalid の定義は、この値が true で、入力のルールに合っていない欄に当てはまる、というものです。
検証環境の Chromium で、いつ赤くなるかを1つずつ確かめました。
| 操作 | 欄の状態 |
|---|---|
| お名前をクリックし、何も入れずに Tab キーで離れる | 赤くならない |
| メール欄に「neko」と打つ(まだ欄の中にいる) | 赤くならない |
| そのまま Tab キーでメール欄を離れる | 赤くなり、エラー文が出る |
| メール欄に戻り、「@example.com」を打ち足す | 「neko@e」まで打った時点で、欄の中にいるまま赤が消える |
| 欄の中にいるまま、Backspace で「@example.com」を消す | 欄の中にいるまま、また赤くなる |
| お名前に「山田」と打ってから消し、Tab キーで離れる | 赤くなる |
クリックして通り過ぎただけの欄は赤くなりません。入力している途中も赤くなりません。値を変えて欄を離れたときに、初めて赤くなりました。いったん赤くなった欄は、そのあとは欄を離れなくても判定が変わり、直せば赤が消え、また間違えれば赤くなります。1文字ずつ打って確かめると、「neko@example.」のように最後が「.」になった時点で一度赤くなり、「c」を打つとまた消えました。検証環境の Chromium では、打って消して元の空に戻した欄も、離れると赤くなりました。
メール欄に「neko」と打って離れ、戻って直し、最後に何も入れていないお名前のまま「登録する」を押したときの動きが、次の GIF です。

送信ボタンを押すと、間違っている欄がまとめて赤くなる
一度も触っていない欄も、送信しようとした時点で判定されます。HTML の仕様のフォームを送信する手順には、送信する前に、フォームの中の欄すべての user validity を true にする、と書かれています。
検証環境で、開いたばかりのフォームの「登録する」を押すと、空のお名前とメールアドレスが両方赤くなり、エラー文も2つ出ました。ページは送信されず、フォーカスはお名前の欄に移りました。お名前とメールアドレスを正しく入れ、郵便番号だけ「1234567」(ハイフンなし)にして押したときは、郵便番号だけが赤くなりました。
このとき、ブラウザー自身も吹き出しを出して、入力の間違いを知らせます。検証環境の Chromium では、お名前の欄の下に「Please fill out this field.」の吹き出しが出て、自分で用意したエラー文と重なって表示されました。
form.reset() でフォームをリセットすると、赤い枠とエラー文は消え、開いた直後と同じ状態に戻りました。仕様でも、入力欄のリセットは user validity を false に戻す、と書かれています。
:user-valid で緑にするなら、任意の空欄に気をつける
正しく入力できた欄に当てはまる :user-valid もあります。これを使うと、正しく入れた欄の枠を緑にできます。
ただし、送信ボタンを押した時点で、すべての欄が判定の対象になります。検証環境で、何も入れずに「登録する」を押すと、required を付けていない空の郵便番号の欄が :user-valid に当てはまりました。空の任意の欄は、入力のルールに合っているからです。:user-valid で緑にすると、空のままの任意の欄も緑になります。
:user-invalid が使えないブラウザー向けの書き方
互換性データでは、:user-invalid と :user-valid は Chrome と Edge が 119、Firefox が 88、Safari が 16.5(iOS の Safari も 16.5)から使えます。:has() は Chrome と Edge が 105、Firefox が 121、Safari が 15.4 からです。
これより古いブラウザーにも合わせたいときは、:placeholder-shown と組み合わせる書き方があります。:placeholder-shown は、プレースホルダーが表示されている欄、つまり空の欄に当てはまります。互換性データでは Chrome 47、Edge 79、Firefox 51、Safari 9 から使えます。
.field input:not(:placeholder-shown):invalid {
border-color: #d85878;
background: #fff5f7;
}
.field .error {
display: none;
color: #c03050;
}
.field:has(input:not(:placeholder-shown):invalid) .error {
display: block;
}
「空ではなく、ルールに合っていない欄」を選ぶので、開いた直後は赤くなりません。ただ、検証環境で試すと、:user-invalid とは2つの点で動きが違いました。メール欄に「n」と1文字打っただけで、欄の中にいるまま赤くなりました。また、開き直して何も入れずに「登録する」を押すと、空のお名前とメールアドレスの欄は赤くならず、エラー文も出ませんでした。空の欄は :placeholder-shown に当てはまり、このセレクターから外れるからです。:placeholder-shown はプレースホルダーが表示されている欄に当てはまるものなので、この書き方は placeholder 属性を付けた欄に使います。





