<input>type の選び方で挙動が大きく変わります。この記事ではタイプ別に「そのまま使えるもの」「実務では避けるべきもの」を判断できるように整理します。

この記事は2024年の公開後、2026年9月に見直しました。 公開当時のコードに、<textarea>pattern 属性を指定するという誤りがありました。訂正したうえで、タイプ別の解説を追加しています。

  • pattern 属性は <input> 専用です。 <textarea> では効きません。最小文字数を求めるなら minlength を使います
  • inputmode / autocomplete / list / pattern はいずれも2024年の新機能ではありません

type 別・使ってよいものと避けるべきもの

type 判断 理由
text / email / tel / url / password そのまま使う 挙動が安定している
search 使う クリアボタンが付く
date / time / datetime-local 条件付き UIがブラウザごとに大きく違う
number 用途を限定 ホイールで値が変わる、先頭の0が落ちる
range 条件付き 正確な値の入力には向かない
color 使う OSのカラーピッカーが開く
file 使う acceptcapture が効く
month / week 避ける Firefox が未対応でただのテキスト欄になる

 

type=”date”:使う前に知っておくこと

表示形式はOSとブラウザのロケールで決まり、CSSでも属性でも変えられません。 日本語環境では「2024/06/09」、英語環境では「06/09/2024」のように表示されます。

<label for="birthday">生年月日</label>
<input type="date" id="birthday" name="birthday"
       min="1900-01-01" max="2024-12-31"
       autocomplete="bday">

 

知っておくべき点は3つです。

  • value は必ず YYYY-MM-DD 形式:表示がどうであれ、送信される値とJavaScriptから読む値はこの形です
  • min / maxYYYY-MM-DD で書く:範囲外は選べなくなります
  • 未入力時は空文字valueAsDatenull を返します

日付の読み取りは valueAsDate に注意

const input = document.getElementById('birthday');

input.value;        // "2024-06-09"(文字列)
input.valueAsDate;  // Date オブジェクト。ただし UTC 基準

 

valueAsDate はUTCの午前0時として解釈されます。 日本時間で扱うと日付が1日ずれることがあるため、日付だけを扱うなら文字列のまま処理する方が安全です。

// ずれる可能性がある
const d = input.valueAsDate;
console.log(d.getDate()); // 環境によって前日になる

// 文字列のまま扱う
const [y, m, day] = input.value.split('-').map(Number);

 

カレンダーを開きやすくする

既定では小さなアイコンを正確にクリックしないとカレンダーが開きません。showPicker() で欄のどこを押しても開くようにできます。

input.addEventListener('click', () => {
  if (!('showPicker' in HTMLInputElement.prototype)) return;
  try { input.showPicker(); } catch {}
});

 

type=”number”:使う場面を限定する

「数字を入れる欄」に無条件で使ってはいけません。 郵便番号・電話番号・クレジットカード番号には向きません。

  • 先頭の 0 が落ちる(0120120
  • ハイフンが入力できない
  • マウスホイールで値が勝手に変わる
  • 桁数の多い数値が指数表記になることがある

向くのは「増減させたい数量」だけです。その場合も min / max / step を必ず付けます。

<!-- 数量:これは type="number" が正しい -->
<label for="qty">数量</label>
<input type="number" id="qty" name="qty" value="1" min="1" max="99" step="1">

<!-- 郵便番号:type="text" にする -->
<label for="zip">郵便番号</label>
<input type="text" id="zip" name="zip"
       inputmode="numeric" autocomplete="postal-code"
       pattern="\d{3}-?\d{4}"
       title="123-4567 または 1234567 の形式で入力してください">

 

ホイールでの誤操作は、フォーカス時にホイールを無効化して防げます。

document.querySelectorAll('input[type="number"]').forEach((el) => {
  el.addEventListener('wheel', (e) => {
    if (document.activeElement === el) e.preventDefault();
  }, { passive: false });
});

 

{ passive: false } が必要です。付けないと preventDefault() が無視されます。

 

type=”file”:accept と capture

<!-- 画像を複数選ぶ -->
<input type="file" name="photos" accept="image/*" multiple>

<!-- スマホで背面カメラを直接起動 -->
<input type="file" name="doc" accept="image/*" capture="environment">

<!-- 拡張子で絞る -->
<input type="file" name="sheet" accept=".csv,.xlsx,text/csv">

 

accept は検証ではありません。 ユーザーはダイアログで「すべてのファイル」に切り替えられます。種類とサイズは必ずサーバー側で確認してください。

選択後にクライアント側で弾く場合はこう書きます。

const MAX = 5 * 1024 * 1024; // 5MB

fileInput.addEventListener('change', () => {
  for (const file of fileInput.files) {
    if (!file.type.startsWith('image/')) {
      alert('画像ファイルを選んでください');
      fileInput.value = '';   // 選択を解除する
      return;
    }
    if (file.size > MAX) {
      alert('5MB以下のファイルを選んでください');
      fileInput.value = '';
      return;
    }
  }
});

 

fileInput.value = '' で選択を解除できます。 これを忘れると、無効なファイルが選ばれたままの表示が残ります。

 

type=”color” と type=”range”

<label for="theme">テーマカラー</label>
<input type="color" id="theme" name="theme" value="#2a7fff">

<label for="volume">音量</label>
<input type="range" id="volume" name="volume"
       min="0" max="100" step="5" value="50"
       list="volume-marks">
<datalist id="volume-marks">
  <option value="0"></option>
  <option value="50"></option>
  <option value="100"></option>
</datalist>
<output for="volume" id="volume-out">50</output>

 

type="range"list を付けると目盛りが表示されます。また、range は現在値が見えないので、<output> を必ず添えてください

const volume = document.getElementById('volume');
const out = document.getElementById('volume-out');

volume.addEventListener('input', () => {
  out.textContent = volume.value;
});

 

input イベントと change イベントの違いに注意してください。input はつまみを動かしている間ずっと発火し、change は離したときだけ発火します。表示の更新は input、サーバーへの保存は change が適切です。

色や部品の見た目は accent-color で変えられます。appearance: none で作り直すより堅実です。

:root { accent-color: #2a7fff; }

 

textarea には pattern が効かない

公開当時のコードにあった誤りです。 pattern 属性は <input> だけのもので、<textarea> には存在しません。

<!-- 効かない -->
<textarea required pattern=".{10,}" title="10文字以上"></textarea>

<!-- 正しい:minlength を使う -->
<label for="feedback">フィードバック</label>
<textarea id="feedback" name="feedback"
          required minlength="10" maxlength="1000"
          placeholder="ご意見をお聞かせください"></textarea>

 

minlength の注意点:ユーザーが一度も入力していない状態では発動せず、required の方が先に効きます。また、改行は1文字として数えられます

入力量に応じて高さを自動調整したい場合は、CSSの field-sizing が使えます。

textarea {
  field-sizing: content;
  min-height: 4lh;
  max-height: 20lh;
  width: 100%;
}

 

残り文字数を出すなら、maxlength と組み合わせます。

const ta = document.getElementById('feedback');
const counter = document.getElementById('counter');

ta.addEventListener('input', () => {
  counter.textContent = `${ta.value.length} / ${ta.maxLength}`;
});

 

実装例:ユーザー登録フォーム

ここまでの内容をまとめた形です。

<form>
  <label for="username">ユーザー名</label>
  <input type="text" id="username" name="username"
         required autocomplete="username" minlength="3">

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

  <label for="password">パスワード</label>
  <input type="password" id="password" name="password"
         required autocomplete="new-password" minlength="8">

  <label for="birthdate">生年月日</label>
  <input type="date" id="birthdate" name="birthdate"
         required autocomplete="bday" max="2024-12-31">

  <label for="phone">電話番号</label>
  <input type="tel" id="phone" name="phone"
         autocomplete="tel" inputmode="tel"
         pattern="0\d{1,4}-?\d{1,4}-?\d{4}"
         title="例: 090-1234-5678">

  <button type="submit">登録</button>
</form>

 

ポイントを整理します。

  • 新規登録は autocomplete="new-password"current-password にするとパスワードマネージャーが誤動作します
  • <input type="submit"> ではなく <button type="submit">:ボタン内にアイコンや複数行を入れられます
  • 電話番号は type="tel":形式が国ごとに違うため、ブラウザ側の検証はありません。pattern で補います

エラー表示は :user-invalid を使ってください。:invalid だと開いた瞬間に全部赤くなります。

input:user-invalid {
  border-color: #d33;
}

 

詳しくはHTML form要素の新機能と活用法にまとめました。

 

datalist は候補以外も入力できる

<label for="search">商品を検索</label>
<input type="search" id="search" list="products"
       enterkeyhint="search" placeholder="商品名を入力">
<datalist id="products">
  <option value="スマートフォン"></option>
  <option value="ノートパソコン"></option>
  <option value="タブレット"></option>
  <option value="ヘッドフォン"></option>
</datalist>

 

<datalist> は入力の補助であって、選択肢の強制ではありません。 候補にない値もそのまま送信できます。選択肢を限定したいなら <select> を使ってください。

そのほかの制約は次の通りです。

  • 候補リストの見た目はCSSで変更できません(ブラウザのUIです)
  • 絞り込みが前方一致か部分一致かはブラウザ依存です
  • 数十件を超えると使いづらくなります

入力欄の属性についてはHTML input要素の新機能と活用法で詳しく扱っています。

 

まとめ

  • pattern<input> 専用。<textarea> には minlength
  • type="date"value は常に YYYY-MM-DDvalueAsDate はUTC基準でずれる
  • type="number" は「増減させる数量」だけに使う。 郵便番号や電話番号には使わない
  • type="number" のホイール誤操作は { passive: false } 付きで無効化する
  • accept は検証ではない。サーバー側の確認が必須value = '' で選択解除できる
  • type="range" には <output> を添える。表示更新は input、保存は change
  • <datalist> は候補以外も入力できる。強制したいなら <select>
  • 新規登録のパスワードは autocomplete="new-password"

type を1つ選び間違えるだけで、ユーザーが入力できない欄ができてしまいます。「その値は増減させるものか」「形式は国際的に一定か」の2点で判断すると、選択を誤りにくくなります。

ABOUT ME
りん
このブログでは、Web開発やプログラミングに関する情報を中心に、私が日々感じたことや学んだことをシェアしています。技術と生活の両方を楽しめるブログを目指して、日常で触れた出来事や本、グルメの話題も取り入れています。気軽に覗いて、少しでも役立つ情報や楽しいひとときを見つけてもらえたら嬉しいです。