期間選択Date range picker
「開始日〜終了日」の2つの欄と、間の日が帯で塗られたカレンダーが組になった入力。「今月」「先月」のボタンで一度に選べることも。集計や宿泊の期間に使う。
表示例
2026年9月
| 日 | 月 | 火 | 水 | 木 | 金 | 土 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | ||
| 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 13 | 14 | 15 | 16 | 17 | 18 | 19 |
| 20 | 21 | 22 | 23 | 24 | 25 | 26 |
| 27 | 28 | 29 | 30 |
アイコン: 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>
/* 貼った先のリセットに頼らないよう、この部品の中だけ 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%);
}
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 色の用途)
- カレンダーを閉じたら、開いたボタンへフォーカスを戻す