CSSの:user-invalidで、間違えた欄だけ目立たせる方法

​​CSS​HTML,Playwright,フォーム

入力欄の枠を赤くするセレクターを :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 で書いたページでは、開いた直後に赤い欄はありません。

2つのフォームを縦に並べた画面。上は「:invalid の場合(開いた直後)」で、お名前とメールアドレスの欄が赤い枠と薄い赤の地になり、下に「お名前を入れてください」「メールアドレスの形で入れてください」が出ている。下は「:user-invalid の場合(開いた直後)」で、3つの欄はどれも灰色の枠のまま、エラー文は出ていない
同じフォームを開いた直後の比較。:invalid は何も入れる前から必須の欄が赤くなり、:user-invalid は赤くならない

コード全体

HTML では、入力のルールを属性で書きます。エラー文は、欄ごとに <p class="error"> で入れておきます。

html
<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 は省いています。

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 です。

上の帯に「操作 0回目/3回」と「赤い枠の欄 0個」が出ている。マウスポインターがメール欄へ動いてクリックし、「neko」と1文字ずつ打つ。Tab キーで離れると「操作 1回目/3回」と「赤い枠の欄 1個」がピンクの地になり、メール欄が赤い枠になって、下に「メールアドレスの形で入れてください」が出る。2回目はメール欄に戻って「@example.com」を1文字ずつ打ち足す。「neko@e」まで打った時点で枠が灰色に戻ってエラー文が消え、「赤い枠の欄 0個」がピンクの地になる。「neko@example.」で一度赤い枠に戻り、「c」を打つとまた灰色になる。3回目は「登録する」を押し、空のお名前の欄が赤い枠になって「赤い枠の欄 1個」になる。お名前の欄の下には、ブラウザーの「Please fill out this field.」の吹き出しも出る
メール欄の入力と、送信ボタンを押したときの動き。上の帯と説明の行、マウスポインターの絵は GIF 用に重ねたもの。最後の吹き出しは Chromium が出す入力チェックのメッセージ

送信ボタンを押すと、間違っている欄がまとめて赤くなる

一度も触っていない欄も、送信しようとした時点で判定されます。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 から使えます。

css
.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 属性を付けた欄に使います。

​Posted by 86