Calendar
Overview
A monthly date grid that supports single or multiple selection, disabled and unavailable dates, min/max bounds, custom cell rendering, and month/year Select controls in the compact header. Use it for booking, scheduling, or any date-picking surface that needs keyboard-navigable grids instead of a plain text input. Pair it with SDatePicker when you need a popup trigger, or SRangeCalendar-style composites for range selection.
Usage
Features
- 📅 Month grid — one or more month grids (
numberOfMonths) with locale-aware weekday headers, today, and outside-view markers - 🎚 Controlled / uncontrolled —
v-model/defaultValuefor single dates or date arrays (multiple), backed byuseControllableState - ✅ Multiple selection — click or keyboard-toggle dates;
preventDeselectkeeps the selection sticky - ⌨️ Full keyboard support —
ArrowLeft/ArrowRight/ArrowUp/ArrowDownnavigation with RTL reversal, cross-month paging, and disabled-date skipping;Enter/Spaceselects - ⛓ Bounds & matching —
minValue/maxValueclamp the page and disable out-of-range dates;isDateDisabled/isDateUnavailablemark arbitrary dates - 🧩 Composable slots —
day,head-cell,heading,prev, andnextslots with rich slot props;*Propspassthrough for every sub-part - 🔤 Localized UI —
locale,weekStartsOn, andweekdayFormat; month/year Select controls carry localizedaria-labels - 🗓 Month/year controls — compact header renders Select controls for fast month/year jumps, disabled per
minValue/maxValue - ↕️ Fixed weeks —
fixedWeeksrenders a consistent 6-row grid;initialFocusmoves focus into the grid on mount - 🚫 Disabled / readonly —
disabledblocks all interaction;readonlykeeps browsing but blocks selection
Component family
SCalendar— the styled wrapper that forwards props to the Aria compact and injectscalendarVariantsclasses (12 slots, shared button icon recipes for prev/next)CalendarCompact(Aria) — data-driven composition ofCalendarRoot+ header (prev/heading/next) + one grid per month (grid head/body rows ofCalendarCellTrigger); import from@vean/aria/calendarfor unstyled usageCalendarRoot(Aria) — state owner:useControllableStatefor value/placeholder,useCalendarfor grid/month pages and validation,useCalendarStatefor selected/invalid stateCalendarCellTrigger/CalendarCell/CalendarGrid*(Aria) — the editable day button (keyboard handling, focus management, data attributes) and its semantic grid cell wrappers
Demos
Basic
| M | T | W | T | F | S | S |
|---|---|---|---|---|---|---|
Disabled
| M | T | W | T | F | S | S |
|---|---|---|---|---|---|---|
Custom Styling
| M | T | W | T | F | S | S |
|---|---|---|---|---|---|---|
API
Aria Layer
14Calendar<M>
Props
| Prop | Type | Default | Description |
|---|---|---|---|
class | ClassValue | — | Additional class names applied to the root element. |
size | ThemeSize | — | Visual size of the component. |
ui | Partial<CalendarUi> | — | Per-slot class overrides for the component. |
headerProps | CalendarHeaderProps | — | Properties forwarded to the header element. |
headingProps | CalendarHeadingProps | — | Properties forwarded to the heading element. |
prevProps | CalendarPrevProps | — | Properties forwarded to the prev element. |
nextProps | CalendarNextProps | — | Properties forwarded to the next element. |
gridProps | CalendarGridProps | — | Properties forwarded to the grid element. |
gridHeadProps | CalendarGridHeadProps | — | Properties forwarded to the grid head element. |
gridBodyProps | CalendarGridBodyProps | — | Properties forwarded to the grid body element. |
gridRowProps | CalendarGridRowProps | — | Properties forwarded to the grid row element. |
headCellProps | CalendarHeadCellProps | — | Properties forwarded to the head cell element. |
cellProps | CalendarCellProps | — | Properties forwarded to the cell element. |
cellTriggerProps | CalendarCellTriggerProps | — | Properties forwarded to the cell trigger element. |
dir | Direction | — | Reading direction of the component. |
locale | string | — | Locale. |
modelValue | CalendarModelValue<M> | — | Current model value. |
defaultValue | CalendarModelValue<M> | — | Default value. |
multiple | M | — | Whether multiple values are supported. |
placeholder | DateValue | — | Placeholder. |
defaultPlaceholder | DateValue | — | Default placeholder. |
disabled | boolean | — | Whether the component is disabled. |
readonly | boolean | — | Whether the component is readonly. |
pagedNavigation | boolean | — | Whether paged navigation. |
preventDeselect | boolean | — | Whether prevent deselect. |
weekStartsOn | WeekStartsOn | — | Week starts on. |
weekdayFormat | WeekDayFormat | — | Weekday format. |
calendarLabel | string | — | Calendar label. |
fixedWeeks | boolean | — | Whether fixed weeks. |
maxValue | DateValue | — | Max value. |
minValue | DateValue | — | Min value. |
numberOfMonths | number | — | Number of months. |
initialFocus | boolean | — | Whether initial focus. |
isDateDisabled | DateMatcher | — | Whether the date is disabled. |
isDateUnavailable | DateMatcher | — | Whether the date is unavailable. |
nextPage | ((placeholder: DateValue) => DateValue) | — | Next page. |
prevPage | ((placeholder: DateValue) => DateValue) | — | Prev page. |
disableDaysOutsideCurrentView | boolean | — | Whether to disable days outside current view. |
asChild | boolean | — | Change the default rendered element for the one passed as a child, merging their props and behavior. |
as | AsTag | Component | 'div' | The element or component this component should render as. Can be overwrite by `asChild` |
Emits
| Emit Name | Parameters | Description |
|---|---|---|
update:modelValue | CalendarModelValue<M> | Emitted when the model value changes. |
update:placeholder | DateValue | Emitted when the placeholder value changes. |
Slots
| Slot Name | Parameters | Description |
|---|---|---|
default | { modelValue: CalendarModelValue<M>; } | Custom content for the default slot. |
prev | { disabled: boolean; } | Custom content for the prev slot. |
heading | CalendarHeadingSlotProps | Custom content for the heading slot. |
next | { disabled: boolean; } | Custom content for the next slot. |
head-cell | { date: Date; index: number; label: string; } | Custom content for the head cell slot. |
day | CalendarDaySlotProps | Custom content for the day slot. |
CalendarCell
Props
| Prop | Type | Default | Description |
|---|---|---|---|
date | Date | — | Date. |
asChild | boolean | — | Change the default rendered element for the one passed as a child, merging their props and behavior. |
as | AsTag | Component | 'div' | The element or component this component should render as. Can be overwrite by `asChild` |
CalendarCellTrigger
Props
| Prop | Type | Default | Description |
|---|---|---|---|
day | Date | — | Day. |
month | Date | — | Month. |
type | ButtonType | 'button' | The type of the button element. Can be one of 'button', 'submit', or 'reset'. |
disabled | boolean | — | Whether the component is disabled. |
asChild | boolean | — | Change the default rendered element for the one passed as a child, merging their props and behavior. |
as | AsTag | Component | 'div' | The element or component this component should render as. Can be overwrite by `asChild` |
Slots
| Slot Name | Parameters | Description |
|---|---|---|
default | CalendarCellTriggerSlotProps | Custom content for the default slot. |
CalendarCompact<M>
Props
| Prop | Type | Default | Description |
|---|---|---|---|
headerProps | CalendarHeaderProps | — | Properties forwarded to the header element. |
headingProps | CalendarHeadingProps | — | Properties forwarded to the heading element. |
prevProps | CalendarPrevProps | — | Properties forwarded to the prev element. |
nextProps | CalendarNextProps | — | Properties forwarded to the next element. |
gridProps | CalendarGridProps | — | Properties forwarded to the grid element. |
gridHeadProps | CalendarGridHeadProps | — | Properties forwarded to the grid head element. |
gridBodyProps | CalendarGridBodyProps | — | Properties forwarded to the grid body element. |
gridRowProps | CalendarGridRowProps | — | Properties forwarded to the grid row element. |
headCellProps | CalendarHeadCellProps | — | Properties forwarded to the head cell element. |
cellProps | CalendarCellProps | — | Properties forwarded to the cell element. |
cellTriggerProps | CalendarCellTriggerProps | — | Properties forwarded to the cell trigger element. |
dir | Direction | — | Reading direction of the component. |
locale | string | — | Locale. |
modelValue | CalendarModelValue<M> | — | Current model value. |
defaultValue | CalendarModelValue<M> | — | Default value. |
multiple | M | — | Whether multiple values are supported. |
placeholder | DateValue | — | Placeholder. |
defaultPlaceholder | DateValue | — | Default placeholder. |
disabled | boolean | — | Whether the component is disabled. |
readonly | boolean | — | Whether the component is readonly. |
pagedNavigation | boolean | — | Whether paged navigation. |
preventDeselect | boolean | — | Whether prevent deselect. |
weekStartsOn | WeekStartsOn | — | Week starts on. |
weekdayFormat | WeekDayFormat | — | Weekday format. |
calendarLabel | string | — | Calendar label. |
fixedWeeks | boolean | — | Whether fixed weeks. |
maxValue | DateValue | — | Max value. |
minValue | DateValue | — | Min value. |
numberOfMonths | number | — | Number of months. |
initialFocus | boolean | — | Whether initial focus. |
isDateDisabled | DateMatcher | — | Whether the date is disabled. |
isDateUnavailable | DateMatcher | — | Whether the date is unavailable. |
nextPage | ((placeholder: DateValue) => DateValue) | — | Next page. |
prevPage | ((placeholder: DateValue) => DateValue) | — | Prev page. |
disableDaysOutsideCurrentView | boolean | — | Whether to disable days outside current view. |
asChild | boolean | — | Change the default rendered element for the one passed as a child, merging their props and behavior. |
as | AsTag | Component | 'div' | The element or component this component should render as. Can be overwrite by `asChild` |
Emits
| Emit Name | Parameters | Description |
|---|---|---|
update:modelValue | CalendarModelValue<M> | Emitted when the model value changes. |
update:placeholder | DateValue | Emitted when the placeholder value changes. |
Slots
| Slot Name | Parameters | Description |
|---|---|---|
default | { modelValue: CalendarModelValue<M>; } | Custom content for the default slot. |
prev | { disabled: boolean; } | Custom content for the prev slot. |
heading | CalendarHeadingSlotProps | Custom content for the heading slot. |
next | { disabled: boolean; } | Custom content for the next slot. |
head-cell | { date: Date; index: number; label: string; } | Custom content for the head cell slot. |
day | CalendarDaySlotProps | Custom content for the day slot. |
CalendarGrid
Props
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | — | Change the default rendered element for the one passed as a child, merging their props and behavior. |
as | AsTag | Component | 'div' | The element or component this component should render as. Can be overwrite by `asChild` |
CalendarGridBody
Props
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | — | Change the default rendered element for the one passed as a child, merging their props and behavior. |
as | AsTag | Component | 'div' | The element or component this component should render as. Can be overwrite by `asChild` |
CalendarGridHead
Props
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | — | Change the default rendered element for the one passed as a child, merging their props and behavior. |
as | AsTag | Component | 'div' | The element or component this component should render as. Can be overwrite by `asChild` |
CalendarGridRow
Props
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | — | Change the default rendered element for the one passed as a child, merging their props and behavior. |
as | AsTag | Component | 'div' | The element or component this component should render as. Can be overwrite by `asChild` |
CalendarHeadCell
Props
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | — | Change the default rendered element for the one passed as a child, merging their props and behavior. |
as | AsTag | Component | 'div' | The element or component this component should render as. Can be overwrite by `asChild` |
CalendarHeader
Props
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | — | Change the default rendered element for the one passed as a child, merging their props and behavior. |
as | AsTag | Component | 'div' | The element or component this component should render as. Can be overwrite by `asChild` |
CalendarHeading
Props
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | — | Change the default rendered element for the one passed as a child, merging their props and behavior. |
as | AsTag | Component | 'div' | The element or component this component should render as. Can be overwrite by `asChild` |
CalendarNext
Props
| Prop | Type | Default | Description |
|---|---|---|---|
nextPage | ((placeholder: DateValue) => DateValue) | — | Next page. |
type | ButtonType | 'button' | The type of the button element. Can be one of 'button', 'submit', or 'reset'. |
disabled | boolean | — | Whether the component is disabled. |
asChild | boolean | — | Change the default rendered element for the one passed as a child, merging their props and behavior. |
as | AsTag | Component | 'div' | The element or component this component should render as. Can be overwrite by `asChild` |
CalendarPrev
Props
| Prop | Type | Default | Description |
|---|---|---|---|
prevPage | ((placeholder: DateValue) => DateValue) | — | Prev page. |
type | ButtonType | 'button' | The type of the button element. Can be one of 'button', 'submit', or 'reset'. |
disabled | boolean | — | Whether the component is disabled. |
asChild | boolean | — | Change the default rendered element for the one passed as a child, merging their props and behavior. |
as | AsTag | Component | 'div' | The element or component this component should render as. Can be overwrite by `asChild` |
CalendarRoot<M>
Props
| Prop | Type | Default | Description |
|---|---|---|---|
dir | Direction | — | Reading direction of the component. |
locale | string | — | Locale. |
modelValue | CalendarModelValue<M> | — | Current model value. |
defaultValue | CalendarModelValue<M> | — | Default value. |
multiple | M | — | Whether multiple values are supported. |
placeholder | DateValue | — | Placeholder. |
defaultPlaceholder | DateValue | — | Default placeholder. |
disabled | boolean | — | Whether the component is disabled. |
readonly | boolean | — | Whether the component is readonly. |
pagedNavigation | boolean | — | Whether paged navigation. |
preventDeselect | boolean | — | Whether prevent deselect. |
weekStartsOn | WeekStartsOn | — | Week starts on. |
weekdayFormat | WeekDayFormat | — | Weekday format. |
calendarLabel | string | — | Calendar label. |
fixedWeeks | boolean | — | Whether fixed weeks. |
maxValue | DateValue | — | Max value. |
minValue | DateValue | — | Min value. |
numberOfMonths | number | — | Number of months. |
initialFocus | boolean | — | Whether initial focus. |
isDateDisabled | DateMatcher | — | Whether the date is disabled. |
isDateUnavailable | DateMatcher | — | Whether the date is unavailable. |
nextPage | ((placeholder: DateValue) => DateValue) | — | Next page. |
prevPage | ((placeholder: DateValue) => DateValue) | — | Prev page. |
disableDaysOutsideCurrentView | boolean | — | Whether to disable days outside current view. |
asChild | boolean | — | Change the default rendered element for the one passed as a child, merging their props and behavior. |
as | AsTag | Component | 'div' | The element or component this component should render as. Can be overwrite by `asChild` |
Emits
| Emit Name | Parameters | Description |
|---|---|---|
update:modelValue | CalendarModelValue<M> | Emitted when the model value changes. |
update:placeholder | DateValue | Emitted when the placeholder value changes. |
Slots
| Slot Name | Parameters | Description |
|---|---|---|
default | CalendarRootSlotProps<M> | Custom content for the default slot. |
Notes
Architecture and benchmark differences
CalendarRoot owns the value via useControllableState (single date or array when multiple), keeps the placeholder for grid paging, and delegates grid creation to useCalendar (which rebuilds the grid on locale/week-start/placeholder changes and clamps pages with minValue/maxValue). useCalendarState derives selected/invalid state. CalendarCellTrigger is the only interactive piece: it reads the shared context, computes data-focused/data-selected/data-today/data-unavailable/data-outside-view, handles Arrow/Enter/Space keys with dir-aware direction (RTL flips ArrowLeft/ArrowRight), and pages to adjacent months when navigation crosses the grid boundary. The compact owns iteration over months/weeks/days and the header wiring (prev/heading/next with month/year Select controls). Most benchmark libraries ship a monolithic calendar panel; the Aria/styled split, per-part *Props passthrough, and slot-driven cell rendering are the differentiators.
| Capability | VeanUI | Ant Design | Element Plus | Mantine | Naive UI | shadcn |
|---|---|---|---|---|---|---|
| Aria/styled split | ✅ | — | — | — | — | — |
| Single / multiple selection | ✅ | ✅ | ✅ | ✅ | ✅ | — |
| Keyboard grid navigation | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| RTL direction reversal | ✅ | — | — | — | — | — |
numberOfMonths grid | ✅ | — | — | ✅ | — | — |
minValue/maxValue bounds | ✅ | ✅ | ✅ | ✅ | ✅ | — |
isDateDisabled / unavailable | ✅ | ✅ | ✅ | ✅ | ✅ | — |
fixedWeeks / weekStartsOn | ✅ | — | ✅ | ✅ | — | — |
| Month/year Select controls | ✅ | ✅ | ✅ | — | — | — |
Custom prevPage/nextPage | ✅ | — | — | — | — | — |
Per-part *Props passthrough | ✅ | — | — | — | — | — |
Localized aria-labels | ✅ | ✅ | ✅ | ✅ | — | — |
Cautions
- The value is a native
Dateat local midnight (theDateValuemodel:Dateor{ date, time? }). Usedate-fnsorIntlwhen formatting or converting. multiplechanges the model shape toDateValue[]; theMgeneric infers it from the prop.defaultValue/defaultPlaceholderare read on mount only — usev-modelfor external control.isDateDisabledandisDateUnavailablediffer: disabled dates are unselectable by policy, unavailable dates are additionally marked visually (line-throughin the default style).- Dates outside the current month are rendered by default; pass
disableDaysOutsideCurrentViewto disable them. - The prev/next
aria-labels default to locale messages; override per button throughprevProps/nextProps. - Navigation buttons auto-disable at
minValue/maxValue; customprevPage/nextPagefunctions take part in that computation.
FAQ
How do I select multiple dates?
Pass multiple (boolean shorthand works in templates) and bind v-model to an array of DateValues. Clicking toggles dates; preventDeselect stops removal.
How do I prevent dates before today?
Pass minValue={new Date(2026, 0, 1)} (or any DateValue). Out-of-range dates are disabled and the prev button auto-disables at the bound.
How do I show several months at once?
Set numberOfMonths={2} — the calendar renders two month grids side by side and pages both when crossing the boundary.
Why is PageUp/PageDown not handled?
Grid navigation currently covers Arrow keys, Enter/Space, and cross-month paging. PageUp/PageDown/Home/End (present in Ant Design/Element Plus panels) are a scheduled enhancement.
How do I build a date range with this component?
Use SRangeCalendar for a dedicated range grid, or pair two SCalendars with minValue/maxValue bound to the other side’s selection.