Webパーツ辞典

期間選択Date range picker

「開始日〜終了日」の2つの欄と、間の日が帯で塗られたカレンダーが組になった入力。「今月」「先月」のボタンで一度に選べることも。集計や宿泊の期間に使う。

表示例

集計期間

年/月/日 で入力

アイコン: Font Awesome 7 Free(fa-solid / fa-regular)

別名・英語表記

  • 日付範囲ピッカー
  • 期間ピッカー
  • レンジカレンダー
  • 開始日と終了日
  • 日付範囲の入力
  • Date range picker

実装例

<div class="x-date-range-picker">
  <fieldset class="x-date-range-picker__fs">
    <legend class="x-date-range-picker__lg">集計期間</legend>
    <div class="x-date-range-picker__row">
      <span class="x-date-range-picker__f">
        <label class="x-date-range-picker__l" for="x-date-range-picker-s">開始日</label>
        <input class="x-date-range-picker__i" id="x-date-range-picker-s" type="text" name="from"
               inputmode="numeric" value="2026/09/07" aria-describedby="x-date-range-picker-fmt">
      </span>
      <span class="x-date-range-picker__sep" aria-hidden="true">〜</span>
      <span class="x-date-range-picker__f">
        <label class="x-date-range-picker__l" for="x-date-range-picker-e">終了日</label>
        <input class="x-date-range-picker__i" id="x-date-range-picker-e" type="text" name="to"
               inputmode="numeric" value="2026/09/15" aria-describedby="x-date-range-picker-fmt">
      </span>
      <button class="x-date-range-picker__b" type="button" aria-label="カレンダーで期間を選ぶ"
              aria-haspopup="dialog" aria-expanded="true" aria-controls="x-date-range-picker-d">
        <i class="fa-solid fa-calendar-days" aria-hidden="true"></i>
      </button>
    </div>
    <p class="x-date-range-picker__vh" id="x-date-range-picker-fmt">年/月/日 で入力</p>
  </fieldset>
  <!-- 見本なので開いた状態。実際は開いたときだけ表示し、aria-modal="true" を付けて開始日へフォーカスを移す -->
  <div class="x-date-range-picker__d" id="x-date-range-picker-d" role="dialog" aria-labelledby="x-date-range-picker-h">
    <div class="x-date-range-picker__nav">
      <button class="x-date-range-picker__m" type="button" aria-label="前の月">
        <i class="fa-solid fa-chevron-left" aria-hidden="true"></i>
      </button>
      <h2 class="x-date-range-picker__h" id="x-date-range-picker-h" aria-live="polite">2026年9月</h2>
      <button class="x-date-range-picker__m" type="button" aria-label="次の月">
        <i class="fa-solid fa-chevron-right" aria-hidden="true"></i>
      </button>
    </div>
    <!-- 1回目に押した日が開始日、2回目が終了日。終了日より前を押したら開始日を差し替える -->
    <table class="x-date-range-picker__g" role="grid" aria-labelledby="x-date-range-picker-h">
      <thead>
        <tr>
          <th scope="col" abbr="日曜日">日</th><th scope="col" abbr="月曜日">月</th>
          <th scope="col" abbr="火曜日">火</th><th scope="col" abbr="水曜日">水</th>
          <th scope="col" abbr="木曜日">木</th><th scope="col" abbr="金曜日">金</th>
          <th scope="col" abbr="土曜日">土</th>
        </tr>
      </thead>
      <tbody>
        <tr><td></td><td></td><td tabindex="-1">1</td><td tabindex="-1">2</td><td tabindex="-1">3</td><td tabindex="-1">4</td><td tabindex="-1">5</td></tr>
        <tr><td tabindex="-1">6</td><td tabindex="0" aria-selected="true" class="x-date-range-picker__s" aria-label="7日 開始日">7</td><td tabindex="-1" class="x-date-range-picker__in">8</td><td tabindex="-1" class="x-date-range-picker__in">9</td><td tabindex="-1" class="x-date-range-picker__in">10</td><td tabindex="-1" class="x-date-range-picker__in">11</td><td tabindex="-1" class="x-date-range-picker__in">12</td></tr>
        <tr><td tabindex="-1" class="x-date-range-picker__in">13</td><td tabindex="-1" class="x-date-range-picker__in">14</td><td tabindex="-1" aria-selected="true" class="x-date-range-picker__e" aria-label="15日 終了日">15</td><td tabindex="-1">16</td><td tabindex="-1">17</td><td tabindex="-1">18</td><td tabindex="-1">19</td></tr>
        <tr><td tabindex="-1">20</td><td tabindex="-1">21</td><td tabindex="-1">22</td><td tabindex="-1">23</td><td tabindex="-1">24</td><td tabindex="-1" aria-current="date">25</td><td tabindex="-1">26</td></tr>
        <tr><td tabindex="-1">27</td><td tabindex="-1">28</td><td tabindex="-1">29</td><td tabindex="-1">30</td><td></td><td></td><td></td></tr>
      </tbody>
    </table>
  </div>
</div>

AIへの指示文

「期間選択(Date range picker)」を実装してください。

## これは何か
「開始日〜終了日」の2つの欄と、間の日が帯で塗られたカレンダーが組になった入力。「今月」「先月」のボタンで一度に選べることも。集計や宿泊の期間に使う。

## 使いどころ
- 売上の集計期間・ログの検索期間のように、始まりと終わりの2つの日付を組で決めてもらうとき
- 宿泊や貸し出しの期間のように、何日間になるかをカレンダーの上で見せたいとき
- 「今月」「先月」「過去7日」のように、よく使う期間をひと押しで選ばせたいとき

## 実装の要件
- fieldset > legend + label + input(開始日)+ 〜 + label + input(終了日)+ button[aria-haspopup=dialog][aria-expanded] + div[role=dialog] > 期間のボタン + 月の見出し + table[role=grid]
- カレンダーは1回目に押した日を開始日、2回目を終了日にする。終了日を選ぶ前は、マウスを乗せた日まで仮に塗ると期間が先に見える
- input type="date" を2つ並べて済ませるなら、終了日の min を開始日に合わせて更新する
- 日付を1つだけ選ぶなら日付ピッカーで足りる。期間選択は、2つの日付の前後関係と間の日数に意味があるときに使う
- 終了日が開始日より前にならないようにする。カレンダーで開始日より前を押したら開始日を差し替える、欄に直接入れたらエラーを出す、のように決めておく
- 開始日と終了日のそれぞれに直接入力の道を残す。カレンダーだけにすると、日付が決まっている人ほど遅くなる
- 期間の端を含むかを書く。「9月7日〜9月15日」が15日を含むのかを画面で示し、集計側とそろえる
- 月をまたぐ期間が多いなら、カレンダーを2か月並べる。1か月表示だと月送りしながら終了日を探すことになる

## アクセシビリティの要件
- 2つの欄を fieldset でくくり、legend を「集計期間」にする。各欄の label は「開始日」「終了日」
- カレンダーは role="grid" の表にし、矢印キーで1日・1週、PageUp / PageDown で1か月動かす(WAI-ARIA APG の Date Picker Dialog にならう)
- 開始日と終了日は aria-selected="true" に加え、名前に「開始日」「終了日」を含める。塗りの違いは読み上げに乗らない
- 期間の中の日は薄い塗り、両端は濃い塗りと太字、のように色の差だけにしない(達成基準 1.4.1 色の用途)
- カレンダーを閉じたら、開いたボタンへフォーカスを戻す

## 参考にする実装
この形を出発点にして、プロジェクトの命名規則・色・余白に合わせて調整してください。

```html
<div class="x-date-range-picker">
  <fieldset class="x-date-range-picker__fs">
    <legend class="x-date-range-picker__lg">集計期間</legend>
    <div class="x-date-range-picker__row">
      <span class="x-date-range-picker__f">
        <label class="x-date-range-picker__l" for="x-date-range-picker-s">開始日</label>
        <input class="x-date-range-picker__i" id="x-date-range-picker-s" type="text" name="from"
               inputmode="numeric" value="2026/09/07" aria-describedby="x-date-range-picker-fmt">
      </span>
      <span class="x-date-range-picker__sep" aria-hidden="true">〜</span>
      <span class="x-date-range-picker__f">
        <label class="x-date-range-picker__l" for="x-date-range-picker-e">終了日</label>
        <input class="x-date-range-picker__i" id="x-date-range-picker-e" type="text" name="to"
               inputmode="numeric" value="2026/09/15" aria-describedby="x-date-range-picker-fmt">
      </span>
      <button class="x-date-range-picker__b" type="button" aria-label="カレンダーで期間を選ぶ"
              aria-haspopup="dialog" aria-expanded="true" aria-controls="x-date-range-picker-d">
        <i class="fa-solid fa-calendar-days" aria-hidden="true"></i>
      </button>
    </div>
    <p class="x-date-range-picker__vh" id="x-date-range-picker-fmt">年/月/日 で入力</p>
  </fieldset>
  <!-- 見本なので開いた状態。実際は開いたときだけ表示し、aria-modal="true" を付けて開始日へフォーカスを移す -->
  <div class="x-date-range-picker__d" id="x-date-range-picker-d" role="dialog" aria-labelledby="x-date-range-picker-h">
    <div class="x-date-range-picker__nav">
      <button class="x-date-range-picker__m" type="button" aria-label="前の月">
        <i class="fa-solid fa-chevron-left" aria-hidden="true"></i>
      </button>
      <h2 class="x-date-range-picker__h" id="x-date-range-picker-h" aria-live="polite">2026年9月</h2>
      <button class="x-date-range-picker__m" type="button" aria-label="次の月">
        <i class="fa-solid fa-chevron-right" aria-hidden="true"></i>
      </button>
    </div>
    <!-- 1回目に押した日が開始日、2回目が終了日。終了日より前を押したら開始日を差し替える -->
    <table class="x-date-range-picker__g" role="grid" aria-labelledby="x-date-range-picker-h">
      <thead>
        <tr>
          <th scope="col" abbr="日曜日">日</th><th scope="col" abbr="月曜日">月</th>
          <th scope="col" abbr="火曜日">火</th><th scope="col" abbr="水曜日">水</th>
          <th scope="col" abbr="木曜日">木</th><th scope="col" abbr="金曜日">金</th>
          <th scope="col" abbr="土曜日">土</th>
        </tr>
      </thead>
      <tbody>
        <tr><td></td><td></td><td tabindex="-1">1</td><td tabindex="-1">2</td><td tabindex="-1">3</td><td tabindex="-1">4</td><td tabindex="-1">5</td></tr>
        <tr><td tabindex="-1">6</td><td tabindex="0" aria-selected="true" class="x-date-range-picker__s" aria-label="7日 開始日">7</td><td tabindex="-1" class="x-date-range-picker__in">8</td><td tabindex="-1" class="x-date-range-picker__in">9</td><td tabindex="-1" class="x-date-range-picker__in">10</td><td tabindex="-1" class="x-date-range-picker__in">11</td><td tabindex="-1" class="x-date-range-picker__in">12</td></tr>
        <tr><td tabindex="-1" class="x-date-range-picker__in">13</td><td tabindex="-1" class="x-date-range-picker__in">14</td><td tabindex="-1" aria-selected="true" class="x-date-range-picker__e" aria-label="15日 終了日">15</td><td tabindex="-1">16</td><td tabindex="-1">17</td><td tabindex="-1">18</td><td tabindex="-1">19</td></tr>
        <tr><td tabindex="-1">20</td><td tabindex="-1">21</td><td tabindex="-1">22</td><td tabindex="-1">23</td><td tabindex="-1">24</td><td tabindex="-1" aria-current="date">25</td><td tabindex="-1">26</td></tr>
        <tr><td tabindex="-1">27</td><td tabindex="-1">28</td><td tabindex="-1">29</td><td tabindex="-1">30</td><td></td><td></td><td></td></tr>
      </tbody>
    </table>
  </div>
</div>
```

```css
/* 貼った先のリセットに頼らないよう、この部品の中だけ border-box にする */
.x-date-range-picker,
.x-date-range-picker *:not([class*="fa-"]),
.x-date-range-picker *:not([class*="fa-"])::before,
.x-date-range-picker *:not([class*="fa-"])::after {
  box-sizing: border-box;
  /* 貼った先の既定の書体に引きずられないようにする。書体が変わると
     文字の幅と高さが変わり、枠のサイズまでずれる */
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Hiragino Sans", "Noto Sans JP", "Yu Gothic UI", Meiryo, sans-serif;
}

.x-date-range-picker {
  width: 300px;
  max-width: 100%;
  color: #1b2028;
}
.x-date-range-picker__fs {
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}
.x-date-range-picker__lg {
  padding: 0;
  margin-bottom: 4px;
  font-size: 12px;
  font-weight: 700;
}
.x-date-range-picker__row {
  display: flex;
  align-items: flex-end;
  gap: 4px;
}
.x-date-range-picker__f {
  display: flex;
  flex: 1 1 0;
  flex-direction: column;
  min-width: 0;
}
.x-date-range-picker__l {
  margin-bottom: 2px;
  color: #5b6472;
  font-size: 11px;
}
.x-date-range-picker__i {
  width: 100%;
  height: 36px;
  padding: 0 8px;
  border: 1px solid #7f8a99;
  border-radius: 8px;
  background: #fff;
  color: #1b2028;
  font: inherit;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}
.x-date-range-picker__sep {
  flex: none;
  padding-bottom: 9px;
  font-size: 13px;
}
.x-date-range-picker__b {
  width: 36px;
  height: 36px;
  flex: none;
  margin-left: 2px;
  border: 1px solid #7f8a99;
  border-radius: 8px;
  background: #fff;
  color: #1b2028;
  cursor: pointer;
}
/* 実際のページでは position: absolute で入力欄の下に重ねる。見本は流れの中に置いている */
.x-date-range-picker__d {
  margin-top: 6px;
  padding: 4px 8px;
  border: 1px solid #7f8a99;
  border-radius: 8px;
  background: #fff;
  box-shadow: 0 6px 16px rgba(27, 32, 40, .12);
}
.x-date-range-picker__pre {
  display: flex;
  gap: 4px;
  padding-bottom: 4px;
  border-bottom: 1px solid #d5dae1;
}
.x-date-range-picker__p {
  height: 22px;
  padding: 0 10px;
  border: 1px solid #7f8a99;
  border-radius: 999px;
  background: #fff;
  color: #1b2028;
  font: inherit;
  font-size: 11px;
  cursor: pointer;
}
.x-date-range-picker__nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.x-date-range-picker__h {
  margin: 0;
  font-size: 13px;
  font-weight: 700;
}
.x-date-range-picker__m {
  width: 28px;
  height: 24px;
  border: 0;
  border-radius: 6px;
  background: none;
  color: #1b2028;
  font-size: 12px;
  cursor: pointer;
}
.x-date-range-picker__g {
  width: 100%;
  border-collapse: collapse;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  text-align: center;
}
.x-date-range-picker__g th {
  height: 16px;
  color: #5b6472;
  font-size: 10.5px;
  font-weight: 700;
}
.x-date-range-picker__g td {
  height: 26px;
  padding: 0;
  cursor: pointer;
}
.x-date-range-picker__g td:empty {
  cursor: default;
}
/* 期間の中は薄い帯でつなぎ、両端は濃い塗り+太字。色の差だけでなく形でも端が分かる */
.x-date-range-picker__g td.x-date-range-picker__in {
  background: #dbe4ff;
}
.x-date-range-picker__g td.x-date-range-picker__s,
.x-date-range-picker__g td.x-date-range-picker__e {
  background: #1d4ed8;
  color: #fff;
  font-weight: 700;
  /* 強制カラーでは背景が消えるので、線として残る透明な枠を持たせる */
  outline: 2px solid transparent;
  outline-offset: -2px;
}
.x-date-range-picker__g td.x-date-range-picker__s {
  border-radius: 999px 0 0 999px;
}
.x-date-range-picker__g td.x-date-range-picker__e {
  border-radius: 0 999px 999px 0;
}
.x-date-range-picker__g td[aria-current="date"] {
  border: 1.5px solid #1b2028;
  border-radius: 6px;
  font-weight: 700;
}
.x-date-range-picker__i:focus-visible,
.x-date-range-picker__b:focus-visible,
.x-date-range-picker__p:focus-visible,
.x-date-range-picker__m:focus-visible,
.x-date-range-picker__g td:focus-visible {
  outline: 2px solid #1d4ed8;
  outline-offset: 1px;
}
.x-date-range-picker__vh {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  overflow: hidden;
  white-space: nowrap;
  clip-path: inset(50%);
}
```

## 出力の約束
- HTML と CSS を分けて出す。JavaScript が要るときは素の JavaScript で書き、UIフレームワークは使わない。
- 参考実装がアイコンに Font Awesome のクラスを使っている場合は、プロジェクトで使っているアイコンかインライン SVG に置き換えてよい。
- 上のアクセシビリティ要件を満たす。満たせない箇所があるときは、理由を添えて先に知らせる。
- 色・角丸・余白は既存のデザイントークンがあればそれを使う。無ければ CSS 変数として切り出す。

使いどころ

  • 売上の集計期間・ログの検索期間のように、始まりと終わりの2つの日付を組で決めてもらうとき
  • 宿泊や貸し出しの期間のように、何日間になるかをカレンダーの上で見せたいとき
  • 「今月」「先月」「過去7日」のように、よく使う期間をひと押しで選ばせたいとき

注意点

  • 日付を1つだけ選ぶなら日付ピッカーで足りる。期間選択は、2つの日付の前後関係と間の日数に意味があるときに使う
  • 終了日が開始日より前にならないようにする。カレンダーで開始日より前を押したら開始日を差し替える、欄に直接入れたらエラーを出す、のように決めておく
  • 開始日と終了日のそれぞれに直接入力の道を残す。カレンダーだけにすると、日付が決まっている人ほど遅くなる
  • 期間の端を含むかを書く。「9月7日〜9月15日」が15日を含むのかを画面で示し、集計側とそろえる
  • 月をまたぐ期間が多いなら、カレンダーを2か月並べる。1か月表示だと月送りしながら終了日を探すことになる

アクセシビリティの要点

  • 2つの欄を fieldset でくくり、legend を「集計期間」にする。各欄の label は「開始日」「終了日」
  • カレンダーは role="grid" の表にし、矢印キーで1日・1週、PageUp / PageDown で1か月動かす(WAI-ARIA APG の Date Picker Dialog にならう)
  • 開始日と終了日は aria-selected="true" に加え、名前に「開始日」「終了日」を含める。塗りの違いは読み上げに乗らない
  • 期間の中の日は薄い塗り、両端は濃い塗りと太字、のように色の差だけにしない(達成基準 1.4.1 色の用途)
  • カレンダーを閉じたら、開いたボタンへフォーカスを戻す

作るときに使えるツール

見分け方

関連するパーツ