用途
Date Picker 是一种表单组件,由显示所选日期的文本输入框和日历浮层组成,用户可以通过日历可视化地浏览并选择日期。日历能提供纯文本输入无法给出的上下文,例如周边日期、星期几以及时间相对位置。
与针对已知日期直接键盘输入的 Date Input 不同,Date Picker 适用于用户需要看到日历的场景:预约排期、选择未来配送日期,或从受限的可用日期集合中选择。
适用场景
- 预约排期:用户需要在上下文中查看可用时段。
- 配送与发货日期:展示可用配送日与不可用日期。
- 活动创建:用户选择相对于今天或其他活动的日期。
- 酒店与旅行预订:结合价格或可用性的上下文日期选择。
- 报表或筛选日期:为数据筛选选择特定日期。
应避免的场景
- 已知的历史日期:出生日期或过往记录应使用 Date Input,日历导航较为繁琐。
- 日期范围:应改用 Date Range 组件。
- 仅移动端场景:原生
<input type="date">会打开平台原生选择器,自定义日历可能多余。 - 非常久远的日期:在日历中导航 80 年以上不切实际,应使用带年份输入的文本日期输入。
实现流程
- 在复制示例前,先确认该模式与问题和约束匹配。
- 从
references/pattern.md中的结构和示例开始,选择最小可行的变体。 - 在叠加视觉打磨之前,先应用可访问性、性能和交互防护规则。
- 使用测试指南验证键盘、屏幕阅读器、响应式和失败场景下的行为。
可访问性防护规则
键盘交互模式:
| 按键 | 操作 |
|---|---|
| Enter / Space | 在触发按钮上打开日历;选择聚焦日期 |
| Escape | 关闭日历并将焦点返回触发按钮 |
| Arrow Right | 焦点移到下一天 |
| Arrow Left | 焦点移到上一天 |
| Arrow Down | 焦点移到下周同一天 |
| Arrow Up | 焦点移到上周同一天 |
| Home | 焦点移到当前周的第一天 |
性能防护规则
- 日历打开时间:从按钮点击到日历可见 < 100ms
- 月份导航:渲染新月份网格 < 50ms
- 日期选择:从点击到输入更新并关闭弹层 < 16ms
- 内存占用:每个日期选择器实例 < 20KB
- DOM 大小:单月日历网格 < 50 个节点
常见错误
在输入框获得焦点时打开日历
问题:当文本输入框获得焦点时自动打开日历,会干扰通过表单导航的键盘用户,以及偏好直接输入日期的用户。
修复方式:仅在日历图标按钮被激活时打开日历。
// Bad
dateInput.addEventListener('focus', openCalendar);
// Good
calendarButton.addEventListener('click', toggleCalendar);
calendarButton.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
toggleCalendar();
}
});
日历网格中没有键盘导航
问题:需要鼠标交互的日历对键盘和屏幕阅读器用户不可访问。
修复方式:实现完整的 ARIA 网格键盘导航模式。
calendarGrid.addEventListener('keydown', (e) => {
const focused = document.activeElement;
switch (e.key) {
case 'ArrowRight': focusDay(getNextDay(focused)); break;
case 'ArrowLeft': focusDay(getPrevDay(focused)); break;
case 'ArrowDown': focusDay(getNextWeek(focused)); break;
case 'ArrowUp': focusDay(getPrevWeek(focused)); break;
case 'Home': focusDay(getFirstDayOfWeek(focused)); break;
case 'End': focusDay(getLastDayOfWeek(focused)); break;
case 'PageUp': navigateToPrevMonth(); break;
case 'PageDown': navigateToNextMonth(); break;
case 'Escape': closeCalendar(); break;
}
});
关闭后未返回焦点
问题:日历关闭后(选择日期或按 Escape),焦点消失或跳到页面顶部。
修复方式:始终将焦点返回触发元素。
function closeCalendar() {
calendarPopup.hidden = true;
calendarButton.setAttribute('aria-expanded', 'false');
calendarButton.focus(); // Always return focus
}
相关模式
- https://uxpatterns.dev/patterns/forms/date-input
- https://uxpatterns.dev/patterns/forms/date-range
- https://uxpatterns.dev/patterns/forms/text-field
完整实现细节、示例和测试说明,请参见 references/pattern.md。
模式页面:https://uxpatterns.dev/patterns/forms/date-picker