RangeCalendar
A range calendar displays a grid of days and allows users to select a contiguous range of dates by clicking a start date and then an end date. It is useful for booking flows, report filtering, and any scenario that requires a date span.
Import
import { RangeCalendar } from '@proyecto-viviana/solid-spectrum';
import {
CalendarDateClass as CalendarDate,
type DateValue,
type RangeValue,
} from '@proyecto-viviana/solid-stately';Basic Range Selection
Click a date to set the range start, then click another date to set the range end. The days between start and end are highlighted. The selected range is displayed below the calendar.
Selected range: None
const [range, setRange] = createSignal<RangeValue<DateValue> | null>(null);
<RangeCalendar
aria-label="Trip dates"
value={range()}
onChange={setRange}
/>
<p>Selected: {formatRange(range())}</p>With Min/Max Constraints
Restrict the selectable range to a specific window. Dates outside the min/max bounds are disabled and navigation beyond those months is prevented.
Only dates in 2026 are selectable
const minDate = new CalendarDate(2026, 1, 1);
const maxDate = new CalendarDate(2026, 12, 31);
<RangeCalendar
aria-label="2026 date range"
minValue={minDate}
maxValue={maxDate}
/>Unavailable Dates
Use isDateUnavailable to block specific dates from being included in a range. Users cannot select a range that spans across unavailable dates.
Mar 25-27 are unavailable
const isDateUnavailable = (date: DateValue) =>
unavailableDates.some(
(d) => d.year === date.year && d.month === date.month && d.day === date.day
);
<RangeCalendar
aria-label="Booking dates"
isDateUnavailable={isDateUnavailable}
defaultValue={{ start: new CalendarDate(2026, 3, 1), end: new CalendarDate(2026, 3, 5) }}
/>Disabled State
A disabled range calendar is non-interactive. All cells are muted and navigation buttons are inoperative.
<RangeCalendar
aria-label="Disabled calendar"
isDisabled
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
value | RangeValue<DateValue> | null | — | The currently selected date range (controlled) |
onChange | (range: RangeValue<DateValue>) => void | — | Handler called when the selected range changes |
defaultValue | RangeValue<DateValue> | — | The default selected range (uncontrolled) |
minValue | DateValue | — | The minimum selectable date |
maxValue | DateValue | — | The maximum selectable date |
isDisabled | boolean | false | Whether the range calendar is disabled |
isDateUnavailable | (date: DateValue) => boolean | — | Callback that returns whether a specific date is unavailable for selection |
aria-label | string | — | Accessible label for the range calendar |
size | 'sm' | 'md' | 'lg' | 'md' | The visual size of the range calendar |
locale | string | — | The locale to use for formatting month and day names |
Accessibility
- Uses a
gridrole withgridcellelements for each day - Full keyboard navigation: Arrow keys move between days, Page Up/Down change months
- Range start and end are visually distinct with rounded corners at the endpoints
- Days within the range use
aria-selected="true"for screen reader announcement - Unavailable dates are announced as disabled via
aria-disabled - Previous/next month buttons include descriptive
aria-labelattributes - Today's date is visually indicated with a ring highlight