HTMLフォームのパターンチェック完全ガイド|pattern属性・正規表現・UX・アクセシビリティまで

7 min 141 views
patternの理解

はじめに:なぜパターンチェックが“最後のひと押し”になるのか

入力フォームの離脱はビジネスに直結します。必須・文字数・型チェックだけでは、細かな書式ミスを取りこぼすことがあります。
pattern属性によるパターンチェックは、「正しいっぽいけどNG」な入力(例:ハイフンなしの郵便番号など)を早期に防ぎ、サーバー負荷とユーザーのストレスを減らす重要な仕組みです。

✅ クライアント側バリデーションはUX向上のため。本質的なチェックはサーバー側でも必ず実施しましょう。

pattern属性の基本ルール

  • 使用可能な要素:<input> の text / search / url / tel / email / password など
  • 書式:JavaScript互換の正規表現(フラグは不可)
  • 評価範囲:フィールド全体(=^ と $ が自動的に付与されるイメージ)
  • よく使う組み合わせ:title(エラーメッセージ)+maxlength+required

 よくある誤解

誤り正解
部分一致で判定される全体一致で判定される
type属性で十分typeは“構文的妥当性”のみ、ビジネスルールはpatternで補完

よく使うパターンの実装例

 郵便番号(ハイフン必須)

<input
  type="text"
  name="postal_code"
  inputmode="numeric"
  autocomplete="postal-code"
  pattern="^\d{3}-\d{4}$"
  title="例:123-4567(ハイフン必須)" />

 電話番号(ハイフン任意)

<input
  type="tel"
  name="tel"
  inputmode="tel"
  pattern="^\d{2,4}-?\d{2,4}-?\d{3,4}$"
  title="例:03-1234-5678 または 0312345678" />

 自社ドメイン限定メール

<input
  type="email"
  name="work_email"
  pattern="^[a-zA-Z0-9._%+-]+@example\.co\.jp$"
  title="社用メール(@example.co.jp)のみ利用できます" />

 強固なパスワード(英大小・数字・記号)

<input
  type="password"
  pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^\w\s]).{8,64}$"
  title="英大文字・英小文字・数字・記号を1文字以上含む8〜64文字" />

title属性で「直し方」を伝える

ブラウザの標準エラーメッセージは冷たく、意味が伝わりにくいものです。
titleを使って、「どこをどう直せばいいか」を自然な言葉で伝えるとUXが大幅に向上します。

例:

  • ❌「パターンに一致しません」
  • ✅「例:123-4567(ハイフン必須)」

CSSで視覚的にわかりやすくする

input:required:invalid { outline: 2px solid #f66; }
input:valid { outline: 2px solid #4caf50; }
input:placeholder-shown { outline-color: #ccc; }
  • :placeholder-shownを使うことで、入力前の赤い枠線を防げます。

JavaScriptとの併用:Constraint Validation API

patternはHTMLだけでも動作しますが、JavaScriptで補うとUXがより向上します。

<form id="signup" novalidate>
  <input name="zip" pattern="^\d{3}-\d{4}$" title="例:123-4567" required />
  <button type="submit">送信</button>
</form>

<script>
const form = document.getElementById('signup');
form.addEventListener('submit', e => {
  if (form.checkValidity()) return;
  e.preventDefault();
  const invalid = form.querySelector(':invalid');
  invalid.setCustomValidity(invalid.title || '入力内容を確認してください');
  invalid.reportValidity();
  invalid.scrollIntoView({ behavior: 'smooth', block: 'center' });
});
</script>

 よく使うメソッド/プロパティ一覧

メソッド/プロパティ説明主な用途
checkValidity()フィールドやフォーム全体の妥当性を判定し、結果を true / false で返す。
(エラーメッセージは表示されない)
JSで独自制御する前のバリデーション判定に使う
reportValidity()ブラウザ標準のエラーバルーンを表示しつつ、true / false を返す。エラーをユーザーに伝えるときに使用
setCustomValidity(message)独自のエラーメッセージを設定できる。
空文字列 "" を渡すとエラー解除。
カスタムメッセージ表示多言語対応に便利
validity入力状態を示すオブジェクト。
patternMismatch / valueMissing / typeMismatch / tooShort / tooLong / rangeOverflow などの詳細を持つ。
エラー内容を細かく条件分岐して処理する際に利用

書いていい正規表現・避けたほうがいい正規表現

分類内容備考
✅ 書いてよい桁数・区切り・簡単なフォーマットフロントでの入力支援
⚠️ 避けたいメール完全仕様・住所妥当性・カード検証サーバー側で行う

type属性との役割分担

  • type=”email”:構文レベルの妥当性
  • pattern:自社ルールなどビジネス要件
    両者を併用することで、より堅牢なチェックを構築できます。

 typeとpatternの役割分担:型で下支え、patternで要件を締める

  • type="email"「メールっぽい」構文を保証(ユーザーが便利)
  • patternビジネスルールを追加(例:ドメインやローカルルール)

multiple×emailの落とし穴

multiple付きメールはカンマ区切りで複数入力できますが、patternはフィールド全体に適用されます。
各トークンごとに検証するJSを併用するか、patternは使わずサーバーで確実に判定する設計が安全。

モバイルUXを高める小ワザ

  • inputmode=”numeric”:スマホで数字キーボードを表示
  • autocomplete=”postal-code”:郵便番号補完
  • maxlength:物理的に文字数制限をかける

アクセシビリティ対策

  • 色だけでエラーを伝えず、テキストでも表示
  • aria-live=”polite” でエラーメッセージを読み上げ
  • titleに頼らず、ラベルや説明文で補足

国際化対応のヒント

  • \d は ASCII 数字相当(環境による違いが混乱を生む場合も)。半角数字に限定したいなら 0-9 明記が安心。
  • ユーザー文化圏ごとのフォーマット差(電話・郵便・住所)はパターンを分けるか、国選択に応じてpatternを差し替える
const tel = document.querySelector('input[name="tel"]');
const country = document.querySelector('select[name="country"]');
const patterns = {
  JP: '^(?:0\\d{1,4}-?\\d{1,4}-?\\d{3,4})$',
  US: '^\\(?\\d{3}\\)?-?\\d{3}-?\\d{4}$'
};
country.addEventListener('change', () => {
  tel.setAttribute('pattern', patterns[country.value] || '.*');
  tel.setAttribute('title', country.value === 'JP' ? '日本の電話番号形式で入力' : 'Enter a valid phone number');
});

フォールバック戦略:未対応ブラウザやJS無効時は?

  • ほとんどのモダンブラウザはpattern対応。ただし企業内レガシーJS無効の可能性はゼロではない。
  • だからこそサーバー側検証が最終防衛線。クライアント側は“早期の気づき”を提供する役割。

実務あるあるQ&A

Q. 半角・全角を区別したい
A. 正規表現の文字クラスで明示する。例:半角英数なら [A-Za-z0-9]、全角カナは [ァ-ヶー]。

Q. 可読性の低い長大な正規表現がつらい
A. 小さく分ける・コメント化・JS側でプリセット管理を。テストケースを別ファイルで管理すると事故が減る。

Q. 入力途中で“全部赤くなる”問題
A. :placeholder-shown と :user-invalid(対応ブラウザ)や、「フォーカスアウト時のみ判定」にするUXポリシーで解決。

テストで正規表現を守る

正規表現は仕様そのもの。テストケースを持つことで安全性を保てます。

入力値結果
03-1234-5678
3-1234-5678
03-12345-5678

パターン辞典

用途pattern例ヒント(title例)
郵便番号(ハイフン必須)^\d{3}-\d{4}$例:123-4567
郵便番号(ハイフン任意)^\d{3}-?\d{4}$例:1234567 または 123-4567
社員ID(英大+数字 8桁)^[A-Z0-9]{8}$例:AB12CD34
UUID v4 風^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$英小字のハイフン区切り
日付(yyyy-mm-dd の最小構文)`^\d{4}-(0[1-9]1[0-2])-(0[1-9]
金額(整数・先頭ゼロ不可)^[1-9]\d*$半角数字で入力
カード名義(英字・スペース)^[A-Za-z]+(?:\s+[A-Za-z]+)+$名字・名前の順で英字

注意:日付の妥当性(うるう年 等)はサーバーで最終チェック。

失敗しない設計のチェックリスト

  • まず type・maxlength・inputmode・autocomplete を適切化
  • pattern で業務要件の過不足なしを確認(例を列挙してテスト)
  • title は「直し方」を自然言語で
  • 視覚以外の手掛かり(テキスト・ARIA・フォーカス)を提供
  • クライアント検証はUXのため真正性はサーバーで二重化
  • i18n(言語/国フォーマット)に備えた切替ロジック
  • ユニットテストで境界とNGを押さえる

まとめ

pattern属性は、ユーザーを困らせるためではなく、迷わせないためのガイドラインです。
「正しい入力」が自然に導かれるようなメッセージ設計と視覚的なフィードバックを整え、UXを高めながら安全なデータ入力を実現しましょう。

付録:サンプルフォーム(ミニ実装一式)

<form id="example" novalidate>
  <label>郵便番号(123-4567)
    <input type="text" name="zip"
      inputmode="numeric" autocomplete="postal-code"
      pattern="^\d{3}-\d{4}$"
      title="例:123-4567(ハイフン必須)" required />
  </label>

  <label>電話番号(ハイフン任意)
    <input type="tel" name="tel"
      inputmode="tel"
      pattern="^\d{2,4}-?\d{2,4}-?\d{3,4}$"
      title="例:03-1234-5678 または 0312345678" required />
  </label>

  <label>社用メール(@example.co.jp)
    <input type="email" name="mail"
      pattern="^[a-zA-Z0-9._%+-]+@example\.co\.jp$"
      title="社用メール(@example.co.jp)のみ利用できます" required />
  </label>

  <button type="submit">送信</button>
  <p id="errors" aria-live="polite"></p>
</form>

<script>
const form = document.getElementById('example');
const errors = document.getElementById('errors');

form.addEventListener('submit', (e) => {
  errors.textContent = '';
  // デフォルトのバルーンを使わず、下部にまとめて表示したい場合
  if (form.checkValidity()) return;
  e.preventDefault();

  const invalids = [...form.querySelectorAll(':invalid')];
  invalids.forEach(input => {
    let msg = '';
    if (input.validity.valueMissing) msg = '必須項目です';
    else if (input.validity.patternMismatch) msg = input.title || '書式を確認してください';
    else if (input.validity.typeMismatch) msg = '入力形式が正しくありません';
    input.setCustomValidity(msg);
    input.reportValidity(); // 最初の一件はバルーン、残りは下に集約
  });

  errors.innerHTML = invalids
    .map(i => `<span>・${i.name}:${i.validationMessage}</span>`)
    .join('<br>');
  invalids[0]?.focus();
});
</script>
関連記事