本文へスキップ

日付ピッカー

入力欄をタップするとカレンダーが開き、日を選ぶと入力される。月の前後移動にも対応。

プレビュー

クリック

操作クリック/タップで動きます。

使いどころ

生年月日や予約日のように「特定の1日」を確定させたい入力に向く。カレンダーを見ながら選べるため、日付の書式(YYYY/MM/DDかMM/DD/YYYYか)を覚える必要がなく、入力ミスの入り込む余地も減る。この実装の input は readonly 属性が付いており、直接タイプでの入力は最初から想定されていない。したがって「今日から10年後の日付」のように離れた月まで前月/翌月ボタンだけで移動する必要がある使い方には向かない(クイックジャンプや年選択が無いため、たとえば2010年生まれを選ぶには数十回「前月」を押すことになる)。日付の範囲選択(開始日〜終了日)にも対応しておらず、範囲を扱うなら2つのピッカーを並べるか別途実装を拡張する必要がある。

仕組み

入力欄をクリックすると #dp-cal の hidden 属性をトグルしてカレンダーを開閉する。render() は cur.y/cur.m が指す月について、new Date(y, m, 1).getDay() で月初の曜日オフセットを求めて空セルを敷き詰め、new Date(y, m + 1, 0).getDate() でその月の日数を出して1日ずつボタンを生成する。today との一致で .today クラス、selected との一致で .selected クラスを付け替える。日付を押すと selected を更新し、YYYY/MM/DD形式(padStart(2,"0")で0埋め)を input.value に書き込んでカレンダーを閉じる。カレンダー外のクリックを document 全体の click リスナーで拾い、.dp-wrap の外側なら閉じる仕組みになっている。

実装の注意

最も見落としやすいのは、input が readonly のためキーボードだけでは開閉できないという点である。クリックイベントでのみ開閉するため、Tabでinputにフォーカスを合わせてEnterを押してもカレンダーは開かない(テキスト入力にEnterはデフォルトで何も起こさない)。マウスかタッチでの操作が前提の実装になっており、キーボードのみのユーザーはこのカレンダーに到達できない。次に、オートコンプリート側にはあるEscapeキーでの閉じる処理がこちらには無く、外側クリックでしか閉じられない非対称な挙動になっている。またカレンダーは position: absolute で入力欄の直下に固定表示されるため、画面下端に近い位置で開くとカレンダー本体が画面外にはみ出す可能性があり、上に開く反転ロジックは実装されていない。

アクセシビリティ

現状のマークアップは前月/翌月ボタンに aria-label(「前月」「翌月」)が付いており、日付グリッドの見出し行も曜日のテキストで構成されているなど基礎はできているが、カレンダー全体に role="dialog" や role="grid" のような構造化されたロールが無く、支援技術には「ボタンが並んだ塊」としか伝わらない。より深刻なのは、日付ボタンが30個前後フラットに並んでおり、矢印キーでの移動に対応していないため、キーボードユーザーは月末の日付に到達するのに30回近くTabを押す必要がある。加えて前述の通りinputがreadonlyでキーボードから開けないため、まずそこを解消しない限りキーボード操作は成立しない。選択後の状態変化(「選択されました」のような読み上げ)もaria-liveで用意されていない。正式に使うなら、WAI-ARIA Authoring PracticesのDate Picker Dialogパターン(roving tabindexでの日付移動、Escapeで閉じてinputへフォーカス復帰、aria-liveでの月変更通知)に沿って作り直す必要がある。

調整して使う

readonly をやめて自由入力+blur時のパースに変える、最小/最大日付(min/max)を設けて過去日や特定範囲外を選択不可にする(予約フォームで過去日を防ぐ用途)、週の始まりを日曜から月曜に変える(現状のグリッド見出しは日始まり固定)、「今日」へのショートカットボタンを足す、といった拡張が考えられる。範囲選択が必要な場合は selected を単一の {y,m,d} ではなく開始・終了の2値に拡張し、範囲内の日をハイライトするロジックを render() に追加する形になる。カレンダーの向きも、入力欄が画面下部にある場合は上方向に開くよう出し分けると崩れを防げる。

コード(コピーして使えます)

<div class="dp-wrap">
  <label class="dp-label" for="dp-input">日付</label>
  <div class="dp-field-wrap">
    <input
      id="dp-input"
      class="dp-input"
      type="text"
      placeholder="YYYY/MM/DD"
      readonly
    />
    <span class="dp-icon" aria-hidden="true">📅</span>
  </div>
  <div class="dp-cal" id="dp-cal" hidden>
    <div class="dp-cal-head">
      <button class="dp-nav" id="dp-prev" type="button" aria-label="前月">&#8249;</button>
      <span class="dp-month" id="dp-month"></span>
      <button class="dp-nav" id="dp-next" type="button" aria-label="翌月">&#8250;</button>
    </div>
    <div class="dp-grid-head">
      <span>日</span><span>月</span><span>火</span><span>水</span><span>木</span><span>金</span><span>土</span>
    </div>
    <div class="dp-grid" id="dp-grid"></div>
  </div>
</div>

タグ

同じカテゴリ「フォーム入力」のほかのパーツ

関連パーツ