第1回

無料公開中公開翌日4時まで

ブラウザに任せるフォームバリデーションの基本

入力チェックはJavaScriptで書くものと思われがちですが、HTMLの属性を指定するだけで、ブラウザがかなりの部分を引き受けてくれます。まずは、属性だけで作ったフォームでブラウザが何をしてくれるのかを確かめ、判定の条件を決める属性とその注意点を見ていきます。

(702号)

目次
シェア

はじめに

フォームの入力チェック(バリデーション)というと、JavaScriptで書くものと思われがちです。ですが、HTMLの属性を適切に指定するだけで、ブラウザはかなりの部分を引き受けてくれます。

一方で、ブラウザに任せきりにすると困る場面もあります。属性の使い方を間違えると、入力した内容が、利用者の気づかないうちに変わってしまうこともあります。

このシリーズでは、まずHTML標準の属性を使って、どのようなバリデーションができるのかを紹介します。そのうえで、属性だけではできないことをJavaScriptで補う場合に、気をつけなければならないことも見ていきます。

バリデーションはとても大事です。ですが、フォームで大事なのは、そもそも利用者が間違えない、間違えにくいフォームを作ることです。この点についても、シリーズの最後で取り上げます。

HTMLだけでできることと、JavaScriptが必要になることの境目を知っておくと、AIにフォームのコードを書かせるときにも役立ちます。「この項目はHTMLの属性で済ませる」「ここはJavaScriptで補う」と指示できれば、必要以上に複雑なコードが生成されるのを防げます。

シリーズは全3回の予定です。

  1. ブラウザに任せるフォームバリデーションの基本(今回)
  2. pattern属性と、ブラウザに任せきれないこと
  3. novalidate属性で、エラーの表示を自前で行う

今回は、HTMLの属性だけで作ったフォームでブラウザが何をしてくれるのかを確かめ、判定の条件を決める基本的な属性とその注意点を見ていきます。

ブラウザに全部任せるとどうなるか

まずは、JavaScriptを一切書かずに、HTMLの属性だけで作ったフォームを見てみましょう。

requiredとtype="email"だけのフォーム
<form action="/contact" method="post">
  <label for="name">お名前</label>
  <input type="text" id="name" name="name" autocomplete="name" required>

  <label for="email">メールアドレス</label>
  <input type="email" id="email" name="email" autocomplete="email" required>

  <button type="submit">送信</button>
</form>

入力欄に付けているのは、入力を必須にするrequired属性と、メールアドレスを入力する欄であることを示すtype="email"だけです。

なお、このシリーズのデモでは、送信できる状態で送信ボタンを押すと「送信しました」というメッセージを表示しますが、デモのため、実際には何も送信していません。

属性だけでバリデーションするフォーム

デモで、何も入力せずに送信ボタンを押してみてください。フォームは送信されず、「お名前」の入力欄にフォーカスが移って、「このフィールドを入力してください」といったメッセージが吹き出しで表示されます。

「お名前」欄に入力し、メールアドレス欄に「abc」のように@のない文字列を入れて送信すると、今度はメールアドレスの入力欄にフォーカスが移り、@がないことを指摘するメッセージが出ます。

ブラウザが引き受ける3つのこと

空欄や@のない値で送信したとき、ブラウザが引き受けているのは次の3つです。

  1. 入力された値が、属性で指定された条件を満たしているかを調べる「判定」
  2. 条件を満たさない値があれば、フォームを送信しない「送信の阻止」
  3. 最初にエラーのある入力欄にフォーカスを移し、メッセージを表示する「エラーの通知」

属性が決めるのは、このうち「判定」の条件です。「送信の阻止」と「エラーの通知」は、送信時にブラウザが標準で行う動作で、判定で条件を満たさない入力欄があれば自動的に起こります。

JavaScriptで同じことをしようとすると、送信イベントを止める処理、値を調べる処理、エラーを表示してフォーカスを移す処理をそれぞれ書く必要があります。判定の条件を属性で指定するだけで、残りの2つもブラウザが引き受けてくれるのは、大きな利点です。

この3つの整理は、シリーズを通して使います。第3回(最終回)では、このうち「送信の阻止」と「エラーの通知」だけをブラウザから引き取り、「判定」はブラウザに任せ続けるという選び方を紹介します。

エラーメッセージはブラウザが用意する

さきほどのデモで見た吹き出しに出るメッセージは、制作者が書いたわけではなく、ブラウザがあらかじめ用意しています。文言はブラウザによって異なりますが、たとえば次のようなメッセージが表示されます。

状況 メッセージの例
必須の入力欄が空 このフィールドを入力してください。
必須のチェックボックスにチェックがない 次に進むにはこのチェックボックスをオンにしてください。
必須のセレクトボックスが未選択 リスト内の項目を選択してください。
type="url"の欄にURLでない値 URLを入力してください。
max="10"の欄に11 値は10以下にする必要があります。

状況に応じてメッセージが変わり、何が問題なのかはひととおり伝わります。

このメッセージは、ブラウザの表示言語に合わせて自動で切り替わります。言語が日本語に設定されたブラウザなら日本語で、英語に設定されたブラウザなら英語で表示されます。この利点については、第2回で詳しく取り上げます。

必須と値の種類を決める属性

それでは、判定の条件を指定する属性を見ていきましょう。どの属性にも、使う場所を間違えると困ることがあるので、注意点も併せて紹介します。

required属性:入力を必須にする

required属性を付けた入力欄は、空のままでは送信できなくなります。input要素のほか、textarea要素やselect要素にも指定できます。

入力欄の種類によって、「空」の意味は少しずつ異なります。

  • テキストの入力欄:何も入力されていない
  • チェックボックス:チェックが入っていない
  • ラジオボタン:同じname属性をもつグループの中で、どれも選ばれていない
  • セレクトボックス:先頭にある、値が空の選択肢(value="")が選ばれている
ラジオボタンとセレクトボックスを必須にする
<fieldset>
  <legend>お問い合わせの種類</legend>
  <label><input type="radio" name="kind" value="work" required>お仕事のご依頼</label>
  <label><input type="radio" name="kind" value="recruit">採用について</label>
  <label><input type="radio" name="kind" value="other">その他</label>
</fieldset>

<label for="pref">お住まいの都道府県</label>
<select id="pref" name="pref" required>
  <option value="">選択してください</option>
  <option value="tokyo">東京都</option>
  <!-- 以下省略 -->
</select>

ラジオボタンとセレクトボックスを必須にする

ラジオボタンは、グループのうちどれか1つにrequired属性があれば、グループ全体が必須になります。とはいえ、どれに付いているのかが読み手にわかりにくいので、すべてのラジオボタンに付けておいてもかまいません。

セレクトボックスは、最初の選択肢を「選択してください」のような値が空の選択肢にしておくと、選ばずに送信したときにエラーにできます。

required属性は、スクリーンリーダーなどの支援技術にも「必須」であることを伝えます。ただし、見た目には何も変わりません。ラベルの横に「必須」と書くなど、目で見てわかる表示は別に用意しましょう。

type属性:値の種類を決める

type属性は入力欄の種類を決める属性ですが、バリデーションの条件にもなります。判定が行われる主な値は次のとおりです。

type属性の値 判定される内容
email メールアドレスの形式になっているか
url URLの形式になっているか(https://などから始まるか)
number 数値として読み取れるか
dateなど日時の値 存在する日付・時刻になっているか(入力が途中で止まっていないか)

type="url"の入力欄では、「example.com」のようにhttps://を省いた値はエラーになります。利用者がドメイン名だけを入力しがちな項目では、「https://から入力してください」と入力例を添えておくとよいでしょう。

一方で、type="tel"は電話番号を入力する欄であることを示しますが、形式の判定はしません。電話番号の書き方は国や地域によってさまざまなので、決まった形式では判定できないためです。type="tel"を指定すると、スマートフォンでは電話番号用のキーボードが表示されます。形式を判定したい場合は、第2回で紹介するpattern属性を組み合わせます。

type="email"の判定は「形式」だけ

type="email"の判定は、「メールアドレスの形式として成り立つか」を見ているだけです。たとえば「a@b」のように、ドメイン名にドット(.)を含まない値も正しい値として扱われます。HTMLの仕様では、メールアドレスとして成り立つ形式だからです。

実際にメールが届くアドレスかどうかまでは、ブラウザにはわかりません。打ち間違いに気づいてもらうには、入力内容を確認する画面を用意するなどの工夫が必要です。

type="number"は「数字の並び」には使わない

type="number"は数値を入力する欄ですが、郵便番号や電話番号、クレジットカード番号のような「数字が並んだ文字列」には向きません。先頭の0が意味をもつ値や、ハイフンを含む値をうまく扱えないためです。

また、type="number"の入力欄に数値として読み取れない文字列を入力すると、入力欄の値は空として扱われます。このとき、required属性を付けていなくても「数字を入力してください」といったエラーになり、送信が止まります。値は空なのに送信が止まるので、少し意外に感じるかもしれません。

数字の入力に適したキーボードを出したいだけなら、type="text"にinputmode="numeric"を組み合わせます。

数字用のキーボードを出す郵便番号の入力欄
<label for="postal-code">郵便番号</label>
<input type="text" id="postal-code" name="postal-code" inputmode="numeric" autocomplete="postal-code">

スマートフォンでデモの入力欄をタップすると、数字を入力しやすいキーボードが表示されます。

数字用のキーボードを出す郵便番号の入力欄

inputmode属性はキーボードの種類を指定するだけで、判定には関わりません。

iOSのSafariでの表示
スクリーンショット:iPhoneのSafariでデモを開き、郵便番号の入力欄に「111」と入力した状態。画面の下半分に、0から9の数字キーと削除キーだけが並ぶテンキー型のキーボードが表示されている。大きな画像

範囲と文字数を決める属性

続いて、値の範囲や文字数を決める属性です。

min属性・max属性・step属性:数値や日付の範囲を決める

type="number"やtype="date"の入力欄では、min属性とmax属性で受け付ける値の範囲を、step属性で値の刻み幅を指定できます。

人数と予約日の範囲を指定
<label for="people">人数</label>
<input type="number" id="people" name="people" min="1" max="10">

<label for="date">予約日</label>
<input type="date" id="date" name="date" min="2026-10-01" max="2026-12-31">

人数と予約日の範囲を指定

人数の欄に範囲を外れた0や11を入力すると、「値は1以上にする必要があります」「値は10以下にする必要があります」といったエラーになります。予約日の欄も、範囲外の日付を入力するとエラーになります。日付を選ぶカレンダーでも、範囲外の日付は選べないようになっていることが多いでしょう。

なお、min属性やmax属性は、空欄のときは判定されません。必須にするならrequired属性も付けましょう。

step属性の初期値に注意

注意したいのはstep属性です。type="number"のstep属性の初期値は1なので、何も指定しないと小数を入力したときにエラーになります。たとえば1.5を入力すると、「有効な値を入力してください」といった趣旨のメッセージが出て、近くの有効な値として1と2が示されます。

体重や金額のように小数を受け付けたい場合は、step="0.1"のように刻み幅を指定するか、刻み幅を問わないstep="any"を指定しましょう。

小数を受け付ける
<label for="weight">体重(kg)</label>
<input type="number" id="weight" name="weight" min="0" step="0.1">

デモでは、step属性を指定していない「個数」の欄と、step="0.1"を指定した「体重」の欄を並べています。どちらにも1.5のような小数を入力して、送信してみてください。

「個数」の欄はエラーになって送信が止まりますが、「体重」の欄は1.5をそのまま受け付けます。

minlength属性・maxlength属性:文字数を決める

minlength属性とmaxlength属性は、入力できる文字数の下限と上限を指定する属性です。テキストの入力欄やtextarea要素に指定できます。

minlength属性は空欄を判定しない

minlength属性で気をつけたいのは、空欄のときは判定されないことです。minlength="3"は「入力するなら3文字以上」という意味になるので、空欄も許さないならrequired属性と組み合わせます。

3文字以上の入力を必須にする
<label for="nickname">ニックネーム(3文字以上)</label>
<input type="text" id="nickname" name="nickname" minlength="3" required>

次のデモの「ニックネーム」欄に何も入力せずに送信すると、エラーになります。

3文字以上の入力を必須にする

maxlength属性は上限を超えた分を切り捨てる

maxlength属性は、さらに注意が必要です。maxlength属性は、上限を超えたときにエラーを出す属性ではありません。上限を超える文字を入力させない属性です。

キーボードで入力しているときは、上限に達するとそれ以上入力できなくなるだけです。利用者も「これ以上入らない」と気づけます。ですが、上限を超える文字列を貼り付けたときは、超えた分がエラーも出ずに切り捨てられます。

たとえば、ハイフンなしの7桁の郵便番号を想定してmaxlength="7"を指定した入力欄に、「123-4567」をコピーして貼り付けたとします。

maxlength属性を指定した郵便番号の入力欄
<label for="postal-code">郵便番号</label>
<input type="text" id="postal-code" name="postal-code" maxlength="7">

maxlength属性で貼り付けた値が切り捨てられる

入力欄には「123-456」と、最後の1文字が欠けた値が入ります。エラーは出ないので、利用者は値が変わったことに気づかないまま送信してしまうかもしれません。入力欄の幅が狭く、値の全体が見えていなければ、なおさら気づけないでしょう。

textarea要素で同じことが起きると、長文の最後が切れてしまいます。パスワードの入力欄で起きると、利用者の想定とは違うパスワードが登録されてしまいます(パスワードの例は「体験が悪いフォームを知る | 「良いフォーム」ってなんだろう」でも紹介しています)。

文字数の上限を利用者に守ってもらいたいだけなら、maxlength属性で入力を止めるよりも、上限をラベルや説明文で伝え、超えたときにエラーとして知らせるほうが安全です。これはHTMLの属性だけでは実現できないので、JavaScriptで補うことになります。

たとえば、入力のたびに文字数を数え、上限を超えていたらsetCustomValidity()メソッドでエラーにする方法があります。次の例では、ニックネームの上限をラベルで伝え、10文字を超えたらエラーにしています。

上限をラベルで伝える入力欄
<label for="nickname">ニックネーム(10文字以内)</label>
<input type="text" id="nickname" name="nickname">
上限を超えたらsetCustomValidity()でエラーにする
const nickname = document.querySelector("#nickname");
const maxLength = 10;

nickname.addEventListener("input", () => {
  if (nickname.value.length > maxLength) {
    nickname.setCustomValidity(`${maxLength}文字以内で入力してください。`);
  } else {
    nickname.setCustomValidity("");
  }
});

setCustomValidity()で文字数の上限を超えたらエラーにする

setCustomValidity()メソッドに文字列を渡すと、その入力欄は条件を満たしていないものとして扱われます。ほかの属性で判定した場合と同じように、送信時には送信が止まり、渡した文字列がメッセージとして表示されます。上限以内に戻ったら、空の文字列を渡してエラーを解除します。

上記のデモで11文字の「コードグリッド太郎です」を貼り付けると、値は切り捨てられずにそのまま残り、送信しようとするとメッセージが表示されます。利用者は自分で1文字削るか、言い回しを変えるかを選べます。

ここまでのまとめ

今回は、HTMLの属性だけで作ったフォームで、ブラウザが「判定」「送信の阻止」「エラーの通知」の3つを引き受けてくれることを確かめました。そして、判定の条件を決める基本的な属性を見てきました。

属性 役割 ポイント
required 入力を必須にする チェックボックスやラジオボタン、セレクトボックスにも使える
type 値の種類を決める 判定は「形式」だけで、type="tel"は判定しない
min・max・step 範囲と刻み幅を決める step属性の初期値は1
minlength・maxlength 文字数を決める maxlength属性は貼り付けた値のうち上限を超えた分を、エラーを出さずに切り捨てる

属性を指定するだけで多くのことをブラウザに任せられますが、maxlength属性のように、任せ方を間違えると利用者が困る属性もあります。

次回は、type属性では表せない書式を判定できるpattern属性を紹介します。そのうえで、ブラウザに任せるといいことと、任せきれないことを整理します。

この記事の内容についての意見・感想を送る
CodeGridのDiscordで知識を深めよう。参加する。

人気のシリーズ