Back to catalog
Component detail entry
Forms
@vean/ui
SCalendarRange

CalendarRange

CalendarRange belongs to the Forms category. Start with demos and usage, then use the API tables to finish integration and styling decisions.

Use the quick jump panel to reach overview, usage, demos, and API first, then continue through the full page.

Quick jump

Related components

If you are composing interactions in the same family, these components usually pair well together.

CalendarRange

Overview

CalendarRange displays one or more month grids and lets users pick a start and end date directly from the calendar surface. Selecting the first date starts the range, a live hover preview highlights the candidate range, and the second click commits it — with reversed selections sorted automatically. Use it for booking, scheduling, or any range-picking surface that needs a keyboard-navigable grid instead of two text inputs. Pair it with SDateRangePicker when you need a popup trigger, or use it standalone inside forms and custom layouts.

Usage

Features

  • 📅 Month grids — one or more grids (numberOfMonths) with locale-aware weekday headers, today, and outside-view markers
  • 🎯 Range picking — click start then end (in either order, sorted automatically); hover previews the candidate range before commit
  • 🎚 Controlled / uncontrolled — v-model/defaultValue bound to a DateRange ({ start, end }), backed by useControllableState
  • ⌨️ Full keyboard support — ArrowLeft/ArrowRight/ArrowUp/ArrowDown navigation with RTL reversal, cross-month paging, and disabled-date skipping; Enter/Space selects the start/end
  • ⛓ Range constraints — allowNonContiguousRanges toggles gaps, maximumDays caps the span, isDateDisabled/isDateUnavailable/isDateHighlightable control every day, minValue/maxValue clamp the page
  • 📌 Fixed anchors — fixedDate ('start'/'end') keeps one end pinned while re-picking the other; preventDeselect keeps a lone start sticky
  • 🧩 Composable slots — day, head-cell, heading, prev, and next slots with rich slot props; *Props passthrough for every sub-part
  • 🔤 Localized UI — locale, weekStartsOn, and weekdayFormat; month/year Select controls carry localized aria-labels
  • 🚫 Disabled / readonly — disabled blocks all interaction; readonly keeps browsing but blocks selection
  • ↕️ Fixed weeks — fixedWeeks renders a consistent 6-row grid; initialFocus moves focus into the grid on mount
  • 🖱 Live hover preview — data-highlighted/data-highlighted-start/data-highlighted-end attributes drive range preview styling while picking the end

Component family

  • SCalendarRange — the styled wrapper that forwards props to the Aria compact, injects calendarRangeVariants classes (12 slots, shared button icon recipes), and renders the default month/year Select controls in the heading
  • CalendarRangeCompact (Aria) — data-driven composition of CalendarRangeRoot + header (prev/heading/next) + one grid per month (grid head/body rows of CalendarRangeCellTrigger); import from @vean/aria/calendar-range for unstyled usage
  • CalendarRangeRoot (Aria) — state owner: useControllableState for DateRange/placeholder, useCalendar for grid/month pages, useCalendarRangeState for selected/highlighted/invalid state and candidate-range validation
  • CalendarRangeCellTrigger / CalendarRangeCell / CalendarRangeGrid* (Aria) — the editable day button (keyboard handling, focus management, range data attributes) and its semantic grid cell wrappers

Demos

Basic

Range: Sat Apr 18 2026 00:00:00 GMT+0000 (Coordinated Universal Time) ~ Wed Apr 22 2026 00:00:00 GMT+0000 (Coordinated Universal Time)

Disabled

Custom Styling

API

Aria Layer

14

CalendarRange

43 Props3 Emits6 Slots
Props
PropTypeDefaultDescription
class
ClassValue
—
Additional class names applied to the root element.
size
ThemeSize
—
Visual size of the component.
ui
Partial<CalendarRangeUi>
—
Per-slot class overrides for the component.
headerProps
CalendarRangeHeaderProps
—
Properties forwarded to the header element.
headingProps
CalendarRangeHeadingProps
—
Properties forwarded to the heading element.
prevProps
CalendarRangePrevProps
—
Properties forwarded to the prev element.
nextProps
CalendarRangeNextProps
—
Properties forwarded to the next element.
gridProps
CalendarRangeGridProps
—
Properties forwarded to the grid element.
gridHeadProps
CalendarRangeGridHeadProps
—
Properties forwarded to the grid head element.
gridBodyProps
CalendarRangeGridBodyProps
—
Properties forwarded to the grid body element.
gridRowProps
CalendarRangeGridRowProps
—
Properties forwarded to the grid row element.
headCellProps
CalendarRangeHeadCellProps
—
Properties forwarded to the head cell element.
cellProps
CalendarRangeCellProps
—
Properties forwarded to the cell element.
cellTriggerProps
CalendarRangeCellTriggerProps
—
Properties forwarded to the cell trigger element.
defaultValue
DateRange
—
Default value.
defaultPlaceholder
DateValue
—
Default placeholder.
placeholder
DateValue
—
Placeholder.
modelValue
DateRange
—
Current model value.
allowNonContiguousRanges
boolean
—
Whether to allow non contiguous ranges.
pagedNavigation
boolean
—
Whether paged navigation.
preventDeselect
boolean
—
Whether prevent deselect.
maximumDays
number
—
Maximum days.
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.
locale
string
—
Locale.
numberOfMonths
number
—
Number of months.
disabled
boolean
—
Whether the component is disabled.
readonly
boolean
—
Whether the component is readonly.
initialFocus
boolean
—
Whether initial focus.
isDateDisabled
DateMatcher
—
Whether the date is disabled.
isDateUnavailable
DateMatcher
—
Whether the date is unavailable.
isDateHighlightable
DateMatcher
—
Whether a date highlightable.
dir
Direction
—
Reading direction of the component.
nextPage
((placeholder: DateValue) => DateValue)
—
Next page.
prevPage
((placeholder: DateValue) => DateValue)
—
Prev page.
disableDaysOutsideCurrentView
boolean
—
Whether to disable days outside current view.
fixedDate
'start' | 'end'
—
Fixed 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`
Emits
Emit NameParametersDescription
update:modelValue
DateRange
Emitted when the model value changes.
update:placeholder
DateValue
Emitted when the placeholder value changes.
update:startValue
DateValue | null
Emitted when the start value changes.
Slots
Slot NameParametersDescription
default
{ modelValue: DateRange; }
Custom content for the default slot.
prev
{ disabled: boolean; }
Custom content for the prev slot.
heading
CalendarRangeHeadingSlotProps
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
CalendarRangeDaySlotProps
Custom content for the day slot.

CalendarRangeCell

3 Props
Props
PropTypeDefaultDescription
date
DateValue
—
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`

CalendarRangeCellTrigger

6 Props1 Slots
Props
PropTypeDefaultDescription
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 NameParametersDescription
default
CalendarRangeCellTriggerSlotProps
—

CalendarRangeCompact

40 Props3 Emits6 Slots
Props
PropTypeDefaultDescription
headerProps
CalendarRangeHeaderProps
—
Properties forwarded to the header element.
headingProps
CalendarRangeHeadingProps
—
Properties forwarded to the heading element.
prevProps
CalendarRangePrevProps
—
Properties forwarded to the prev element.
nextProps
CalendarRangeNextProps
—
Properties forwarded to the next element.
gridProps
CalendarRangeGridProps
—
Properties forwarded to the grid element.
gridHeadProps
CalendarRangeGridHeadProps
—
Properties forwarded to the grid head element.
gridBodyProps
CalendarRangeGridBodyProps
—
Properties forwarded to the grid body element.
gridRowProps
CalendarRangeGridRowProps
—
Properties forwarded to the grid row element.
headCellProps
CalendarRangeHeadCellProps
—
Properties forwarded to the head cell element.
cellProps
CalendarRangeCellProps
—
Properties forwarded to the cell element.
cellTriggerProps
CalendarRangeCellTriggerProps
—
Properties forwarded to the cell trigger element.
defaultValue
DateRange
—
Default value.
defaultPlaceholder
DateValue
—
Default placeholder.
placeholder
DateValue
—
Placeholder.
modelValue
DateRange
—
Current model value.
allowNonContiguousRanges
boolean
—
Whether to allow non contiguous ranges.
pagedNavigation
boolean
—
Whether paged navigation.
preventDeselect
boolean
—
Whether prevent deselect.
maximumDays
number
—
Maximum days.
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.
locale
string
—
Locale.
numberOfMonths
number
—
Number of months.
disabled
boolean
—
Whether the component is disabled.
readonly
boolean
—
Whether the component is readonly.
initialFocus
boolean
—
Whether initial focus.
isDateDisabled
DateMatcher
—
Whether the date is disabled.
isDateUnavailable
DateMatcher
—
Whether the date is unavailable.
isDateHighlightable
DateMatcher
—
Whether a date highlightable.
dir
Direction
—
Reading direction of the component.
nextPage
((placeholder: DateValue) => DateValue)
—
Next page.
prevPage
((placeholder: DateValue) => DateValue)
—
Prev page.
disableDaysOutsideCurrentView
boolean
—
Whether to disable days outside current view.
fixedDate
'start' | 'end'
—
Fixed 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`
Emits
Emit NameParametersDescription
update:modelValue
DateRange
Emitted when the model value changes.
update:placeholder
DateValue
Emitted when the placeholder value changes.
update:startValue
DateValue | null
Emitted when the start value changes.
Slots
Slot NameParametersDescription
default
{ modelValue: DateRange; }
Custom content for the default slot.
prev
{ disabled: boolean; }
Custom content for the prev slot.
heading
CalendarRangeHeadingSlotProps
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
CalendarRangeDaySlotProps
Custom content for the day slot.

CalendarRangeGrid

2 Props
Props
PropTypeDefaultDescription
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`

CalendarRangeGridBody

2 Props
Props
PropTypeDefaultDescription
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`

CalendarRangeGridHead

2 Props
Props
PropTypeDefaultDescription
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`

CalendarRangeGridRow

2 Props
Props
PropTypeDefaultDescription
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`

CalendarRangeHeadCell

2 Props
Props
PropTypeDefaultDescription
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`

CalendarRangeHeader

2 Props
Props
PropTypeDefaultDescription
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`

CalendarRangeHeading

2 Props
Props
PropTypeDefaultDescription
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`

CalendarRangeNext

5 Props
Props
PropTypeDefaultDescription
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`

CalendarRangePrev

5 Props
Props
PropTypeDefaultDescription
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`

CalendarRangeRoot

29 Props3 Emits1 Slots
Props
PropTypeDefaultDescription
defaultValue
DateRange
—
Default value.
defaultPlaceholder
DateValue
—
Default placeholder.
placeholder
DateValue
—
Placeholder.
modelValue
DateRange
—
Current model value.
allowNonContiguousRanges
boolean
—
Whether to allow non contiguous ranges.
pagedNavigation
boolean
—
Whether paged navigation.
preventDeselect
boolean
—
Whether prevent deselect.
maximumDays
number
—
Maximum days.
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.
locale
string
—
Locale.
numberOfMonths
number
—
Number of months.
disabled
boolean
—
Whether the component is disabled.
readonly
boolean
—
Whether the component is readonly.
initialFocus
boolean
—
Whether initial focus.
isDateDisabled
DateMatcher
—
Whether the date is disabled.
isDateUnavailable
DateMatcher
—
Whether the date is unavailable.
isDateHighlightable
DateMatcher
—
Whether a date highlightable.
dir
Direction
—
Reading direction of the component.
nextPage
((placeholder: DateValue) => DateValue)
—
Next page.
prevPage
((placeholder: DateValue) => DateValue)
—
Prev page.
disableDaysOutsideCurrentView
boolean
—
Whether to disable days outside current view.
fixedDate
'start' | 'end'
—
Fixed 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`
Emits
Emit NameParametersDescription
update:modelValue
DateRange
Emitted when the model value changes.
update:placeholder
DateValue
Emitted when the placeholder value changes.
update:startValue
DateValue | null
Emitted when the start value changes.
Slots
Slot NameParametersDescription
default
CalendarRangeRootSlotProps
Custom content for the default slot.

Notes

Architecture and benchmark differences

CalendarRangeRoot owns the value via useControllableState (DateRange = { start, end }), keeps the placeholder for grid paging, and delegates grid creation to useCalendar (shared with calendar). useCalendarRangeState derives selected/highlighted state and exposes isRangeInvalid(start, end) — a candidate-range validator used both by the derived data-invalid state and by onDateChange when committing a new range (so a non-contiguous range is rejected based on the candidate, not the previously committed state). CalendarRangeCellTrigger is the only interactive piece: it reads the shared context, computes data-selection-start/data-selection-end/data-highlighted/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. CalendarRangeCell carries data-in-range for the days of a committed range, which is what the styled band keys on — a lone start is data-selected without a band, so the band can only appear once both ends are picked. Most benchmark libraries ship a monolithic range panel; the Aria/styled split, per-part *Props passthrough, hover preview, and candidate-range validation are the differentiators.

CapabilityVeanUIAnt DesignElement PlusMantineNaive UIshadcn
Aria/styled split✅—————
Range picking (start + end)✅✅✅✅✅—
Hover range preview✅——✅——
Keyboard grid navigation✅✅✅✅✅✅
RTL direction reversal✅—————
numberOfMonths grid✅——✅——
allowNonContiguousRanges✅—✅✅——
maximumDays cap✅——✅——
fixedDate pinned end✅—————
minValue/maxValue bounds✅✅✅✅✅—
isDateDisabled / unavailable✅✅✅✅✅—
Month/year Select controls✅✅✅———
Candidate-range validation✅—————

Cautions

  • The value is a DateRange — { start, end } of DateValues (native Date at local midnight), never strings.
  • A range with gaps is rejected by default (the second click restarts the range at the clicked day); pass allowNonContiguousRanges to allow gaps.
  • defaultValue/defaultPlaceholder are read on mount only — use v-model for external control.
  • isDateDisabled and isDateUnavailable differ: disabled dates are unselectable by policy, unavailable dates are additionally marked visually (line-through in the default style).
  • fixedDate pins one end: with fixedDate="start" re-picking always updates the end; with fixedDate="end" re-picking always updates the start.
  • The prev/next aria-labels default to locale messages; override per button through prevProps/nextProps.
  • Each day button’s aria-label defaults to the full localized date; override it per cell through cellTriggerProps.
  • The range band is cell-level: days inside a committed range carry data-in-range (a lone start is data-selected only, so it draws no band). Key custom band styling on ui.cell + data-in-range, not on data-selected.

FAQ

How do I select a range?

Click the start date, then the end date — in either order. While picking the end, the hovered span is previewed with the highlighted state. The range is emitted as { start, end } via v-model.

How do I prevent ranges that span unavailable days?

Keep allowNonContiguousRanges off (default): when the second click would create a range crossing a disabled or unavailable day, the selection is rejected and restarts at the clicked day. Set allowNonContiguousRanges to allow gaps.

How do I cap the range length?

Pass maximumDays (inclusive). Clicking an end that exceeds the cap restarts the selection at the clicked day.

How do I keep one end fixed while re-picking?

Pass fixedDate="start" (or "end"). Clicking any day updates the other end, and clicking before/after the pinned end sorts the result automatically.

How do I build a popup range picker?

Use SDateRangePicker, which composes CalendarRangeCompact inside a popover trigger. Use SCalendarRange standalone when the calendar surface is always visible.

Changelog

Version history is grouped by release so you can quickly see what changed for this component across published versions.

v0.50.0

2026-10-02

Compare release

Feature

mark committed range days with data-in-range

Scope: calendar-rangeAuthors: soybeanjsView commit2db06

Fix

draw the range band only after both ends are picked

Scope: calendar-rangeAuthors: soybeanjsView commit26cbd

Optimization

update root style to include rounded corners and shadow

Scope: calendar-rangeAuthors: soybeanjsView commita9c87

Docs

document the cell data-in-range band contract

Scope: calendar-rangeAuthors: soybeanjsView commitab6d0

Chore

cover the committed range band state

Scope: calendar-rangeAuthors: soybeanjsView commit963b4

Feature

mark committed range days with data-in-range

Scope: calendar-rangeAuthors: soybeanjsView commit2db06

Fix

draw the range band only after both ends are picked

Scope: calendar-rangeAuthors: soybeanjsView commit26cbd

Optimization

update root style to include rounded corners and shadow

Scope: calendar-rangeAuthors: soybeanjsView commita9c87

Docs

document the cell data-in-range band contract

Scope: calendar-rangeAuthors: soybeanjsView commitab6d0

Chore

cover the committed range band state

Scope: calendar-rangeAuthors: soybeanjsView commit963b4

v0.30.0

2026-08-20

Compare release

Style

fix highlighted style

Scope: calendar-rangeAuthors: soybeanfeView commit37b50

Style

fix highlighted style

Scope: calendar-rangeAuthors: soybeanfeView commit37b50