跳转到内容

CalendarPicker 日历选择器

以天、周、月、季度或年为周期浏览并选择一个或多个日期,也可以在日期格中展示日程内容。

用法

选择日期

2026年9月
周一周二周三周四周五周六周日
31
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
1
2
3
4
5
6
7
8
9
10
11

组件结构

加粗的是必需部件。

data-scope="calendar-picker"root · header · prev-year-trigger · prev-trigger · next-trigger · next-year-trigger · heading · heading-year-trigger · heading-month-trigger · grid · grid-head · week-day · grid-body · week-row · week-number · cell · cell-trigger

示例

多选

selection-mode=multiple:点击一次加入,再点击一次移除,集合按日期升序

2026年9月
周一周二周三周四周五周六周日
31
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
1
2
3
4
5
6
7
8
9
10
11
已选 3 天:2026-09-08、2026-09-15、2026-09-22

不可选的日期

isDateUnavailable 与 min / max 都只阻止落值不阻止聚焦:方向键照常可以经过

2026年9月
31
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
1
2
3
4
5
6
7
8
9
10
11
可选窗口 2026-09-15 ~ 2026-09-29,周末除外 · 选中:(未选)

格子内放置内容

cell-trigger 的内容全部由作者编写,日号之外还可放置自己的标记

2026年9月
周一周二周三周四周五周六周日
31
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
1
2
3
4
5
6
7
8
9
10
11
选中:(未选)

设计指引

何时使用

  • 需要先看到整段时间的分布再选择日期:日程、排班、可预约情况。
  • 需要在格子中显示当天的事件。
  • 需要一次选择多个不连续的日期。

何时不用

特性

  • 标准结构由标题栏、前后翻页按钮、星期表头和日期网格组成;网格数据通过插槽作用域交给作者渲染。
  • granularity 决定周期格的生成方式,selectionMode 独立决定单选或多选;两个维度互不绑定。
  • 五种粒度统一产出 CalendarPeriod:稳定键、周期首尾、标签与相邻容器标记都来自同一份数据。
  • week 是一级粒度,使用一行一个整周的网格;不通过日格高亮模拟整周选择。
  • isDateUnavailablemin / max 只阻止取值,不阻止聚焦;粗粒度周期越过任一边界时整格不可选。
  • 支持固定六行与显式多面板;翻页时整个视窗一起移动。
  • 日期、月份与年份格按下时轻微缩放,松开后复原;减弱动效下自动收敛。
  • 年份网格采用三列紧凑滚动面,可由作者按业务上下界铺入连续年份,复用日历格的选中与键盘语义。
  • calendarPeriodValue 将选中的周期转换为 { granularity, start, end, keys },可直接用于查询参数。
  • 切换粒度会清空旧选择并保留浏览锚点,避免不同周期键之间发生隐式转换。
  • 周首日、月份名与星期名跟随 localeen-US 周日起、zh-CN 周一起。未提供 locale 时跟随宿主浏览器语言,读取失败时使用 en-US;需要固定排法时显式传入 locale

组合

最佳实践

  • 今天使用 1px 品牌环 + 品牌字,选中使用实心强调面,两种状态必须能同时辨认。
  • 多选时使用 aria-multiselectable 告知读屏用户可以多选,不依赖视觉提示。
  • 格子中的内容超出时收起,避免某一行明显高于其他行。

反模式

  • 不可选的日期无法聚焦,键盘用户无从知道该位置的内容。
  • 将它用作日期输入框。
  • 用多选模拟区间:中间的日期不会自动补齐,也没有拖选与预览。

API 参考

产物

自定义元素<xh-calendar-picker>
Vue 组件XhCalendarPickerCell XhCalendarPickerCellTrigger XhCalendarPickerGrid XhCalendarPickerGridBody XhCalendarPickerGridHead XhCalendarPickerHeader XhCalendarPickerHeading XhCalendarPickerHeadingMonthTrigger XhCalendarPickerHeadingYearTrigger XhCalendarPickerNextTrigger XhCalendarPickerNextYearTrigger XhCalendarPickerPrevTrigger XhCalendarPickerPrevYearTrigger XhCalendarPickerRoot XhCalendarPickerWeekDay XhCalendarPickerWeekNumber XhCalendarPickerWeekRow
组合式函数useCalendarPicker
状态机calendarPickerMachine
皮肤@xihan-ui/styles/calendar-picker.css

事件

自定义元素将载荷放在 detail;Vue 使用同名 emit。

事件载荷说明
value-changeCalendarPickerValueChangeDetails选中集合变化;detail 为 { value: string[] }
focused-value-changeCalendarFocusChangeDetails聚焦日变化;detail 为 { focusedValue: string }
active-view-changeCalendarViewChangeDetails切换到另一层级;detail 为 { activeView: 'day'|'week'|'month'|'quarter'|'year' }

插槽

仅列出带载荷的插槽。

Vue 组件插槽载荷说明
XhCalendarPickerRootdefaultCalendarPickerRootSlotProps

状态

以下名称仅用于内部状态机。

状态idle

事件PRESS.START · PRESS.END

判据canPress

connect API

getXxxProps() 返回对应部件的宿主属性。

成员类型说明
selectionModeCalendarPickerSelectionMode

无障碍

键盘

规格出处:W3C APG

按键生效条件行为
Tab / Shift+Tabfocus outside the grid整张网格只占一个 Tab 位:焦点进入聚焦日那一格
ArrowLeftfocus in grid焦点前移一天;越过月首即翻到上一月并落在那一天。粗粒度视图里走一格(一个月 / 一季 / 一年)
ArrowRightfocus in grid焦点后移一天;越过月末即翻到下一月并落在那一天。粗粒度视图里走一格
ArrowUpfocus in grid焦点上移一周(减七天),跨月照样翻页。粗粒度视图里上移一行
ArrowDownfocus in grid焦点下移一周(加七天),跨月照样翻页。粗粒度视图里下移一行
Homefocus in grid焦点移到本周第一天;周首日随 locale 变。粗粒度视图里移到本行头一格
Endfocus in grid焦点移到本周最后一天。粗粒度视图里移到本行末一格
PageUpfocus in grid退一个月,日号不变(月末日被目标月夹住:3 月 31 日退成 2 月 29 日)。粗粒度视图里退一整页
PageDownfocus in grid进一个月,日号不变。粗粒度视图里进一整页
Shift+PageUpfocus in grid退一年;粗粒度视图里退十页
Shift+PageDownfocus in grid进一年;粗粒度视图里进十页
Enter / Spacefocus in grid, 聚焦周期可用且非只读选中聚焦周期:单选替换、多选切换。还没钻到 granularity 那一档时这一下是往下钻一层
Enter / Spaceheld in prev-year-trigger / prev-trigger / next-trigger / next-year-trigger / heading-year-trigger / heading-month-trigger / cell-trigger, 该部件可按按住期间该部件投影 data-pressed,与指针 :active 同一副按压面;抬起或失焦撤下,按住途中整张转入禁用也撤下。整张禁用时谁都不进;只读时日期格不进(翻页与钻层照常);到界的翻页钮与到顶的标题是原生 disabled,不可选的格子是 aria-disabled,都不进

ARIA

以下属性由 connect 生成。

部件属性
gridaria-disabled'true' | 'false'
gridaria-labelledbyframe.headingId(panel.index)
gridaria-multiselectable'true' | 'false'
gridaria-readonly'true' | 'false'
gridrole'grid'
grid-headrole'rowgroup'
week-dayaria-labelmeta?.long
week-dayrole'columnheader'
grid-bodyrole'rowgroup'
week-rowrole'row'
week-numberaria-hidden'true'
week-numberrole'rowheader'
cellaria-selected'true' | 'false'
cellrole'gridcell'
cell-triggeraria-disabled'true' | 'false'
cell-triggeraria-labelframe.dateLabel(state.date, state.period)
cell-triggerrole'button'

样式参考

皮肤

@xihan-ui/styles/calendar-picker.css 使用 [data-scope="calendar-picker"][data-part="root"] 部件选择器,位于 xihan.components 层。覆盖样式使用 xihan.overrides

forced-colors: active 下另有一套规则:颜色交给系统,边框与状态标记改用系统色关键字。

数据属性

connect 生成;条件不成立时不输出无值属性。

部件属性
rootdata-disabled''(条件成立时才出现)
rootdata-invalid''(条件成立时才出现)
rootdata-readonly''(条件成立时才出现)
prev-year-triggerdata-disabled''(条件成立时才出现)
prev-year-triggerdata-pressed''(条件成立时才出现)
prev-year-triggerdata-xh-action-control''
prev-year-triggerdata-xh-action-display'always'
prev-year-triggerdata-xh-action-profile'icon'
prev-year-triggerdata-xh-action-size'sm'
prev-year-triggerdata-xh-action-variant'ghost'
prev-triggerdata-disabled''(条件成立时才出现)
prev-triggerdata-pressed''(条件成立时才出现)
prev-triggerdata-xh-action-control''
prev-triggerdata-xh-action-display'always'
prev-triggerdata-xh-action-profile'icon'
prev-triggerdata-xh-action-size'sm'
prev-triggerdata-xh-action-variant'ghost'
next-triggerdata-disabled''(条件成立时才出现)
next-triggerdata-pressed''(条件成立时才出现)
next-triggerdata-xh-action-control''
next-triggerdata-xh-action-display'always'
next-triggerdata-xh-action-profile'icon'
next-triggerdata-xh-action-size'sm'
next-triggerdata-xh-action-variant'ghost'
next-year-triggerdata-disabled''(条件成立时才出现)
next-year-triggerdata-pressed''(条件成立时才出现)
next-year-triggerdata-xh-action-control''
next-year-triggerdata-xh-action-display'always'
next-year-triggerdata-xh-action-profile'icon'
next-year-triggerdata-xh-action-size'sm'
next-year-triggerdata-xh-action-variant'ghost'
headingdata-indexframe.panelOf(panel).index
headingdata-viewview
heading-year-triggerdata-disabled''(条件成立时才出现)
heading-year-triggerdata-indexframe.panelOf(panel).index
heading-year-triggerdata-pressed''(条件成立时才出现)
heading-year-triggerdata-viewview
heading-year-triggerdata-xh-action-control''
heading-year-triggerdata-xh-action-display'always'
heading-year-triggerdata-xh-action-profile'text'
heading-year-triggerdata-xh-action-size'sm'
heading-year-triggerdata-xh-action-variant'ghost'
heading-month-triggerdata-disabled''(条件成立时才出现)
heading-month-triggerdata-indexframe.panelOf(panel).index
heading-month-triggerdata-pressed''(条件成立时才出现)
heading-month-triggerdata-viewview
heading-month-triggerdata-xh-action-control''
heading-month-triggerdata-xh-action-display'always'
heading-month-triggerdata-xh-action-profile'text'
heading-month-triggerdata-xh-action-size'sm'
heading-month-triggerdata-xh-action-variant'ghost'
griddata-disabled''(条件成立时才出现)
griddata-indexframe.panelOf(panel).index
griddata-readonly''(条件成立时才出现)
griddata-viewview
celldata-disabled''(条件成立时才出现)
celldata-focus''(条件成立时才出现)
celldata-outside-month''(条件成立时才出现)
celldata-selected''(条件成立时才出现)
celldata-today''(条件成立时才出现)
cell-triggerdata-disabled''(条件成立时才出现)
cell-triggerdata-focus''(条件成立时才出现)
cell-triggerdata-outside-month''(条件成立时才出现)
cell-triggerdata-pressed''(条件成立时才出现)
cell-triggerdata-selected''(条件成立时才出现)
cell-triggerdata-today''(条件成立时才出现)
cell-triggerdata-xh-action-control''
cell-triggerdata-xh-action-display'always'
cell-triggerdata-xh-action-profile'text'
cell-triggerdata-xh-action-size'sm'
cell-triggerdata-xh-action-variant'ghost'

CSS 变量

本组件公开覆盖槽由独立皮肤的实际消费位生成;默认来源、作用部件和状态均与 CSS 同源。

变量部件CSS 属性状态默认来源说明
--xh-calendar-picker-cell-bg-hovercell-triggerbackground-colordisabled
hover
loading
not([data-disabled])
not([data-loading])
--xh-_action-variant-bg-hovercalendar-picker 的 cell-trigger 部件 background-color 覆盖槽。
--xh-calendar-picker-cell-bg-pressedcell-triggerbackground-colordisabled
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
--xh-_action-variant-bg-pressedcalendar-picker 的 cell-trigger 部件 background-color 覆盖槽。
--xh-calendar-picker-cell-bg-selectedcell-triggerbackground-colordisabled
focus-visible
hover
loading
not([data-disabled])
not([data-loading])
selected
--xh-bg-brandcalendar-picker 的 cell-trigger 部件 background-color 覆盖槽。
--xh-calendar-picker-cell-bg-selected-activecell-triggerbackground-colordisabled
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
selected
--xh-bg-brand-activecalendar-picker 的 cell-trigger 部件 background-color 覆盖槽。
--xh-calendar-picker-cell-bg-selected-disabledcell-triggerbackground-colordisabled
selected
--xh-bg-subtlecalendar-picker 的 cell-trigger 部件 background-color 覆盖槽。
--xh-calendar-picker-cell-fgcell-triggercolor@media print
default
disabled
focus-visible
hover
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
selected
--xh-fg-defaultcalendar-picker 的 cell-trigger 部件 color 覆盖槽。
--xh-calendar-picker-cell-fg-outsidecell-triggercolordisabled
focus-visible
hover
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
outside-month
pressed
--xh-fg-subtlecalendar-picker 的 cell-trigger 部件 color 覆盖槽。
--xh-calendar-picker-cell-fg-selectedcell-triggercolordisabled
focus-visible
hover
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
selected
--xh-fg-on-brandcalendar-picker 的 cell-trigger 部件 color 覆盖槽。
--xh-calendar-picker-cell-font-sizecell-triggerfont-sizedefault--xh-text-body-sizecalendar-picker 的 cell-trigger 部件 font-size 覆盖槽。
--xh-calendar-picker-cell-font-weightcell-triggerfont-weightdefault--xh-font-weight-mediumcalendar-picker 的 cell-trigger 部件 font-weight 覆盖槽。
--xh-calendar-picker-cell-gapcell
cell-trigger
inset
padding
default--xh-space-0_5calendar-picker 的 cell、cell-trigger 部件 inset、padding 覆盖槽。
--xh-calendar-picker-cell-radiuscell-triggerborder-radiusdefault--xh-shape-insetcalendar-picker 的 cell-trigger 部件 border-radius 覆盖槽。
--xh-calendar-picker-cell-sizecell-triggermin-inline-sizedefault--xh-control-h-smcalendar-picker 的 cell-trigger 部件 min-inline-size 覆盖槽。
--xh-calendar-picker-gaprootgapdefault--xh-space-2calendar-picker 的 root 部件 gap 覆盖槽。
--xh-calendar-picker-grid-gapgridgapdefault--xh-space-1calendar-picker 的 grid 部件 gap 覆盖槽。
--xh-calendar-picker-header-gapheadergapdefault--xh-space-2calendar-picker 的 header 部件 gap 覆盖槽。
--xh-calendar-picker-heading-fgheading
heading-month-trigger
heading-year-trigger
colordefault
disabled
focus-visible
not([hidden])
--xh-fg-defaultcalendar-picker 的 heading、heading-month-trigger、heading-year-trigger 部件 color 覆盖槽。
--xh-calendar-picker-heading-font-sizeheading
heading-month-trigger
heading-year-trigger
font-sizedefault
not([hidden])
--xh-text-label-sizecalendar-picker 的 heading、heading-month-trigger、heading-year-trigger 部件 font-size 覆盖槽。
--xh-calendar-picker-heading-font-weightheading
heading-month-trigger
heading-year-trigger
font-weightdefault
not([hidden])
--xh-font-weight-semiboldcalendar-picker 的 heading、heading-month-trigger、heading-year-trigger 部件 font-weight 覆盖槽。
--xh-calendar-picker-heading-trigger-bg-pressedheading-month-trigger
heading-year-trigger
background-colordisabled
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
not([hidden])
pressed
--xh-_action-variant-bg-pressedcalendar-picker 的 heading-month-trigger、heading-year-trigger 部件 background-color 覆盖槽。
--xh-calendar-picker-heading-trigger-fg-hoverheading-month-trigger
heading-year-trigger
colordisabled
hover
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
not([hidden])
pressed
--xh-fg-brandcalendar-picker 的 heading-month-trigger、heading-year-trigger 部件 color 覆盖槽。
--xh-calendar-picker-heading-trigger-pxheading-month-trigger
heading-year-trigger
padding-inlinenot([hidden])--xh-space-1calendar-picker 的 heading-month-trigger、heading-year-trigger 部件 padding-inline 覆盖槽。
--xh-calendar-picker-heading-trigger-radiusheading-month-trigger
heading-year-trigger
border-radiusnot([hidden])--xh-shape-controlcalendar-picker 的 heading-month-trigger、heading-year-trigger 部件 border-radius 覆盖槽。
--xh-calendar-picker-icon-sizeroot--xh-icon-sizedefault--xh-glyph-size-smcalendar-picker 的 root 部件 --xh-icon-size 覆盖槽。
--xh-calendar-picker-nav-bgnext-trigger
next-year-trigger
prev-trigger
prev-year-trigger
background-colordefault
focus-visible
--xh-_action-variant-bg-focus-visible
--xh-_action-variant-bg-rest
calendar-picker 的 next-trigger、next-year-trigger、prev-trigger、prev-year-trigger 部件 background-color 覆盖槽。
--xh-calendar-picker-nav-bg-hovernext-trigger
next-year-trigger
prev-trigger
prev-year-trigger
background-colordisabled
hover
loading
not([data-disabled])
not([data-loading])
--xh-_action-variant-bg-hovercalendar-picker 的 next-trigger、next-year-trigger、prev-trigger、prev-year-trigger 部件 background-color 覆盖槽。
--xh-calendar-picker-nav-bg-pressednext-trigger
next-year-trigger
prev-trigger
prev-year-trigger
background-colordisabled
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
--xh-_action-variant-bg-pressedcalendar-picker 的 next-trigger、next-year-trigger、prev-trigger、prev-year-trigger 部件 background-color 覆盖槽。
--xh-calendar-picker-nav-fgnext-trigger
next-year-trigger
prev-trigger
prev-year-trigger
colordefault
focus-visible
--xh-fg-mutedcalendar-picker 的 next-trigger、next-year-trigger、prev-trigger、prev-year-trigger 部件 color 覆盖槽。
--xh-calendar-picker-nav-fg-hovernext-trigger
next-year-trigger
prev-trigger
prev-year-trigger
colordisabled
hover
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
--xh-fg-defaultcalendar-picker 的 next-trigger、next-year-trigger、prev-trigger、prev-year-trigger 部件 color 覆盖槽。
--xh-calendar-picker-nav-radiusnext-trigger
next-year-trigger
prev-trigger
prev-year-trigger
border-radiusdefault--xh-shape-controlcalendar-picker 的 next-trigger、next-year-trigger、prev-trigger、prev-year-trigger 部件 border-radius 覆盖槽。
--xh-calendar-picker-nav-sizenext-trigger
next-year-trigger
prev-trigger
prev-year-trigger
block-size
inline-size
min-inline-size
default
xh-action-profile=icon
--xh-_action-profile-visual-sizecalendar-picker 的 next-trigger、next-year-trigger、prev-trigger、prev-year-trigger 部件 block-size、inline-size、min-inline-size 覆盖槽。
--xh-calendar-picker-period-gapgridgapview=month
view=quarter
view=week
view=year
--xh-space-1calendar-picker 的 grid 部件 gap 覆盖槽。
--xh-calendar-picker-period-pycell-trigger
grid
padding-blockis([data-view='week'], [data-view='month'], [data-view='quarter'], [data-view='year'])
view=month
view=quarter
view=week
view=year
--xh-space-2calendar-picker 的 cell-trigger、grid 部件 padding-block 覆盖槽。
--xh-calendar-picker-period-radiuscell-trigger
grid
border-radiusis([data-view='week'], [data-view='month'], [data-view='quarter'], [data-view='year'])
view=month
view=quarter
view=week
view=year
--xh-shape-controlcalendar-picker 的 cell-trigger、grid 部件 border-radius 覆盖槽。
--xh-calendar-picker-row-gapgrid-body
grid-head
gapdefault--xh-space-0calendar-picker 的 grid-body、grid-head 部件 gap 覆盖槽。
--xh-calendar-picker-today-bgcell-triggerbackground-colordisabled
focus-visible
today
transparentcalendar-picker 的 cell-trigger 部件 background-color 覆盖槽。
--xh-calendar-picker-today-bordercell-triggerborder
border-color
disabled
focus-visible
hover
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
today
--xh-fg-brandcalendar-picker 的 cell-trigger 部件 border、border-color 覆盖槽。
--xh-calendar-picker-today-fgcell-triggercolordisabled
focus-visible
hover
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
today
--xh-fg-brandcalendar-picker 的 cell-trigger 部件 color 覆盖槽。
--xh-calendar-picker-week-cell-pxcell-trigger
grid
padding-inlineview=week--xh-space-3calendar-picker 的 cell-trigger、grid 部件 padding-inline 覆盖槽。
--xh-calendar-picker-week-day-fgweek-daycolordefault--xh-fg-subtlecalendar-picker 的 week-day 部件 color 覆盖槽。
--xh-calendar-picker-week-day-font-sizeweek-dayfont-sizedefault--xh-text-caption-sizecalendar-picker 的 week-day 部件 font-size 覆盖槽。
--xh-calendar-picker-week-day-font-weightweek-dayfont-weightdefault--xh-font-weight-mediumcalendar-picker 的 week-day 部件 font-weight 覆盖槽。
--xh-calendar-picker-week-day-hweek-dayblock-sizedefault--xh-control-h-smcalendar-picker 的 week-day 部件 block-size 覆盖槽。
--xh-calendar-picker-week-number-fgweek-numbercolordefault--xh-fg-subtlecalendar-picker 的 week-number 部件 color 覆盖槽。
--xh-calendar-picker-week-number-font-sizeweek-numberfont-sizedefault--xh-text-caption-sizecalendar-picker 的 week-number 部件 font-size 覆盖槽。
--xh-calendar-picker-week-number-wweek-number
week-row
grid-template-columnshas(> [data-part='week-number'])
not([hidden])
--xh-control-h-mdcalendar-picker 的 week-number、week-row 部件 grid-template-columns 覆盖槽。
--xh-calendar-picker-year-grid-max-hgridmax-block-sizeview=year--xh-viewport-h-smcalendar-picker 的 grid 部件 max-block-size 覆盖槽。
--xh-calendar-picker-year-grid-pegridpadding-inline-endview=year--xh-space-1calendar-picker 的 grid 部件 padding-inline-end 覆盖槽。

动效

本组件皮肤不含过渡与关键帧,也没有脚本驱动的动效:状态一变,外观立即到位。

响应式

皮肤另按输入能力分档:pointer: coarse:同一份皮肤在触屏与带指针的设备上不一样,与视口宽度无关。

RTL

皮肤用逻辑属性排布(inline-start 一族),dir="rtl" 下自动镜像;另有按 dir 分支的规则。

Released under The MIT License