Thumbprint logo

Components

Calendar

A visual calendar display for displaying availability and/or selecting dates

CalendarV2 and RangeCalendar are single-month calendars built on React Aria and styled with Thumbprint v2 semantic tokens. React Aria owns state, keyboard navigation, focus management, and the ARIA grid semantics. Both are controlled and Date-based: values go in and come out as JavaScript Date objects.

Migrating from v1

The v1 Calendar packed single, multiple, and range selection into one component behind boolean flags. v2 splits that surface in two: CalendarV2 handles single and multiple selection, and RangeCalendar handles range selection. Most other prop names carry over, so many call sites migrate by swapping the import and adjusting the props below.

The biggest change is how the selectable window is expressed. v1 used disabledDays (a react-day-picker matcher) both to disable past days and to grey out specific days, plus lastMonth for the upper bound. v2 splits these: minValue / maxValue bound the window (and minValue defaults to today, so past days are non-selectable out of the box), while disabledDays is now a simple (date) => boolean that marks individual days unavailable. To allow selecting past days, pass an earlier minValue rather than disabledDays={null}.

v1 Calendarv2 equivalent
allowMultiSelectionselectionMode="multiple" on CalendarV2
enableDateRangeSelectionthe RangeCalendar component
rangeStartDaterangeStartDate on RangeCalendar (same behavior)
lastMonthmaxValue
disabledDays={null} (to allow past days)minValue set to an earlier date
disabledDays (matcher, to grey out days)disabledDays: (date) => boolean
value, month, onChange, onMonthChange, daysThemeDotIndicator, daysThemeStrikeoutunchanged

CalendarV2

Single date selection

By default the calendar selects a single day. It is controlled — hold the value in state and update it from onChange, which always hands back an array (empty when the selection is cleared).

function CalendarExample() {
    const [value, setValue] = React.useState([new Date()]);
    return <CalendarV2 value={value} onChange={setValue} />;
}

Multiple date selection

Set selectionMode="multiple" to let the user toggle several days. value is an array and onChange receives the full updated array.

function CalendarExample() {
    const DAY = 24 * 60 * 60 * 1000; // one day in milliseconds
    const [value, setValue] = React.useState([
        new Date(),
        new Date(Date.now() + DAY * 2),
        new Date(Date.now() + DAY * 5),
    ]);
    return <CalendarV2 selectionMode="multiple" value={value} onChange={setValue} />;
}

Bounding the selectable window

minValue defaults to today, so past days can’t be selected or navigated to. Pass an earlier minValue to allow past days, and maxValue to cap the upper end. Days outside the window are disabled and the nav buttons stop at the boundary month.

function CalendarExample() {
    const DAY = 24 * 60 * 60 * 1000; // one day in milliseconds
    const [value, setValue] = React.useState([new Date()]);
    return (
        <CalendarV2
            value={value}
            onChange={setValue}
            minValue={new Date(Date.now() - DAY * 30)}
            maxValue={new Date(Date.now() + DAY * 30)}
        />
    );
}

Unavailable days

disabledDays takes a (date) => boolean predicate. Days it returns true for stay focusable but can’t be selected, and are struck through.

function CalendarExample() {
    const [value, setValue] = React.useState([]);
    const isWeekend = date => date.getDay() === 0 || date.getDay() === 6;
    return <CalendarV2 value={value} onChange={setValue} disabledDays={isWeekend} />;
}

Dot indicator with daysThemeDotIndicator

Pass a function that returns true for any date that should show a dot below the number.

function CalendarExample() {
    const [value, setValue] = React.useState([new Date()]);
    const isMonday = date => date.getDay() === 1;
    return <CalendarV2 value={value} onChange={setValue} daysThemeDotIndicator={isMonday} />;
}

Strikeout with daysThemeStrikeout

Pass a function that returns true for any date whose number should be crossed out with a diagonal strike.

function CalendarExample() {
    const [value, setValue] = React.useState([new Date()]);
    const isFriday = date => date.getDay() === 5;
    return <CalendarV2 value={value} onChange={setValue} daysThemeStrikeout={isFriday} />;
}

RangeCalendar

Selecting a range

Click a start date, then an end date. The two ends render as filled circles with the days between them in a connecting band; while picking the end, the day under the cursor is previewed. onChange fires once both ends are chosen and receives the [start, end] pair.

function RangeCalendarExample() {
    const DAY = 24 * 60 * 60 * 1000; // one day in milliseconds
    const [value, setValue] = React.useState([
        new Date(Date.now() + DAY * 2),
        new Date(Date.now() + DAY * 6),
    ]);
    return <RangeCalendar value={value} onChange={setValue} />;
}

Fixed start date

Set rangeStartDate to lock the start so the user only ever picks the end. Clicking the start or any earlier day is ignored, so the end always lands after the start. Provide value as [rangeStartDate, endDate] (or just [rangeStartDate] before an end is chosen). Until an end is chosen, hovering a later day previews the range; once one is committed the band is frozen — click another day to re-pick.

function RangeCalendarExample() {
    const start = new Date();
    const [value, setValue] = React.useState([start]);
    return <RangeCalendar value={value} onChange={setValue} rangeStartDate={start} />;
}

Ranges that span unavailable days

By default a range can’t cross a day made unavailable by disabledDays — the selectable end is capped before the first one. Set allowsNonContiguousRanges to let a range span them. (This is ignored when rangeStartDate is set.)

function RangeCalendarExample() {
    const [value, setValue] = React.useState(null);
    const isWeekend = date => date.getDay() === 0 || date.getDay() === 6;
    return (
        <RangeCalendar
            value={value}
            onChange={setValue}
            disabledDays={isWeekend}
            allowsNonContiguousRanges
        />
    );
}

Props

CalendarV2

A single month calendar built on React Aria's `Calendar`.
  • onChange
    required

    Fired when the selection changes. Always receives an array, even in single mode ([] when cleared, [date] when a day is selected).

    Type
    (selectedDates: Date[]) => void
  • minValue

    Disables (and prevents navigation to) dates before this point. Defaults to today, so past days are non-selectable out of the box. Pass an earlier Date to allow selecting past days.

    Type
    Date
  • maxValue

    Disables (and prevents navigation to) dates after this point.

    Type
    Date
  • disabledDays

    Return true to make a day unavailable (focusable but not selectable).

    Type
    (date: Date) => boolean
  • month

    The displayed month — any day within the desired month works. It seeds the initial month and, when changed, pushes the calendar to that month; navigation still works without updating it. Pair with onMonthChange to drive the month externally.

    Type
    Date
  • onMonthChange

    Called when the user navigates to a different month (via the nav buttons or keyboard). Receives a JS Date in the newly displayed month. Unlike React Aria's day-level onFocusChange, this fires only when the month changes, not on every focused-day movement.

    Type
    (month: Date) => void
  • daysThemeDotIndicator

    Applies a dot indicator below the numeric day when the function returns true for a given Date. Emits data-dot on the cell.

    Type
    (date: Date) => boolean
  • daysThemeStrikeout

    Applies a strikeout treatment on the numeric day when the function returns true for a given Date. Emits data-strikeout on the cell.

    Type
    (date: Date) => boolean
  • dataTestId

    A selector hook into the React component for use in automated testing environments.

    Type
    string
  • aria-label

    Accessible label for the calendar. Provide this or aria-labelledby.

    Type
    string
  • aria-labelledby

    ID of an element that labels the calendar. Provide this or aria-label.

    Type
    string
  • selectionMode

    Whether the user can select a single day or many. Defaults to 'single'. (Range selection lives in the separate RangeCalendar component.)

    Type
    'single' | 'multiple'
    Default
    'single'
  • value

    Selected date(s). In 'single' mode either a scalar or a 1-element array works; in 'multiple' mode pass an array. Each "date" can be a JS Date, a date string, or a numeric UNIX timestamp. The calendar is controlled: pair this with onChange.

    Type
    DateIsh | DateIsh[] | null

RangeCalendar

A single month range calendar built on React Aria. Its public surface is `Date`-based: `value`/`onChange` speak `[start, end]` pairs.
  • onChange
    required

    Fired once a range is completed (both ends chosen). Receives the [start, end] pair as local-midnight JS Dates.

    Type
    (range: [Date, Date]) => void
  • minValue

    Disables (and prevents navigation to) dates before this point. Defaults to today, so past days are non-selectable out of the box. Pass an earlier Date to allow selecting past days.

    Type
    Date
  • maxValue

    Disables (and prevents navigation to) dates after this point.

    Type
    Date
  • disabledDays

    Return true to make a day unavailable (focusable but not selectable).

    Type
    (date: Date) => boolean
  • month

    The displayed month — any day within the desired month works. It seeds the initial month and, when changed, pushes the calendar to that month; navigation still works without updating it. Pair with onMonthChange to drive the month externally.

    Type
    Date
  • onMonthChange

    Called when the user navigates to a different month (via the nav buttons or keyboard). Receives a JS Date in the newly displayed month. Unlike React Aria's day-level onFocusChange, this fires only when the month changes, not on every focused-day movement.

    Type
    (month: Date) => void
  • daysThemeDotIndicator

    Applies a dot indicator below the numeric day when the function returns true for a given Date. Emits data-dot on the cell.

    Type
    (date: Date) => boolean
  • daysThemeStrikeout

    Applies a strikeout treatment on the numeric day when the function returns true for a given Date. Emits data-strikeout on the cell.

    Type
    (date: Date) => boolean
  • dataTestId

    A selector hook into the React component for use in automated testing environments.

    Type
    string
  • aria-label

    Accessible label for the calendar. Provide this or aria-labelledby.

    Type
    string
  • aria-labelledby

    ID of an element that labels the calendar. Provide this or aria-label.

    Type
    string
  • value

    The selected [start, end] range. Each endpoint can be a JS Date, a date string, or a numeric UNIX timestamp. The calendar is controlled: pair this with onChange.

    Type
    DateRangeIsh | null
  • allowsNonContiguousRanges

    Allows a range to span unavailable days (from disabledDays). By default a range cannot cross an unavailable day, so the selectable end is capped before the first one. Ignored when rangeStartDate is set.

    Type
    boolean
  • rangeStartDate

    Locks the start of the range to this date so the user only ever picks the end. When set, each click chooses the end — clicking the start or an earlier day is ignored, so the end always lands after the start — and, until an end is chosen, hovering a later day previews the start-to-hovered range. value holds [rangeStartDate, endDate] once an end is chosen.

    Type
    Date