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.
mode | value | onChange | Extra props |
|---|---|---|---|
'single' (default) | Date | null | (date: Date) => void | โ |
'range' | DateRange ({ start: Date | null; end: Date | null }) | (range: DateRange) => void | minRangeDays, maxRangeDays, allowSingleDayRange |
'multiple' | Date[] | (dates: Date[]) => void | maxSelections |
'ranges' | DateRange[] | (ranges: DateRange[]) => void | maxRanges, minRangeDays, maxRangeDays, allowSingleDayRange |
The modes are also exported as constants: mode={CALENDAR_MODE.ranges}.
Try the propsโ
modemaxRangeDaysmaxSelectionsmaxRanges<DatePicker
value={value}
onChange={setValue}
mode="range"
maxRangeDays={14}
maxSelections={5}
maxRanges={3}
/>
Rangeโ
import { useState } from 'react'; import { Text } from 'react-native'; import { DatePicker, formatDate } from '@elik56a/react-native-pickers'; import type { DateRange } from '@elik56a/react-native-pickers'; export default function Example() { const [range, setRange] = useState<DateRange>({ start: null, end: null }); const nights = range.start && range.end ? Math.round((+range.end - +range.start) / 864e5) : 0; return ( <> <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โ
- First tap sets the start:
onChange({ start: day, end: null }). - Second tap on or after the start sets the end:
onChange({ start, end: day }). - 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:
import { useState } from 'react'; import { DatePicker, toDateKey, addDays } from '@elik56a/react-native-pickers'; import type { DateRange } from '@elik56a/react-native-pickers'; export default function Example() { const today = new Date(); const booked = new Set([toDateKey(addDays(today, 4)), toDateKey(addDays(today, 5)), toDateKey(addDays(today, 12))]); const [range, setRange] = useState<DateRange>({ start: null, end: null }); return ( <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.
| Prop | Effect when violated |
|---|---|
minRangeDays | A second tap that would be too short is ignored; the start stays selected |
maxRangeDays | A 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) |
import { useState } from 'react'; import { Text } from 'react-native'; import { DatePicker } from '@elik56a/react-native-pickers'; import type { DateRange } from '@elik56a/react-native-pickers'; export default function Example() { const [range, setRange] = useState<DateRange>({ start: null, end: null }); return ( <> <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โ
import { useState } from 'react'; import { DatePicker, addDays } from '@elik56a/react-native-pickers'; import type { DateRange } from '@elik56a/react-native-pickers'; export default function Example() { const t = new Date(); const [range, setRange] = useState<DateRange>({ start: addDays(t, 2), end: addDays(t, 6) }); return ( <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).
import { useState } from 'react'; import { Text } from 'react-native'; import { DatePicker, formatDate } from '@elik56a/react-native-pickers'; export default function Example() { const [dates, setDates] = useState<Date[]>([]); return ( <> <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.
import { useState } from 'react'; import { Text } from 'react-native'; import { DatePicker, formatDate } from '@elik56a/react-native-pickers'; import type { DateRange } from '@elik56a/react-native-pickers'; export default function Example() { const [ranges, setRanges] = useState<DateRange[]>([]); const short = (d: Date | null) => (d ? formatDate(d, undefined, { day: 'numeric', month: 'short' }) : 'โฆ'); return ( <> <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:
<DatePickerField label="Your stay" placeholder="Add dates" mode="range" value={range} onConfirm={setRange} minDate={new Date()} />
โ Picker fields
Propsโ
All calendar props apply. Mode-specific props:
| Prop | Mode | Type | Default | Description |
|---|---|---|---|---|
minRangeDays | range, ranges | number | โ | Minimum nights (end โ start in days). Shorter second taps are ignored. |
maxRangeDays | range, ranges | number | โ | Maximum nights. Longer second taps start a new range. |
allowSingleDayRange | range, ranges | boolean | true | Allow start === end. |
maxSelections | multiple | number | โ | Maximum number of selected days. |
maxRanges | ranges | number | โ | 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;