Skip to main content
Version: v0.22.x

Date range & multiple dates

Pick a check-in / check-out date range, several ranges, or several separate dates, in React Native. DatePicker (and Calendar) switch selection model with mode. TypeScript narrows value and onChange for each mode.

modevalueonChangeExtra props
'single' (default)Date | null(date: Date) => void—
'range'DateRange ({ start: Date | null; end: Date | null })(range: DateRange) => voidminRangeDays, maxRangeDays, allowSingleDayRange
'multiple'Date[](dates: Date[]) => voidmaxSelections
'ranges'DateRange[](ranges: DateRange[]) => voidmaxRanges, minRangeDays, maxRangeDays, allowSingleDayRange

The modes are also exported as constants: mode={CALENDAR_MODE.ranges}.

Try the props​

Platform
mode
maxRangeDays
maxSelections
maxRanges
<DatePicker
value={value}
onChange={setValue}
mode="range"
maxRangeDays={14}
maxSelections={5}
maxRanges={3}
/>

Range​

Loading…
<>
  <DatePicker
    mode="range"
    value={range}
    onChange={setRange}
    minDate={new Date()}
  />
  <Text style={{ textAlign: 'center', color: '#6E685E' }}>
    {range.start ? formatDate(range.start) : 'Check-in'} →{' '}
    {range.end ? formatDate(range.end) : 'Check-out'}
    {nights ? `  ·  ${nights} night${nights > 1 ? 's' : ''}` : ''}
  </Text>
</>

How taps work​

  1. First tap sets the start: onChange({ start: day, end: null }).
  2. Second tap on or after the start sets the end: onChange({ start, end: day }).
  3. A tap before the start, or any tap after a complete range, starts a new range from that day.

The band between start and end uses primaryMuted; the ends use primary.

Ranges never cross disabled days​

If the days between start and the tapped day include a disabled one (from minDate/maxDate, isDateDisabled or markedDates), the tap starts a new range instead of producing a range that spans an unavailable night. That's exactly what booking flows need:

Loading…
<DatePicker
  mode="range"
  value={range}
  onChange={setRange}
  minDate={today}
  isDateDisabled={(d) => booked.has(toDateKey(d))}
/>

Minimum and maximum length​

Length is measured in days between start and end (nights): a range from the 10th to the 13th is 3.

PropEffect when violated
minRangeDaysA second tap that would be too short is ignored; the start stays selected
maxRangeDaysA second tap that would be too long starts a new range from that day
allowSingleDayRange={false}Tapping the start again is ignored (by default it makes a one-day range, start === end)
Loading…
<>
  <Text style={{ textAlign: 'center', marginBottom: 4, color: '#6E685E' }}>
    Stay 2 – 7 nights
  </Text>
  <DatePicker
    mode="range"
    value={range}
    onChange={setRange}
    minDate={new Date()}
    minRangeDays={2}
    maxRangeDays={7}
    allowSingleDayRange={false}
  />
</>

Range styling​

Loading…
<DatePicker
  mode="range"
  value={range}
  onChange={setRange}
  theme={{ colors: { primary: '#E11D48', primaryMuted: '#FFE4E6' } }}
  styles={{
    selectedDay: { borderRadius: 10 },
    rangeBand: { borderRadius: 0 },
  }}
/>

Multiple​

Tap days to toggle them. onChange always receives the dates sorted ascending. With maxSelections, further taps on unselected days are ignored once the limit is reached (deselecting still works).

Loading…
<>
  <DatePicker
    mode="multiple"
    value={dates}
    onChange={setDates}
    maxSelections={5}
    minDate={new Date()}
  />
  <Text style={{ textAlign: 'center', color: '#6E685E' }}>
    {dates.length
      ? dates
          .map((d) =>
            formatDate(d, undefined, { day: 'numeric', month: 'short' })
          )
          .join(', ')
      : 'Pick up to 5 days'}
  </Text>
</>

Multiple ranges​

mode="ranges" picks several date ranges, e.g. blackout periods or trip legs. Each range is built with two taps, just like range mode. Tapping any day of a finished range removes it. Ranges never overlap, and onChange receives them sorted by start. The range rules (minRangeDays, maxRangeDays, allowSingleDayRange) apply to each range. With maxRanges, taps that would start another range are ignored once the limit is reached.

Loading…
<>
  <DatePicker
    mode="ranges"
    value={ranges}
    onChange={setRanges}
    maxRanges={3}
    minDate={new Date()}
  />
  <Text style={{ textAlign: 'center', color: '#6E685E' }}>
    {ranges.length
      ? ranges.map((r) => `${short(r.start)} – ${short(r.end)}`).join(', ')
      : 'Pick up to 3 ranges'}
  </Text>
</>

Uncontrolled​

<DatePicker mode="range" defaultValue={{ start: new Date(), end: null }} onChange={(r) => console.log(r)} />
<DatePicker mode="multiple" defaultValue={[]} onChange={(d) => console.log(d)} />

In a sheet​

DatePickerSheet disables Done until the range is complete (both start and end). In ranges mode, it waits until no range is half-picked.

<DatePickerSheet
visible={open}
onClose={() => setOpen(false)}
title="Your stay"
mode="range"
value={range}
onConfirm={setRange}
minDate={new Date()}
minRangeDays={1}
/>

→ DatePickerSheet

In a form field​

DatePickerField takes the same props without visible, and shows the confirmed range (Oct 5, 2026 – Oct 9, 2026) or dates in the field. Switch presentation in the panel: auto opens a dropdown under the field on the web and a bottom sheet on iOS and Android.

Platform
presentation
mode
<DatePickerField
label="Your stay"
placeholder="Add dates"
value={stay}
onConfirm={setStay}
mode="range"
/>

→ Picker fields

Props​

All calendar props apply. Mode-specific props:

PropModeTypeDefaultDescription
minRangeDaysrange, rangesnumber—Minimum nights (end − start in days). Shorter second taps are ignored.
maxRangeDaysrange, rangesnumber—Maximum nights. Longer second taps start a new range.
allowSingleDayRangerange, rangesbooleantrueAllow start === end.
maxSelectionsmultiplenumber—Maximum number of selected days.
maxRangesrangesnumber—Maximum number of ranges.

Working with ranges​

The date utilities make common range checks one-liners:

import { isBetweenDays, isSameDay, compareDay, addDays } from '@elik56a/react-native-pickers';

const nights = Math.round((range.end!.getTime() - range.start!.getTime()) / 86_400_000);
const includesToday = isSameDay(range.start, new Date()) || isBetweenDays(new Date(), range.start, range.end);
const isFuture = compareDay(range.start!, new Date()) > 0;