HTML
この記事では、時刻だけを入力するフォーム部品<input type="time">の基本と、日付やタイムゾーンを扱えない制限を説明します。
A tag for creating a time selection input field.
<input type="time">
はじめに
HTML5で導入された<input type="time">は、ユーザーに時刻(例:14:30など)を簡単かつ正確に入力してもらうためのフォーム部品です。この要素を使うことで、従来のテキスト入力と比べて入力ミス(例:形式の間違いなど)を防ぎやすくなります。また、スマートフォンなどのモバイル端末では、画面上に専用の時刻ピッカーUI(スピナーやホイール型の選択UI)が自動的に表示され、指先で直感的に時刻を選べるようになります。一方、デスクトップ環境では、スピンボックス(数値の上下ボタン)やドロップダウン形式など、プラットフォームごとに最適化されたUIが提供されるため、JavaScriptを使って一からインターフェースを構築するよりも圧倒的に簡単に導入できます。
ただし、この<input type="time">は「日付の情報を含まない時刻のみ(ローカル時刻)」を扱うため、日付やタイムゾーンの指定はできません。さらに、「秒(SS)」の入力や表示にも一部のブラウザでは非対応であるなど、あまり知られていない制限が存在します。こうした仕様は、初心者にとっては気づきにくい一方で、上級者でも設計段階で見落としやすく、思わぬ不具合の原因となることがあります。
そのため、この要素を使用する際には、ブラウザごとの対応状況や仕様の制限を正しく理解したうえで、用途に応じた適切な使い方を心がけることが重要です。
最小構成 ―― “まず動かす”
HTML
<form>
<label for="alarm">アラーム時刻:</label>
<input id="alarm" type="time" name="alarm" required>
<button type="submit">送信</button>
</form>
フォームに含めることで、バリデーション(時刻が空なら送信ブロック)とキーボード操作が既に利用可能です。このデモでは送信後の画面遷移を止めています。ブラウザは自動で24時間または12時間表示を決めるため、ユーザー側はlocaleを意識せずに済みます。
主要属性と振る舞い
value
- 形式は
HH:MM(先頭 0 必須、24 時間制固定) - サーバーには 常に 上記フォーマットで送信されます
- 秒を含める仕様は草案止まり。現行ブラウザでは入力不可
min / max
HTML
<input type="time" min="09:30" max="17:00">
- 入力値が範囲外だと
rangeUnderflow/rangeOverflowでネイティブエラー - 一貫性確保のため 同じフォーマット (
HH:MM) で書くこと
step
HTML
<input type="time" step="900"> <!-- 15 分刻み -->
- 単位は 秒。900 = 15 分
- デフォルトは 60 秒
- たとえば
09:00にstep="3600"を組み合わせると「毎時ちょうど」のみ許可
required, disabled, readonly, autofocus, autocomplete, list
- 通常の
<input>と同様 autocompleteキーワードは"off" | "on"相当のみ(仕様上細粒度キーワードなし)
注意 : placeholder はレンダリングされません。空欄時でもピッカー UI が前面に出るため、視覚的ヒントは <label> や外部テキストで補う必要があります。
JavaScript API
JavaScript
const el = document.querySelector('#alarm');
// 文字列として取得 / 設定
console.log(el.value); // "07:45" など
el.value = '08:00';
// 数値 (ミリ秒) で扱いたい場合
console.log(el.valueAsNumber); // 27000000 = 7.5h * 3600 s * 1000ms
el.valueAsNumber += 15 * 60 * 1000; // 15 分進める
// Date オブジェクト化(UTC 1970-01-01 基準)
const d = el.valueAsDate; // 1970-01-01T07:45:00.000Z
ブラウザ間差異がやや大きいため、数値演算を伴う場合は valueAsNumber を基準にして丸め誤差を防ぎます。
フォーム送信とサーバーサイド処理
PHP
PHP
$time = $_POST['alarm'] ?? ''; // "07:45"
if (preg_match('/\A([01]\d|2[0-3]):[0-5]\d\z/', $time)) {
[$h, $m] = explode(':', $time);
$seconds = $h * 3600 + $m * 60;
}
Node.js (Express)
JavaScript
app.post('/reserve', (req, res) => {
const { start } = req.body; // "18:30"
if (!/^\d{2}:\d{2}$/.test(start)) return res.status(400).end();
const [h, m] = start.split(':').map(Number);
const seconds = h * 3600 + m * 60;
});
重要ポイント : <input type="time"> 自体には タイムゾーン情報が含まれません。ユーザーのローカル時刻として解釈し、サーバー到着後にアプリケーション固有の TZ(例: Asia/Tokyo)へマッピングする必要があります。
アクセシビリティ & UX
- ラベル
for属性で結びつける。スクリーンリーダーはラベルを読み上げ、操作モードを告知- エラーメッセージ
aria-invalid="true"とaria-describedbyでカスタムメッセージを紐付け- フォーカスリング
- ブラウザ既定の outline を尊重しつつ、CSS でコントラストを高める
- キーボード
ArrowUp/Downで分・時間を増減、Home/Endで最小/最大にジャンプ(ブラウザ依存あり)
スタイリングのコツ
CSS
/* アイコンを非表示にして独自 UI だけを見せる例 (Chrome / Edge) */
input[type="time"]::-webkit-calendar-picker-indicator {
opacity: 0;
pointer-events: none;
}
/* フォーカス時に時計アイコンをスライドインさせるイメージ */
input[type="time"]:focus-visible + .clock-icon {
transform: translateX(0); /* 初期は -100% で隠す */
}
Firefox は ::-moz-focus-inner/appearance で、Safari は現状カスタムしづらい点に注意。完全なクロスブラウザデザイン を目指すなら、隠さずネイティブ UI を活かす プログレッシブエンハンスメント戦略が推奨されます。
スマートフォン実装で押さえるポイント
デフォルト UI の違いを理解する
iOS は回転式ホイール、Android はダイヤル/キーパッド型など OS ごとにピッカー UI が異なります。これを前提に「完璧に同じ UI」を強要せず、OS ネイティブの操作感を尊重する方がアクセシビリティと保守性を高めます。
ネイティブのピッカーが不都合なケース(秒まで入力したい・カスタムテーマを当てたい等)は、<input type="time"> を隠し、アクセシブルなカスタムピッカー(例:dialog+role="dialog"・aria-modal="true"+ボタン操作)を提供しつつ、値は隠れた input に同期させる方式が安全です。
step・min・max 属性でタップ数を最小化
モバイルでは「数タップ増えるだけ」で離脱率が跳ね上がります。
15 分単位で良いなら step="900"(= 15 min)を指定し、ピッカー側で不要な時刻をスキップさせて入力コストを削減します。
営業時間など範囲が決まっている場合は min="09:00" / max="18:00" を設定し、範囲外の候補をそもそも表示させないことで誤入力を防ぎます。
24 h/12 h 表記の自動切替にまかせる
時刻表記は ブラウザのロケールと端末の時刻設定に従って自動で 24 h / 12 h が切り替わります。
アプリ側で強制したい場合はカスタムピッカーに切り替えるしかありませんが、ユーザーの慣れた表記を壊すリスクが高いので「表示はローカライズ任せ、内部ロジックは常に HH:MM 24 h で扱う」設計が推奨されます。
バリデーションは「入力直後」+「送信前」の二段階
タッチ UI ではキーボードを閉じた瞬間に即時フィードバックを返さないと再入力が面倒になります。input イベントで簡易チェック → invalid / submit で確定チェックの二層に分け、エラーは フィールド直下に短文で表示。
フォーカス移動時にスクロール位置がずれやすいので、エラー要素へ scrollIntoView({block:"center"}) を添えると UX が安定します。
タッチ目標サイズとスペース確保
ガイドライン目安は 幅・高さともに 48 px(9 mm)以上、間隔 8 px 以上。
インラインで <input type="time"> を置く場合でも、ピッカー呼び出しアイコン・ラベル含めてタップ領域を確保し、意図しない隣接要素タップを避けます。
フォールバック戦略を持つ
今も一部の 旧 Android WebView/組み込みブラウザ は type="time" をサポートしません。非対応環境では type="text" として振る舞うため、プレースホルダーにフォーマット例(例: 09:30)を必ず入れるか、先頭で pattern="^([01]\d|2[0-3]):[0-5]\d$" を与えて手動入力を保護します。
さらに JavaScript でサポート検出を行い、未サポートなら軽量なポリフィル(inputmask・tiny-date-picker など)を動的読み込みすると UX が保たれます。
仮想キーボードのレイアウト崩れ対策
<input> が画面下部にある場合、キーボード出現でピッカーが隠れることがあります。
重要フォームでは scroll-padding-bottom に env(safe-area-inset-bottom)+80 px などを指定し、ピッカー or キーボード分だけ自動でスクロール余白を確保するとレイアウトジャンプを防げます。
ダークモード/高コントラストモード対応
モバイルは OS ダークモード比率が高いので、カスタムピッカーを実装する場合は prefers-color-scheme で配色を分岐。
システム UI をそのまま使う場合でも、隣接する入力枠・ラベルが背景と同化しないよう color-scheme: light dark; と background-color: Canvas 相当の変数を併用すると一貫性が高まります。
フォームの自動ズーム抑止
iOS Safari はフォントサイズ 16 px 未満の入力要素をタップすると自動ズームします。ズーム→ピッカー表示→戻る、の流れはユーザーが迷子になりやすいので、入力要素のフォントは必ず 16 px 以上に設定し、ズーム発生を防ぎましょう。
高速化とパフォーマンス
モバイルでは JavaScript 実行コストが直にバッテリーと熱に跳ね返ります。
カスタムピッカーの場合でも Shadow DOM+CSS Animation で極力 DOM 操作を減らし、スクロール同期や時刻リスト生成は requestAnimationFrame 内で行うとフレーム落ちが起きにくくなります。
フォント読み込み遅延があるとミリ秒単位で表示がブレるので、font-display: swap 指定で CLS を抑えてください。
これらを踏まえて実装すれば、OS 標準ピッカーの利点を活かしつつ、非対応環境や視認性・操作性の問題にも備えたモバイル向け<input type="time"> UI が完成します。
Fallback / Polyfill 戦略 ―― 旧ブラウザ & 秒単位対応
機能検出:
JavaScript
const supportsTime = (() => {
const i = document.createElement('input');
i.setAttribute('type', 'time');
return i.type === 'time';
})();
flatpickr + timePlugin などの軽量ライブラリを条件付きで読み込む
pattern="\d{2}:\d{2}", inputmode="numeric" を併用し、最低限のモバイル対応を担保
秒が必要なら hidden フィールドに格納し、UI は polyfill 上で実装する
よくある落とし穴
- 秒値がトリミングされる:
<input type="time" value="08:15:30">は 08:15 に丸められる placeholderが表示されない: ブラウザ仕様。ダミーテキストを期待しない- step 不一致エラー (stepMismatch): JS で
inputEl.stepUp()/stepDown()してもstepが大きすぎると失敗 minとmaxの論理矛盾: 深夜跨ぎ(例:min="22:00"max="02:00") は 現状非対応- 送信時の空文字列: 値が未入力で
requiredも付けず送信 → 空文字が飛ぶ。null 判定を忘れずに
高度なユースケース
React で “Controlled” に扱う
JavaScript
export default function TimeField() {
const [time, setTime] = useState('12:00');
return (
<>
<label htmlFor="start">開始時刻</label>
<input
id="start"
type="time"
value={time}
onChange={e => setTime(e.target.value)}
step={300}
min="09:00"
max="21:00"
/>
<span>{`→ 秒換算: ${toSeconds(time)}s`}</span>
</>
);
}
秒への換算は正規表現+分割で関数化して再利用するとバグを抑制できます。
Web Components でカスタム要素化
Shadow DOM 内に <input type="time"> をラップし、observedAttributes で min/max/step を動的管理。Form Associated Custom Elements を使えば <form> とネイティブ同等に連携可能です。
テストと QA チェックリスト
- フォーマット検証
- 自動テストで
/\A([01]\d|2[0-3]):[0-5]\d\z/を回す - ロケール差
- 英語・日本語・アラビア語 UI でラベルオーバーフローを確認
- アクセシビリティ
- キーボードのみで開始→値入力→送信→エラー確認の一連を完走
- 境界ケース
min=09:00 step=3600で09:59を入力 → エラー通知されるか- サマータイム
- TZ が DST 切替日に跨る場合、アプリ側でローカル値がずれないか
まとめ
<input type="time"> は ローカル時刻用の軽量 UI を半自動で提供してくれる便利要素ですが、
- 秒・タイムゾーンを持たない
placeholderが効かない- モバイル挙動が多様
といった制約を理解し、バリデーション・アクセシビリティ・フォールバック を組み合わせてはじめて “頑丈” なコンポーネントになります。
実運用では サーバー側での正規化 と テスト自動化 を最優先に据え、ユーザー体験を壊さないラインでネイティブ UI を活かしましょう。