日期選擇 (Date Picker)

  • 新提案
  • 封閉測試
  • 公開測試
  • 已穩定
成熟度說明
  • 新提案:未完成開發、請勿使用。
  • 封閉測試:開發暫時性完成,可使用。可能仍有親和力或其他使用上問題待透過實測發現。
  • 公開測試:開發暫時性完成,歡迎使用。具有完整親和力報告。
  • 已穩定:開發完成、歡迎使用。應無任何親和力或使用上問題。

基本 Date Picker

HTML
<!-- Date Picker 元件
     降級策略:
       - 無 JS:顯示 <input type="date">,瀏覽器提供原生 date picker
       - 觸控裝置(pointer: coarse):JS 不增強,維持原生 <input type="date">
       - 桌面 + JS:增強為 combobox + dialog + grid 模式
-->
<div class="dp" data-date-picker>
    <label class="dp__label" for="dp-input">選擇日期</label>
    <div class="dp__input-group">
      <input
        type="date"
        id="dp-input"
        name="date"
        class="dp__input dp__input--native"
      />

      <!--
        JS 增強後插入的 text input(combobox 模式)
        由 JS 動態建立並插入,初始不存在於 HTML
        JS 會加上以下屬性:
          role="combobox"
          aria-haspopup="dialog"
          aria-expanded="false"
          aria-controls="dp-dialog"
          aria-autocomplete="none"
          placeholder="YYYY-MM-DD"
      -->
      <button
        type="button"
        class="dp__button"
        aria-label="開啟月曆"
        aria-expanded="false"
        aria-controls="dp-dialog"
        hidden
      >
        <svg aria-hidden="true" focusable="false" width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
          <path d="M19 4h-1V2h-2v2H8V2H6v2H5C3.89 4 3 4.9 3 6v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 16H5V9h14v11zM7 11h2v2H7zm4 0h2v2h-2zm4 0h2v2h-2z"/>
        </svg>
      </button>
    </div>
    <div
      id="dp-dialog"
      class="dp__dialog"
      role="dialog"
      aria-modal="true"
      aria-label="選擇日期"
      hidden
    >
      <div class="dp__nav">
        <button type="button" class="dp__nav-btn dp__nav-btn--prev-year" aria-label="上一年">
          <svg aria-hidden="true" focusable="false" width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
            <path d="M18.41 7.41L17 6l-6 6 6 6 1.41-1.41L13.83 12zM12.41 7.41L11 6l-6 6 6 6 1.41-1.41L7.83 12z"/>
          </svg>
        </button>
        <button type="button" class="dp__nav-btn dp__nav-btn--prev" aria-label="上個月">
          <svg aria-hidden="true" focusable="false" width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
            <path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/>
          </svg>
        </button>

        <!--
          月份標題:visible text 供視覺使用,內部 span 作為 live region
          (heading 本身作為 aria-live 在部分 AT 不觸發,故另設子 span)
        -->
        <h2
          class="dp__month-heading"
          id="dp-month-label"
        >
          <span class="dp__month-live" aria-live="polite" aria-atomic="true"></span>
        </h2>
        <button type="button" class="dp__nav-btn dp__nav-btn--next" aria-label="下個月">
          <svg aria-hidden="true" focusable="false" width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
            <path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/>
          </svg>
        </button>
        <button type="button" class="dp__nav-btn dp__nav-btn--next-year" aria-label="下一年">
          <svg aria-hidden="true" focusable="false" width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
            <path d="M5.59 7.41L7 6l6 6-6 6-1.41-1.41L10.17 12zM11.59 7.41L13 6l6 6-6 6-1.41-1.41L16.17 12z"/>
          </svg>
        </button>
      </div>
      <table
        role="grid"
        id="dp-grid"
        class="dp__grid"
        aria-labelledby="dp-month-label"
      >
        <thead>
          <tr>
            <th scope="col" aria-label="星期日">日</th>
            <th scope="col" aria-label="星期一">一</th>
            <th scope="col" aria-label="星期二">二</th>
            <th scope="col" aria-label="星期三">三</th>
            <th scope="col" aria-label="星期四">四</th>
            <th scope="col" aria-label="星期五">五</th>
            <th scope="col" aria-label="星期六">六</th>
          </tr>
        </thead>
        <tbody>
          <!--
            由 JS renderCalendar() 動態產生,每列結構如下:
            <tr>
              <td
                class="dp__cell"
                tabindex="-1"
                data-date="2026-03-01"
              >1</td>
              ...
            </tr>
          -->
        </tbody>
      </table>
      <div class="dp__actions">
        <button type="button" class="dp__close-btn">關閉</button>
      </div>
    </div>
    <output
      class="dp__feedback"
      aria-live="polite"
      aria-atomic="true"
    ></output>
</div>

CSS

  • .dp__input--native:原生 <input type="date">,無 JS 或觸控裝置時使用。
  • .dp__input--enhanced:JS 增強後的 text input(role="combobox")。
  • .dp__dialog:月曆面板容器(role="dialog")。
  • .dp__cell-btn:日期格子按鈕,狀態由 aria-current="date"aria-selectedaria-disabled 驅動。
  • .dp__feedback<output> 元素,選取日期後由輔助科技播報。

親和力

  • 採 combobox + dialog + grid 架構,符合 W3C APG Date Picker Dialog 模式。
  • 月曆開啟後方向鍵移動日期、Enter/Space 選取、ESC 關閉、Tab 焦點陷阱。月份與年度切換使用導覽按鈕。
  • 超出 data-min / data-max 範圍的日期使用 aria-disabled="true"(非 HTML disabled),鍵盤仍可經過。
  • 星期欄位由 <th scope="col" aria-label="星期X"> 提供完整語境,日期格子由格線結構自動帶出星期與日期資訊。

JavaScript

  • 使用 date-picker.js
  • 無 JS 時:使用原生 <input type="date">。觸控裝置(pointer: coarse)不啟動自訂月曆,維持原生體驗。

參考