フォームのエラー表示は、CSSの :user-invalid を使うと、入力したあとにだけ赤くできます。ページを開いた瞬間から全部の入力欄が赤い、という失敗を避けられます。
使いどころは、入力の形が決まっている項目です。メールアドレスや郵便番号なら、CSSと短いHTMLだけで間違いを知らせられます。空欄のまま送られる場面まで自分の言葉で伝えるなら、短いJSを足します。どちらの場合も、赤だけで終わらせず、直し方を文字で書きます。
この記事の要点
:user-invalidを使うと、人が入力したあとで内容が正しくない入力欄だけを赤くでき、直し方の文も一緒に出せる- 使いどころは、メールアドレスや郵便番号のように形が決まっている項目。空欄の送信まで自分の言葉で知らせるなら短いJSを足す
- 赤い色だけでは伝わらない人がいるので、何をどう直すかを文字で書く。ブラウザでの確認はサーバー側の確認の代わりにならない
完成見本:エラーの知らせ方4種類
見本は自動で、入力する→間違いを知らせる→直す、をくり返します。4だけは、送信ボタンを押したところから始まります。
※ 見本は読み上げソフトでは読まれないようにしています。動きを減らす設定にしている方には、エラーの状態で止まって見えます。実際に使うときは、下のコードのまま使えば読み上げの対象になります。
コピペ用のコード
CSSは、サイトのCSSファイルや、WordPressならテーマの「追加CSS」などに入れます。クラス名の頭の kd-er は、サイトのほかのデザインと名前がぶつからないようにするための目印です。1のCSSの前半(見出しと入力欄)は、2〜4でも使います。1〜3はJSなしで動きます。
1. 赤い枠と、直し方の文
ふだんは文を隠しておき、:user-invalid(人が入力したあとで、内容が正しくないとき)に、枠を赤くして文を出します。+ は「すぐ次の要素」を選ぶ記号なので、HTMLは入力欄のすぐ下に文を書きます。
<div class="kd-er-field">
<label for="kd-er-mail">メールアドレス</label>
<input id="kd-er-mail" type="email" name="email"
autocomplete="email" required
aria-describedby="kd-er-mail-msg">
<p class="kd-er-msg" id="kd-er-mail-msg">「info@example.com」の形で入力してください</p>
</div>/* 2〜4でも使う:見出しと入力欄 */
.kd-er-field label {
display: block;
margin-bottom: 6px;
font-size: 14px;
font-weight: 700;
color: #10283A;
}
.kd-er-field input {
display: block;
box-sizing: border-box;
width: 100%;
height: 48px;
padding: 0 14px;
border: 2px solid #7B8A97;
border-radius: 8px;
background-color: #FFFFFF;
font-size: 16px;
color: #10283A;
}
/* 入力したあとで正しくないとき:枠を赤く */
.kd-er-field input:user-invalid {
border-color: #B91C1C;
}
/* 文は隠しておき、正しくないときだけ出す */
.kd-er-msg {
display: none;
margin: 6px 0 0;
font-size: 14px;
line-height: 1.5;
color: #B91C1C;
}
.kd-er-msg::before {
content: "!";
font-weight: 700;
}
.kd-er-field input:user-invalid + .kd-er-msg {
display: block;
}2. 説明の文が、そのままエラーになる
直し方の文を最初から見せておき、正しくないときだけ赤くして「!」を足します。文が増えないので、入力欄の下の部品が動きません。pattern は入力の形を決める属性で、[0-9]{7} は「数字がちょうど7つ」という意味です。
<div class="kd-er-field">
<label for="kd-er-zip">郵便番号</label>
<input id="kd-er-zip" type="text" name="zip"
inputmode="numeric" pattern="[0-9]{7}"
autocomplete="postal-code" required
aria-describedby="kd-er-zip-hint">
<p class="kd-er-hint" id="kd-er-zip-hint">半角数字7けたで入力してください(ハイフンなし)</p>
</div>.kd-er-hint {
margin: 6px 0 0;
font-size: 14px;
line-height: 1.5;
color: #33495B;
}
.kd-er-field input:user-invalid + .kd-er-hint {
font-weight: 700;
color: #B91C1C;
}
.kd-er-field input:user-invalid + .kd-er-hint::before {
content: "!";
}3. 入力欄の中に ✕ と ✓
入力欄の右に、正しくないときは ✕、正しいときは ✓ を出します。正しいほうは :user-valid で選びます。記号は背景画像(SVG)なので、HTMLはクラスを1つ足すだけです。✕ だけでは直し方が分からないので、1の文と組み合わせます。
<div class="kd-er-field">
<label for="kd-er-url">ホームページのURL</label>
<input class="kd-er-icon" id="kd-er-url" type="url" name="url"
autocomplete="url" required
aria-describedby="kd-er-url-msg">
<p class="kd-er-msg" id="kd-er-url-msg">「https://」から入力してください</p>
</div>.kd-er-field input.kd-er-icon {
padding-right: 44px;
background-repeat: no-repeat;
background-position: right 12px center;
background-size: 20px 20px;
}
.kd-er-field input.kd-er-icon:user-invalid {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23B91C1C' stroke-width='3' stroke-linecap='round'%3E%3Cpath d='M6 6l12 12M18 6L6 18'/%3E%3C/svg%3E");
}
.kd-er-field input.kd-er-icon:user-valid {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23047857' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 13l5 5L20 7'/%3E%3C/svg%3E");
}4. 送信したときに、まとめて知らせる
novalidate でブラウザの吹き出しを止め、短いJSで全項目を確かめます。正しくない項目に aria-invalid="true"(入力が正しくない印)を付けて文を入れ、最初の項目へカーソルを移します。赤い枠は [aria-invalid="true"] で付けます。
<form class="kd-er-form" novalidate>
<div class="kd-er-field">
<label for="kd-er-name">お名前</label>
<input id="kd-er-name" type="text" name="name" autocomplete="name"
required aria-describedby="kd-er-name-tip"
data-msg="お名前を入力してください">
<p class="kd-er-tip" id="kd-er-name-tip"></p>
</div>
<div class="kd-er-field">
<label for="kd-er-to">メールアドレス</label>
<input id="kd-er-to" type="email" name="email" autocomplete="email"
required aria-describedby="kd-er-to-tip"
data-msg="「info@example.com」の形で入力してください">
<p class="kd-er-tip" id="kd-er-to-tip"></p>
</div>
<button type="submit">送信する</button>
</form>.kd-er-form .kd-er-field {
margin-bottom: 16px;
}
.kd-er-form [aria-invalid="true"] {
border-color: #B91C1C;
}
.kd-er-tip {
margin: 6px 0 0;
font-size: 14px;
line-height: 1.5;
font-weight: 700;
color: #B91C1C;
}
.kd-er-tip:empty {
display: none;
}
.kd-er-tip::before {
content: "!";
}
.kd-er-form button {
min-height: 48px;
padding: 0 28px;
border: 2px solid transparent;
border-radius: 8px;
background: #0369A1;
font-size: 16px;
font-weight: 700;
color: #FFFFFF;
cursor: pointer;
}document.querySelectorAll('.kd-er-form').forEach(function (form) {
function mark(el) {
var ok = el.checkValidity();
el.setAttribute('aria-invalid', ok ? 'false' : 'true');
document.getElementById(el.getAttribute('aria-describedby'))
.textContent = ok ? '' : el.dataset.msg;
return ok;
}
form.addEventListener('submit', function (e) {
var first = null;
form.querySelectorAll('input').forEach(function (el) {
if (!mark(el) && !first) first = el;
});
if (first) {
e.preventDefault();
first.focus();
}
});
// 直している間に正しくなったら、その場で赤を消す
form.addEventListener('input', function (e) {
if (e.target.getAttribute('aria-invalid') === 'true') mark(e.target);
});
});仕組み:「正しくない」と「人が入力した」は別のこと
:invalid と :user-invalid の違い
入力の決まりは、HTMLに書きます。required(入力が要る)、type="email"(メールアドレスの形)、pattern(決めた形)などです。ブラウザはこの決まりをもとに、いつでも入力欄が正しいかを判定しています。
:invalid は、その判定をそのまま使います。空欄の required は「正しくない」ので、ページを開いた瞬間に当たります。:user-invalid は、人が操作したあとにだけ当たります。MDNでは2023年11月から主なブラウザで使えるとされています。
Chromeで操作して確かめた結果
どの時点で当たるのかを、Chromeで実際に操作して確かめました。required を付けたメールアドレスの入力欄です。ほかのブラウザでは確かめていないので、大事なフォームでは使うブラウザで試してください。
| 操作 | :invalid | :user-invalid |
|---|---|---|
| ページを開いた | 当たる | 当たらない |
| 選んで、空のまま離れた | 当たる | 当たらない |
| 「info@」と入力している途中 | 当たる | 当たらない |
| 「info@」のまま離れた | 当たる | 当たる |
| 戻って直し、正しい形になった | 当たらない | 当たらない(:user-valid が当たる) |
| もう一度消して、正しくない形にした(入力中) | 当たる | 当たる |
| 空のまま送信ボタンを押した | 当たる | 当たる |
novalidate のフォームで、空のまま送信ボタンを押した | 当たる | 当たらない |
最初の入力の途中では赤くならず、直せばその場で赤が消えます。これが読む人にとってちょうどいい動きです。つまずきやすいのは最後の行です。novalidate を付けたフォームでは、Chromeでは送信ボタンを押しても :user-invalid が当たりませんでした。4でJSを使っているのはこのためです。
ブラウザの吹き出しとの関係
novalidate がないフォームは、送信ボタンを押すとブラウザが送信を止めて、吹き出しでエラーを出します。MDNによると、この文言はブラウザごとに違い、見た目は変えられません。1〜3をそのままフォームに入れると、吹き出しと自分の文の両方が出ます。自分の文に統一したいなら4の形にします。
文を出す場所に + を使う理由
input:user-invalid + .kd-er-msg の + は、「この入力欄のすぐ次の要素」という意味です。HTMLの順番が変わると効かなくなるので、入力欄の直後に文を書きます。見出しの色まで変えたいときは、.kd-er-field:has(input:user-invalid) label のように :has()(中の状態で親を選ぶ指定)を使います。
見た目を変えるとどうなるか
色だけのとき
同じものの色を抜くと、次のようになります。色の見え方は人によって違うので、色だけの知らせ方は届かないことがあります。
MDNも、赤だけでは伝わらない人がいるので文やアイコンを足すよう案内しています。W3Cのアクセシビリティの基準でも、色だけで伝えないこと(1.4.1)と、間違いを文字で説明すること(3.3.1)が、いちばん基本のレベルAに入っています。
赤の濃さ
うすい赤は、やさしく見えても文字が読めません。文字に使うなら、白との差が4.5:1以上の濃さを選びます。今回の #B91C1C は6.5:1です。
文の書き方
いちばん差が出るのはここです。W3Cの基準(3.3.3)にも、直し方が分かるなら伝えることが入っています。「エラーです」と言うのではなく、何をすれば通るかを書きます。
スマホと、見る人への配慮
文と入力欄を、aria-describedby で結ぶ
aria-describedby は、入力欄と説明の文を結びつける属性です。読み上げソフトの利用者にとっては、見た目で下に並んでいるだけでは、どの入力欄の文なのか伝わりません。
1と3では、文を隠している間も結びついたままです。MDNにあるとおり、結びつける文は画面に見えていなくても使われます。Chromeのアクセシビリティ情報でも、隠した文が入力欄の説明として入っていました。そのため、文は「正しくありません」ではなく「〜の形で入力してください」と書きます。こうすると、正しい状態で読まれても意味が通ります。
CSSだけでは、読み上げに「間違い」と伝わらない
:user-invalid で変わるのは見た目だけです。「正しくない状態」を読み上げに確実に伝えたいなら、4のようにJSで aria-invalid="true" を付けます。MDNは、これを確かめた結果として付けること、空の項目には送信を試すまで付けないことを案内しています。最初から aria-invalid="true" をHTMLに書くと、まだ何も入力していない人に「間違い」と伝えることになります。
強制カラーモードでは、赤が消える
Windowsの設定などで画面の色を決まった配色に変える「強制カラーモード」では、枠線と文字の色が決まった色に置きかわり、赤い枠は見分けられなくなります。文は残るので、Chromeの再現で確かめても間違いは伝わりました。3の ✕ は背景画像(url の画像)なので、このモードでも残ります。ここでも、文があることが効いています。
スマホでは、入力欄の種類も合わせる
type="email" や inputmode="numeric"(数字のキーボードを出す指定)を付けると、スマホで合ったキーボードが出ます。間違いそのものを減らせます。文字の大きさは16pxにしています。また、1のように文が増える形は、下の部品が押し下げられます。すぐ下にボタンがあるときは、2のように最初から文を見せておくと動きません。
やりすぎ注意
入力の途中で何度も赤くしない、エラーの文を点滅させない、✓ を「確かに存在する」という意味で使わない。✓ は形が合っているだけで、そのメールアドレスに届くかどうかは分かりません。

ブラウザでの確認は、入力する人を助けるためのものです。
開発者ツールなどで外せるので、MDNも案内しているとおり、サーバー側でも必ず確かめます。
WordPressで使うとき
フォームのプラグインには、独自の確認の仕組みやエラーの出し方を持っているものがあります。その場合、今回のCSSをそのまま入れても、プラグイン側のエラー表示が使われます。先に開発者ツールで、エラーのときに入力欄へどんなクラスや属性が付くかを見て、そこへ色の指定を当てます。
自分で書いたフォームや、カスタムHTMLブロックに置くフォームなら、今回のコードをそのまま使えます。4のJSは、カスタムHTMLブロックの中か、フッターで読み込むJSファイルに入れます。
AIに頼むなら
Claudeなどの生成AIに頼むときは、見た目よりも「いつ出すか」と「何と書くか」を伝えると、そのまま使いやすいコードになります。
お問い合わせフォームのエラー表示を作って。
開いた瞬間には赤くせず、入力して離れたときと送信を押したときに出して。
赤い枠だけでなく、直し方の文を入力欄の下に出し、aria-describedby で結んで。
送信時は aria-invalid を付けて最初の項目へカーソルを移して。文字の赤は白と4.5:1以上で。AIが出したコードは、そのまま使う前に、次の3つを確かめます。
:invalidで赤くしていないか。ページを開いて、何も触らないうちに赤くなっていないかを見ます- 色を抜いても間違いが分かるか。文が出ているか、その文で直し方まで分かるかを、スマホ幅でも見ます
novalidateと:user-invalidの組み合わせ。空欄のまま送信ボタンを押して、知らせが出るかを試します。サーバー側の確認があるかも聞きます
KAKERUならこう使う/使わない
最後に、作り手としての判断です。
使う場面
- 項目が少ないお問い合わせフォーム。4の形で文言を自分で書きます。送信ボタンを押したあとの体験を、ブラウザ任せにしないためです
- メールアドレスや郵便番号のように、形が決まっている項目。離れた時点で知らせられる1か2を使います
- 入力の決まりが分かりにくい項目。先に伝えられる2を選びます
使わない場面
- 氏名やお問い合わせ内容のように、形が決まっていない項目への ✓。何か入れれば ✓ になるので、確かめた意味がありません
- パスワードの間違いなど、サーバーに聞かないと分からないもの。CSSでは判定できないので、送信後の画面で伝えます
- 検索欄。エラーを出す場面がほとんどなく、飾りになります

KAKERUなら、まずエラーの文を書きます。
「何をどう直すか」が一文で言えたら、赤い枠はそのあとで足すだけです。
まとめ
:invalidは開いた瞬間から当たる。人が入力したあとにしたいなら:user-invalid- 赤い枠だけで終わらせず、何をどう直すかを文字で書き、
aria-describedbyで入力欄と結ぶ - 文字の赤は白との差が4.5:1以上。うすい赤は読めない
novalidateのフォームでは、Chromeで:user-invalidが送信時に当たらなかった。送信時はJSでaria-invalidを付ける- ブラウザでの確認は入力を助けるためのもの。サーバー側でも確かめる
まずは1のコードをコピーして、自分のフォームに入れてみてください。そのあとに「この文で直し方が分かるか」を読み返すと、フォームの印象が変わります。
参考にした資料
いずれも2026年11月7日に確認しました。ブラウザの対応状況は変わることがあります。最新の情報は各ページでご確認ください。
- MDN「:user-invalid」(英語)
https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/:user-invalid
※人が操作したあとで当たること。Baseline は Widely available(2023年11月から主なブラウザで使える) - MDN「:user-valid」(英語)
https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/:user-valid
※正しいほうの指定。当たる条件。Baseline は Widely available(2023年11月から) - MDN「:invalid」(英語)
https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/:invalid
※正しくない要素に当たること。赤だけでは伝わらない人がいるので、文やアイコンを足すこと - MDN「Constraint validation」(英語)
https://developer.mozilla.org/en-US/docs/Web/HTML/Guides/Constraint_validation
※required・type・pattern の意味、novalidate、サーバー側でも確かめること - MDN「aria-invalid」(英語)
https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-invalid
※確かめた結果として付けること。空の項目には送信を試すまで付けないこと。ブラウザの吹き出しは見た目を変えられないこと - MDN「aria-describedby」(英語)
https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-describedby
※説明の文を結びつける属性。結びつけた文は画面に見えていなくてもよいこと - MDN「forced-colors」(英語)
https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media/forced-colors
※強制カラーモードで border-color や color が決まった色に置きかわること。url の背景画像は残ること - W3C「Understanding SC 3.3.1 Error Identification」(英語)
https://www.w3.org/WAI/WCAG22/Understanding/error-identification.html
※入力の間違いは、項目を示して文字で説明すること(レベルA) - W3C「Understanding SC 3.3.3 Error Suggestion」(英語)
https://www.w3.org/WAI/WCAG22/Understanding/error-suggestion.html
※直し方が分かるなら伝えること(レベルAA) - W3C「Understanding SC 1.4.1 Use of Color」(英語)
https://www.w3.org/WAI/WCAG22/Understanding/use-of-color.html
※色だけで情報を伝えないこと(レベルA) - W3C「ARIA21: Using aria-invalid to Indicate An Error Field」(英語)
https://www.w3.org/WAI/WCAG22/Techniques/aria/ARIA21
※aria-invalid とエラーの文を組み合わせる例
ふだんの制作の話や、作ったものの裏側は Instagram と X でも出しています。





























連載「WEBの技術」の47回目です。
今回は、入力欄のエラー表示。赤くする指定より、「いつ」「何と書くか」が大事な回です。