HTML
この記事では、入力できる値の範囲を指定するHTMLのmin属性とmax属性の基本を説明します。
この記事でわかること
minとmaxが制限する範囲stepとの組み合わせ- ブラウザー検証とサーバー検証の違い
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>
- そもそも何を解決するのか
- 属性
- 基本の書き方と動作の流れ
- 対応している type
- ブラウザー UI とバリデーション挙動の差異
- step と組み合わせるときの注意点
- アクセシビリティ: 画面読み上げとの統合
- JavaScript での制御例
- サーバー側バリデーションの必然
- 実践シナリオ
- よくある落とし穴
- ベストプラクティスまとめ
- よく使われる 15 のユースケース
- ありきたりではない「min / max」の使い倒しアイデア集
そもそも何を解決するのか
input 要素の min と max 属性を指定すると、数値型や日付型の入力欄で入力できる最小値と最大値を指定することができます。
自由入力フィールドは便利ですが、想定より小さすぎる数や未来すぎる日付が送られてくるとビジネスロジックが壊れます。
min と max 属性は「この範囲内しか受け付けません」とブラウザーに宣言することで、
- ユーザーの入力ミスを即座に防ぐ
- サーバーへの不要なリクエストを減らす
- UI コンポーネント(スピンボタン・カレンダーなど)の初期位置を決める
という三つの効果を同時に達成します。
属性
- 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
| 直感的用途 | 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 |
ブラウザー UI とバリデーション挙動の差異
- Chrome / Edge
- 範囲外入力時に赤枠+ツールチップ
- Firefox
- ツールチップ文言が独自(日本語ロケール可)
- Safari (iOS)
- モバイルキーボードの 増減ボタン が
min/maxに合わせて停止 - 古い IE
type="number"未実装。polyfill か JavaScript でフォールバック必須
step と組み合わせるときの注意点
step が min と “割り切れない” 場合、ブラウザーは「最も近い有効値」を提案 しつつ、依然として制約エラー (stepMismatch) を返します。
HTML
<!-- 例: 0.3 刻みだが min は 0.2 -->
<label for="step-number">0.2から1まで(0.3刻み):</label>
<input type="number" id="step-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領域で自前通知が確実
JavaScript での制御例
0〜120 の整数で入力してください
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"を付け、スクリーンリーダーでも通知されるようにしています。
setCustomValidity() を使ったサンプル
0〜120 の整数で入力してください
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のみ許可」を意味します。
ベストプラクティスまとめ
- UI・UX
- 境界値をラベルやプレースホルダーにも明示
- アクセシビリティ
- エラー時は
aria-liveで動的メッセージを読ませる - セキュリティ
- サーバー側で同一制約を再検証
- 保守性
- 境界値は 定数化 し、クライアントと API で一元管理
- 互換性
- 非対応ブラウザー検出時は Polyfill か
pattern属性で代替検証
よく使われる 15 のユースケース
数字・日付・時刻など “連続値” を扱う入力欄では、境界値を宣言するだけで UX と品質が大きく向上 します。以下は実務で頻出するシーンをタイプ別にまとめたものです。
数値 (type="number" / type="range")
- 年齢登録フォーム
min="0"max="120"で非現実的な値をブロック。- チケット枚数・商品の購入数量
min="1"max="10"など在庫上限に合わせて制限。- EC サイトの価格フィルター
- 上限付きのスライダー (
range) でmin="0"max="50000"。 - パスワード長さのカスタム設定
- 管理画面で
min="6"max="128"を指定し、不適切な長さを防止。 - 動画再生速度や音量のスライダー
min="0.25"max="2"step="0.05"で倍速範囲をコントロール。
日付 (type="date" / type="datetime-local")
- 誕生日入力
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"で年度開始以降のみ許可。
時刻 (type="time")
- 営業時間内の問い合わせ予約
min="09:00"max="18:00"で深夜帯を自動却下。- レストランのディナー席予約
min="17:30"max="22:00"、さらにstep="900"(15 分刻み)で UX 向上。
月・週 (type="month" / type="week")
- 家計簿の入力期間
type="month"でmin="2020-01"max="<?= date('Y-m') ?>"。- スクラムのスプリント週選択
type="week"で四半期内 (min="2025-W27"max="2025-W39") に限定。
システム設定・管理系
- ファイルアップロードの同時実行数
- 管理 UI で
min="1"max="8"— サーバー負荷をコントロール。 - メール送信リトライ回数
min="0"max="5"を設定し、過度なリトライを防止。
まとめ
min と max は 入力制御・データ品質・アクセシビリティを同時に底上げできる “宣言的ガードレール” です。ユーザー体験を損なわず、サーバー側のバリデーションと二重化することで、安全かつ保守性の高いフォームを実現できます。
ありきたりではない「min / max」の使い倒しアイデア集
単に「範囲を決める」だけではもったいない。
先進的な UI/UX やドメインロジックと組み合わせることで、min と max は “リアクティブ・パラメータ” へと進化します。
動的境界値 — 他フィールドや外部データをトリガーに即時更新
- 開始日・終了日ペア
- 終了日の
minを「開始日 + 1 日」に JavaScript で随時書き換え。 changeイベントでend.min = dayjs(start.value).add(1, 'day').format('YYYY‑MM‑DD')。- 株価・暗号資産のリアルタイム取引
- WebSocket で受け取った板情報に応じて、価格入力欄の
min/maxを毎秒更新。 - 送信前に
input.reportValidity()を呼び、自動的に最新の “許容スプレッド” を強制。
ユーザーごとにパーソナライズされた閾値
- 年齢制限付きコンテンツ
- サーバーから返すユーザープロファイルに応じて
min="18"もしくはmin="21"に切り替え。 - クレジット残高に連動する購入数量
- API から取得した
remainingCreditsをそのままmaxにバインド。 - フロント側で値が変わるたびに
maxを書き換えることで、二重送信を防止。
非線形スケールをラップする“ゲート”としての利用
- 音量やゲインを対数スケールで制御
- 実際に送る値は
Math.pow(10, dB/20)だが、ユーザー入力は dB。 min="-60"max="0"にしておき、変換はinputハンドラで行う。- ゲームの難易度スライダー
- UI は 0〜100 だが、内部ロジックは指数関数で敵 HP を計算。
min/maxが UI の手掛かり、実際のゲームバランスは JS 側で調整。
アクセシビリティ & ガイダンス向上のための“適応的バリデーション”
- エラー時に境界を“寄せる”セルフヒーリングフォーム
- 入力が
min未満ならminをその値に一旦合わせてsetCustomValidity('')→ ユーザーが再入力しやすい余白を作る。 - 送信前にサーバー閾値へ戻すことでセキュリティも両立。
aria-live+min/maxの協調min/max変更時に「入力可能範囲が変わりました」とライブリージョンへ通知し、視覚障害者にも即時共有。
スキーマ駆動開発との連携
- GraphQL / JSON Schema → HTML 生成
- バックエンドの型定義 (
minLength,maximum) をビルド時に静的 HTML へ埋め込み。 - “サーバーと同一ソース” なのでバリデーションの ドリフトゼロ。
- フォームビルダー SaaS でのメタ・テンプレート
- 設計画面で管理者が数値を入力すると、そのまま渡された値が公開フォームの
min/maxへ反映される。
UI コンポーネントの状態同期
- CSS の
attr()+ カスタムプロパティと連動 - スライダーのトラック色を
--range-max等で線形グラデーション。 input[min]変更時にstyle.setProperty('--range-max', input.max)でダイナミックに再描画。- 複数スライダーの“相互拘束”
- 2 軸フィルター (例: 価格・期間) で、一方の変更に合わせてもう一方の
min/maxが縮まる。 - 交差しそうになったら JS でスワップし、ユーザーは常に「左 ≤ 右」を保てる。
IoT・リアルタイム制御ダッシュボード
- 温室制御アプリ
- センサーが壊れたときに飛び値を送らない保険として
min="-10"max="50"を設定。 - 設定温度を変えるごとに MQTT 経由で許容範囲を再配布し、UI と実機が同期。
- ドローン操縦アプリ
- バッテリー残量や飛行高度の安全域を
min/maxに反映。 - フロント側で
geofenceExceededが発生すると即座にmaxを下げ、操縦不能域を防ぐ。
セキュリティ・レートリミットのフロント提示
- パスワード再試行インターバル
- 連続失敗回数に応じて待機秒数入力欄の
minを指数的に増加させ、人間は編集不可だがロック解除 API が共通パラメータで使う。 - 2FA トークン残存時間調整
- 管理 UI で
max="300"→ 運用変更時にロールアウト用スクリプトが自動で 180 秒 へ書換え。
アニメーションやゲームエンジン的応用
- シークバーを“キーフレームスキップ”用に変形
min/maxを切り替えて「0〜1.0」のフェーズ位置と「1〜24」の FPS 設定でモードを多重化。- Web MIDI コントローラー
- 物理ツマミの範囲(0–127)を
input[type="range"]にマッピングし、min/maxでリング LED と同期。
実験的 API・仕様とのハイブリッド
autocomplete="off"+min/maxで PWA オフラインキャッシュ指標- ネットワーク無効時でもキャッシュ済み
min/maxが残るので、オフラインフォームでも制約が生きる。 - Declarative Shadow DOM 内での
min/max <template shadowrootmode="open">に書いた入力欄でも、ホスト要素が外部から動的に属性を更新可能。- マイクロフロントエンドで “独立デプロイ & 共通制約” を実現。
まとめ
min と max は「入力範囲の固定値」というイメージを超え、リアルタイムで変わるビジネスルールや安全制約を宣言的に押し出す “双方向インターフェース” に進化させることができます。
- 動的・個別パーソナライズで UX 最適化
- 非線形マッピングや複数フィールド拘束で複雑ロジックを単純化
- IoT・セキュリティ領域で「ハードガード」の前段階として活用
JavaScript で値を変えるだけでブラウザー標準のバリデーション機構が再利用できるため、実装コスト < 効果 の非常に高い “モダンなフォーム戦略” と言えます。