A tag for creating a time selection input field.
HTML5で導入された<input type="time">
は、ユーザーに時刻(例:14:30など)を簡単かつ正確に入力してもらうためのフォーム部品です。この要素を使うことで、従来のテキスト入力と比べて入力ミス(例:形式の間違いなど)を防ぎやすくなります。また、スマートフォンなどのモバイル端末では、画面上に専用の時刻ピッカーUI(スピナーやホイール型の選択UI)が自動的に表示され、指先で直感的に時刻を選べるようになります。一方、デスクトップ環境では、スピンボックス(数値の上下ボタン)やドロップダウン形式など、プラットフォームごとに最適化されたUIが提供されるため、JavaScriptを使って一からインターフェースを構築するよりも圧倒的に簡単に導入できます。
ただし、この<input type="time">
は「日付の情報を含まない時刻のみ(ローカル時刻)」を扱うため、日付やタイムゾーンの指定はできません。さらに、「秒(SS)」の入力や表示にも一部のブラウザでは非対応であるなど、あまり知られていない制限が存在します。こうした仕様は、初心者にとっては気づきにくい一方で、上級者でも設計段階で見落としやすく、思わぬ不具合の原因となることがあります。
そのため、この要素を使用する際には、ブラウザごとの対応状況や仕様の制限を正しく理解したうえで、用途に応じた適切な使い方を心がけることが重要です。
HTML
<label for="alarm">アラーム時刻:</label>
<input id="alarm" type="time" name="alarm" required>
ここまでで バリデーション(時刻が空なら送信ブロック)と キーボード操作 が既に利用可能です。ブラウザは自動で 24 時間 or 12 時間表示を決めるため、ユーザー側は locale を意識せずに済みます。
HH:MM
(先頭 0 必須、24 時間制固定)HTML
<input type="time" min="09:30" max="17:00">
rangeUnderflow
/rangeOverflow
でネイティブエラーHH:MM
) で書くことHTML
<input type="time" step="900"> <!-- 15 分刻み -->
09:00
に step="3600"
を組み合わせると「毎時ちょうど」のみ許可<input>
と同様autocomplete
キーワードは "off" | "on"
相当のみ(仕様上細粒度キーワードなし)注意 : placeholder
はレンダリングされません。空欄時でもピッカー UI が前面に出るため、視覚的ヒントは <label>
や外部テキストで補う必要があります。
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
$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;
}
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
)へマッピングする必要があります。
for
属性で結びつける。スクリーンリーダーはラベルを読み上げ、操作モードを告知aria-invalid="true"
と aria-describedby
でカスタムメッセージを紐付け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 を活かす プログレッシブエンハンスメント戦略が推奨されます。
iOS は回転式ホイール、Android はダイヤル/キーパッド型など OS ごとにピッカー UI が異なります。これを前提に「完璧に同じ UI」を強要せず、OS ネイティブの操作感を尊重する方がアクセシビリティと保守性を高めます。
ネイティブのピッカーが不都合なケース(秒まで入力したい・カスタムテーマを当てたい等)は、<input type="time">
を隠し、アクセシブルなカスタムピッカー(例:dialog
+role="dialog"
・aria-modal="true"
+ボタン操作)を提供しつつ、値は隠れた input
に同期させる方式が安全です。
モバイルでは「数タップ増えるだけ」で離脱率が跳ね上がります。
15 分単位で良いなら step="900"
(= 15 min)を指定し、ピッカー側で不要な時刻をスキップさせて入力コストを削減します。
営業時間など範囲が決まっている場合は min="09:00"
/ max="18:00"
を設定し、範囲外の候補をそもそも表示させないことで誤入力を防ぎます。
時刻表記は ブラウザのロケールと端末の時刻設定に従って自動で 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 が完成します。
機能検出:
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
が表示されない: ブラウザ仕様。ダミーテキストを期待しないinputEl.stepUp()
/stepDown()
しても step
が大きすぎると失敗min
と max
の論理矛盾: 深夜跨ぎ(例: min="22:00"
max="02:00"
) は 現状非対応required
も付けず送信 → 空文字が飛ぶ。null 判定を忘れずに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>
</>
);
}
秒への換算は正規表現+分割で関数化して再利用するとバグを抑制できます。
Shadow DOM 内に <input type="time">
をラップし、observedAttributes
で min
/max
/step
を動的管理。Form Associated Custom Elements を使えば <form>
とネイティブ同等に連携可能です。
/\A([01]\d|2[0-3]):[0-5]\d\z/
を回すmin=09:00 step=3600
で 09:59
を入力 → エラー通知されるか<input type="time">
は ローカル時刻用の軽量 UI を半自動で提供してくれる便利要素ですが、
placeholder
が効かないといった制約を理解し、バリデーション・アクセシビリティ・フォールバック を組み合わせてはじめて “頑丈” なコンポーネントになります。
実運用では サーバー側での正規化 と テスト自動化 を最優先に据え、ユーザー体験を壊さないラインでネイティブ UI を活かしましょう。