The min and max attributes declare the smallest and largest values an input may accept—letting the browser automatically block anything outside that range.
input 要素の min と max 属性を指定すると、数値型や日付型の入力欄で入力できる最小値と最大値を指定することができます。
自由入力フィールドは便利ですが、想定より小さすぎる数や未来すぎる日付が送られてくるとビジネスロジックが壊れます。
min と max 属性は「この範囲内しか受け付けません」とブラウザーに宣言することで、
という三つの効果を同時に達成します。
HTML
<label>
年齢:
<input type="number" name="age" min="0" max="120" step="1" required>
</label>
type に応じて数値や日付へ変換min〜max の範囲かを検証:invalid 疑似クラスを付与、フォーム送信をブロックmin / max は “表示用のヒント” ではなく Constraint Validation API が参照する 仕様レベルの制約 です。
| 直感的用途 | type 設定例 | min / max の値の書式例 |
|---|---|---|
| 整数・小数 | number / range | 0 , 10.5 |
| 絶対時間 | date / datetime-local | 2025-01-01 , 2025-01-01T09:00 |
| 年月 | month | 2025-07 |
| 週 | week | 2025-W27 |
| 時刻 | time | 09:00 , 23:59 |
min / max に合わせて停止type="number" 未実装。polyfill か JavaScript でフォールバック必須step が min と “割り切れない” 場合、ブラウザーは「最も近い有効値」を提案 しつつ、依然として制約エラー (stepMismatch) を返します。
HTML
<!-- 例: 0.3 刻みだが min は 0.2 -->
<input type="number" min="0.2" max="1" step="0.3">
<!-- 0.2, 0.5, 0.8 が OK/0.3 は NG -->
aria-invalid="true" を自動付与するブラウザーもあるaria-describedby で「最小値:0 最大値:120」のヒントを必ずテキストでも提供role="spinbutton" を読み上げられるが、error message は aria-live 領域で自前通知が確実
HTML
<label>
年齢:
<input
type="number"
id="ageInput"
name="age"
min="0"
max="120"
step="1"
required
>
</label>
<button id="showAgeBtn" type="button">表示</button><br>
<!-- バリデーション範囲のヒント -->
<small id="ageHint">0〜120 の整数で入力してください</small>
<!-- 結果やエラーをここに出力 -->
<p id="message" aria-live="polite"> </p>
<script>
<!-- JavaScript code here -->
</script>
JavaScript
const ageInput = document.getElementById('ageInput');
const message = document.getElementById('message');
const showAgeBtn = document.getElementById('showAgeBtn');
showAgeBtn.addEventListener('click', () => {
if (ageInput.validity.valid) {
message.textContent = `入力された年齢: ${ageInput.value}`;
} else {
message.textContent = '年齢は 0 から 120 の範囲で入力してください。';
}
});
ageInput.addEventListener('input', () => {
if (ageInput.validity.rangeUnderflow) {
message.textContent = '年齢は 0 以上でなければなりません。';
} else if (ageInput.validity.rangeOverflow) {
message.textContent = '年齢は 120 以下でなければなりません。';
} else if (ageInput.validity.stepMismatch) {
message.textContent = `年齢は ${ageInput.step} 刻みで入力してください。`;
} else {
message.textContent = '';
}
});
validity.rangeUnderflow / rangeOverflow プロパティで瞬時に判定。validity オブジェクトで不足分を補足しています。aria-live="polite" を付け、スクリーンリーダーでも通知されるようにしています。HTML
<label>
年齢:
<input
type="number"
id="ageInputCv"
name="age"
min="0"
max="120"
step="1"
required
aria-describedby="ageHintCv"
>
</label>
<button id="showAgeBtnCv" type="button">表示</button><br>
<!-- バリデーション範囲のヒント -->
<small id="ageHintCv">0〜120 の整数で入力してください</small>
<!-- 結果やエラーをここに出力 -->
<p id="messageCv" aria-live="polite"></p>
<script>
<!-- JavaScript code here -->
</script>
JavaScript
(function () {
const ageInput = document.getElementById('ageInputCv');
const showBtn = document.getElementById('showAgeBtnCv');
const messageEl = document.getElementById('messageCv');
/** カスタムバリデーションの付与・解除 */
function applyCustomValidity() {
if (!ageInput.value) {
ageInput.setCustomValidity('年齢を入力してください');
} else if (ageInput.validity.rangeUnderflow) {
ageInput.setCustomValidity('年齢は 0 歳以上で入力してください');
} else if (ageInput.validity.rangeOverflow) {
ageInput.setCustomValidity('年齢は 120 歳以下で入力してください');
} else if (ageInput.validity.stepMismatch) {
ageInput.setCustomValidity('整数で入力してください');
} else {
ageInput.setCustomValidity(''); // エラー解除
}
}
/** インプット時に即時検証 */
ageInput.addEventListener('input', () => {
applyCustomValidity();
messageEl.textContent = ageInput.validationMessage; // 空文字なら正常
});
/** ボタン押下時の処理 */
showBtn.addEventListener('click', () => {
applyCustomValidity(); // 念のため再検証
if (ageInput.checkValidity()) { // すべて OK
messageEl.textContent = `入力された年齢: ${ageInput.value}`;
} else {
// ブラウザー標準のエラー吹き出しも表示
ageInput.reportValidity();
// messageEl には既にエラー文が入っている
}
});
})();
setCustomValidity('…'):invalid) に。validationMessagesetCustomValidity() で設定した文言がそのまま取得できるので、同じテキストを <p> にも反映。reportValidity()aria-describedby で範囲ヒントを事前提供、エラーは aria-live に即時反映。HTML の制約は「親切な顧客に対するサービス」であり、悪意あるリクエスト は cURL や改竄 JS で容易に突破できます。
したがって、サーバー側でも同じ制約を再実装し、受信した値が min / max の範囲内であることを確認する必要があります。
このように、クライアント側の制約はユーザー体験を向上させるためのものであり、セキュリティやデータ整合性のためにはサーバー側でも同様のチェックが不可欠です。
PHP
$age = filter_input(INPUT_POST, 'age', FILTER_VALIDATE_INT, [
'options' => ['min_range' => 0, 'max_range' => 120]
]);
if ($age === false) {
http_response_code(400);
exit('Invalid age');
}
min="0" max="120"type="number" step="0.01" min="0" max="99999.99"type="date" min="<?= date('Y-m-d') ?>" max="2026-12-31"type="time" min="09:00" max="18:00"type="range" min="0.25" max="2" step="0.05"min より小さな値でも JavaScript から value を直接代入 すると見た目は反映されるrange 型は 常に数値扱い。min="0" max="10" に "3e1" を入力しても 30 ではなく不正値datetime-local は タイムゾーンを含まない。フロントとバックエンドで “日付がズレる” 可能性大max を 0 や空文字にすると 設定されていない 扱いになるので注意。例えば min="0" max="0" は「0 のみ許可」ではなく「何も許可しない」状態になるaria-live で動的メッセージを読ませるpattern 属性で代替検証数字・日付・時刻など “連続値” を扱う入力欄では、境界値を宣言するだけで UX と品質が大きく向上 します。以下は実務で頻出するシーンをタイプ別にまとめたものです。
min="0" max="120" で非現実的な値をブロック。min="1" max="10" など在庫上限に合わせて制限。range) で min="0" max="50000"。min="6" max="128" を指定し、不適切な長さを防止。min="0.25" max="2" step="0.05" で倍速範囲をコントロール。min="1900-01-01" max="<?= date('Y-m-d') ?>" で未来日を排除。min="<?= date('Y-m-d') ?>" max="2026-12-31" — 予約開始日から 1 年半先まで許可。min="2025-08-01" max="2025-08-31" の 1 か月間に限定。min="2025-04-01T00:00" で年度開始以降のみ許可。min="09:00" max="18:00" で深夜帯を自動却下。min="17:30" max="22:00"、さらに step="900"(15 分刻み)で UX 向上。type="month" で min="2020-01" max="<?= date('Y-m') ?>"。type="week" で四半期内 (min="2025-W27" max="2025-W39") に限定。min="1" max="8" — サーバー負荷をコントロール。min="0" max="5" を設定し、過度なリトライを防止。min と max は 入力制御・データ品質・アクセシビリティを同時に底上げできる “宣言的ガードレール” です。ユーザー体験を損なわず、サーバー側のバリデーションと二重化することで、安全かつ保守性の高いフォームを実現できます。
単に「範囲を決める」だけではもったいない。
先進的な UI/UX やドメインロジックと組み合わせることで、min と max は “リアクティブ・パラメータ” へと進化します。
min を「開始日 + 1 日」に JavaScript で随時書き換え。change イベントで end.min = dayjs(start.value).add(1, 'day').format('YYYY‑MM‑DD')。min / max を毎秒更新。input.reportValidity() を呼び、自動的に最新の “許容スプレッド” を強制。min="18" もしくは min="21" に切り替え。remainingCredits をそのまま max にバインド。max を書き換えることで、二重送信を防止。Math.pow(10, dB/20) だが、ユーザー入力は dB。min="-60" max="0" にしておき、変換は input ハンドラで行う。min / max が UI の手掛かり、実際のゲームバランスは JS 側で調整。min 未満なら min をその値に一旦合わせて setCustomValidity('') → ユーザーが再入力しやすい余白を作る。aria-live+min/max の協調min/max 変更時に「入力可能範囲が変わりました」とライブリージョンへ通知し、視覚障害者にも即時共有。minLength, maximum) をビルド時に静的 HTML へ埋め込み。min/max へ反映される。attr() + カスタムプロパティと連動--range-max 等で線形グラデーション。input[min] 変更時に style.setProperty('--range-max', input.max) でダイナミックに再描画。min/max が縮まる。min="-10" max="50" を設定。min/max に反映。geofenceExceeded が発生すると即座に max を下げ、操縦不能域を防ぐ。min を指数的に増加させ、人間は編集不可だがロック解除 API が共通パラメータで使う。max="300" → 運用変更時にロールアウト用スクリプトが自動で 180 秒 へ書換え。min/max を切り替えて「0〜1.0」のフェーズ位置と「1〜24」の FPS 設定でモードを多重化。input[type="range"] にマッピングし、min/max でリング LED と同期。autocomplete="off" + min/max で PWA オフラインキャッシュ指標min/max が残るので、オフラインフォームでも制約が生きる。min/max<template shadowrootmode="open"> に書いた入力欄でも、ホスト要素が外部から動的に属性を更新可能。min と max は「入力範囲の固定値」というイメージを超え、リアルタイムで変わるビジネスルールや安全制約を宣言的に押し出す “双方向インターフェース” に進化させることができます。
JavaScript で値を変えるだけでブラウザー標準のバリデーション機構が再利用できるため、実装コスト < 効果 の非常に高い “モダンなフォーム戦略” と言えます。