目次
はじめに:なぜパターンチェックが“最後のひと押し”になるのか
入力フォームの離脱はビジネスに直結します。必須・文字数・型チェックだけでは、細かな書式ミスを取りこぼすことがあります。
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>



