A date range picker that combines a trigger field with a range calendar popup for selecting start and end dates.
DateRangePickeruses@internationalized/datefor all date manipulations (CalendarDate, calendar systems, time zones, locale-aware formatting). For full context on the date types and helpers exposed through this component's props and callbacks, read the@internationalized/datedocs alongside this page.
import { DateRangePicker } from 'heroui-native-pro';<DateRangePicker>
<Label>...</Label>
<DateRangePicker.Select>
<DateRangePicker.Trigger>
<DateRangePicker.Value />
<DateRangePicker.TriggerIndicator />
</DateRangePicker.Trigger>
<DateRangePicker.Portal>
<DateRangePicker.Overlay />
<DateRangePicker.Content>
<DateRangePicker.Calendar>
<RangeCalendar.Header>...</RangeCalendar.Header>
<RangeCalendar.Grid>...</RangeCalendar.Grid>
</DateRangePicker.Calendar>
</DateRangePicker.Content>
</DateRangePicker.Portal>
</DateRangePicker.Select>
<Description>...</Description>
</DateRangePicker>value, isOpen, onValueChange, onOpenChange) are managed by the root.variant and inherits invalid border styling from the root."popover", "dialog", and "bottom-sheet" presentations.RangeCalendar compound parts as children.The DateRangePicker uses a popover presentation by default. Pass RangeCalendar compound parts as children of DateRangePicker.Calendar.
<DateRangePicker>
<Label>Trip dates</Label>
<DateRangePicker.Select>
<DateRangePicker.Trigger>
<DateRangePicker.Value />
<DateRangePicker.TriggerIndicator />
</DateRangePicker.Trigger>
<DateRangePicker.Portal>
<DateRangePicker.Overlay />
<DateRangePicker.Content presentation="popover" width="trigger">
<DateRangePicker.Calendar>
<RangeCalendar.Header>
<RangeCalendar.Heading />
<RangeCalendar.NavButton slot="previous" />
<RangeCalendar.NavButton slot="next" />
</RangeCalendar.Header>
<RangeCalendar.Grid>
<RangeCalendar.GridHeader>
{(day) => <RangeCalendar.HeaderCell day={day} />}
</RangeCalendar.GridHeader>
<RangeCalendar.GridBody>
{(date) => <RangeCalendar.Cell date={date} />}
</RangeCalendar.GridBody>
</RangeCalendar.Grid>
</DateRangePicker.Calendar>
</DateRangePicker.Content>
</DateRangePicker.Portal>
</DateRangePicker.Select>
</DateRangePicker>Control the selected range externally with value and onValueChange. The option stores a JSON string with ISO start/end dates in value and a display label shown in the trigger. You can also control the calendar overlay with isOpen and onOpenChange.
import type { DateRangePickerOption } from 'heroui-native-pro';
import { useState } from 'react';
const [selected, setSelected] = useState<DateRangePickerOption | undefined>({
value: '{"start":"2026-04-01","end":"2026-04-07"}',
label: 'Apr 1, 2026 – Apr 7, 2026',
});
const [isOpen, setIsOpen] = useState(false);
<DateRangePicker
value={selected}
onValueChange={setSelected}
isOpen={isOpen}
onOpenChange={setIsOpen}
>
<Label>Trip dates</Label>
<DateRangePicker.Select>
<DateRangePicker.Trigger>
<DateRangePicker.Value />
<DateRangePicker.TriggerIndicator />
</DateRangePicker.Trigger>
<DateRangePicker.Portal>
<DateRangePicker.Overlay />
<DateRangePicker.Content presentation="popover" width="trigger">
<DateRangePicker.Calendar>
<RangeCalendar.Header>
<RangeCalendar.Heading />
<RangeCalendar.NavButton slot="previous" />
<RangeCalendar.NavButton slot="next" />
</RangeCalendar.Header>
<RangeCalendar.Grid>
<RangeCalendar.GridHeader>
{(day) => <RangeCalendar.HeaderCell day={day} />}
</RangeCalendar.GridHeader>
<RangeCalendar.GridBody>
{(date) => <RangeCalendar.Cell date={date} />}
</RangeCalendar.GridBody>
</RangeCalendar.Grid>
</DateRangePicker.Calendar>
</DateRangePicker.Content>
</DateRangePicker.Portal>
</DateRangePicker.Select>
</DateRangePicker>;Display the range calendar in a centered modal dialog.
<DateRangePicker>
<Label>Trip dates</Label>
<DateRangePicker.Select presentation="dialog">
<DateRangePicker.Trigger>
<DateRangePicker.Value />
<DateRangePicker.TriggerIndicator />
</DateRangePicker.Trigger>
<DateRangePicker.Portal>
<DateRangePicker.Overlay />
<DateRangePicker.Content presentation="dialog">
<DateRangePicker.Calendar>...</DateRangePicker.Calendar>
</DateRangePicker.Content>
</DateRangePicker.Portal>
</DateRangePicker.Select>
</DateRangePicker>Display the range calendar in a bottom sheet.
<DateRangePicker>
<Label>Trip dates</Label>
<DateRangePicker.Select presentation="bottom-sheet">
<DateRangePicker.Trigger>
<DateRangePicker.Value />
<DateRangePicker.TriggerIndicator />
</DateRangePicker.Trigger>
<DateRangePicker.Portal>
<DateRangePicker.Overlay />
<DateRangePicker.Content presentation="bottom-sheet">
<DateRangePicker.Calendar>...</DateRangePicker.Calendar>
</DateRangePicker.Content>
</DateRangePicker.Portal>
</DateRangePicker.Select>
</DateRangePicker>Configure how the selected range is displayed in the trigger using dateDisplayFormat.
<DateRangePicker dateDisplayFormat="long">
<Label>Trip dates</Label>
<DateRangePicker.Select>
<DateRangePicker.Trigger>
<DateRangePicker.Value />
<DateRangePicker.TriggerIndicator />
</DateRangePicker.Trigger>
<DateRangePicker.Portal>
<DateRangePicker.Overlay />
<DateRangePicker.Content presentation="popover" width="trigger">
<DateRangePicker.Calendar>...</DateRangePicker.Calendar>
</DateRangePicker.Content>
</DateRangePicker.Portal>
</DateRangePicker.Select>
</DateRangePicker>Override the display label entirely with formatDateRange.
function formatSpanishRange(start: CalendarDate, end: CalendarDate): string {
const fmt = new Intl.DateTimeFormat('es-ES', {
day: 'numeric',
month: 'long',
year: 'numeric',
});
const a = fmt.format(start.toDate(getLocalTimeZone()));
const b = fmt.format(end.toDate(getLocalTimeZone()));
if (start.compare(end) === 0) return a;
return `${a} – ${b}`;
}
<DateRangePicker formatDateRange={formatSpanishRange} locale="es-ES">
<Label>Custom range label</Label>
<DateRangePicker.Select>
<DateRangePicker.Trigger>
<DateRangePicker.Value />
<DateRangePicker.TriggerIndicator />
</DateRangePicker.Trigger>
<DateRangePicker.Portal>
<DateRangePicker.Overlay />
<DateRangePicker.Content presentation="popover" width="trigger">
<DateRangePicker.Calendar>...</DateRangePicker.Calendar>
</DateRangePicker.Content>
</DateRangePicker.Portal>
</DateRangePicker.Select>
<Description>Pick a start date, then an end date.</Description>
</DateRangePicker>;Change the separator between formatted start and end dates in the trigger label.
<DateRangePicker rangeSeparator="to">
<Label>Stay</Label>
<DateRangePicker.Select>
<DateRangePicker.Trigger>
<DateRangePicker.Value />
<DateRangePicker.TriggerIndicator />
</DateRangePicker.Trigger>
<DateRangePicker.Portal>
<DateRangePicker.Overlay />
<DateRangePicker.Content presentation="popover" width="trigger">
<DateRangePicker.Calendar>...</DateRangePicker.Calendar>
</DateRangePicker.Content>
</DateRangePicker.Portal>
</DateRangePicker.Select>
</DateRangePicker>Use root props for required, invalid, and disabled states.
<DateRangePicker isRequired>
<Label>Travel dates</Label>
<DateRangePicker.Select>
<DateRangePicker.Trigger>
<DateRangePicker.Value placeholder="Select travel dates" />
<DateRangePicker.TriggerIndicator />
</DateRangePicker.Trigger>
<DateRangePicker.Portal>
<DateRangePicker.Overlay />
<DateRangePicker.Content presentation="popover" width="trigger">
<DateRangePicker.Calendar>...</DateRangePicker.Calendar>
</DateRangePicker.Content>
</DateRangePicker.Portal>
</DateRangePicker.Select>
<Description>Required for booking confirmation.</Description>
</DateRangePicker>import type { CalendarDate } from '@internationalized/date';
import { getLocalTimeZone } from '@internationalized/date';
import { Description, Label } from 'heroui-native';
import { DateRangePicker, RangeCalendar } from 'heroui-native-pro';
import { View } from 'react-native';
function formatSpanishDateRange(
start: CalendarDate,
end: CalendarDate
): string {
const fmt = new Intl.DateTimeFormat('es-ES', {
day: 'numeric',
month: 'long',
year: 'numeric',
});
const a = fmt.format(start.toDate(getLocalTimeZone()));
const b = fmt.format(end.toDate(getLocalTimeZone()));
if (start.compare(end) === 0) return a;
return `${a} – ${b}`;
}
export default function DateRangePickerExample() {
return (
<View className="flex-1 justify-center px-5 gap-12">
<DateRangePicker formatDateRange={formatSpanishDateRange} locale="es-ES">
<Label>Custom range label (es-ES)</Label>
<DateRangePicker.Select>
<DateRangePicker.Trigger>
<DateRangePicker.Value className="text-sm" numberOfLines={1} />
<DateRangePicker.TriggerIndicator />
</DateRangePicker.Trigger>
<DateRangePicker.Portal>
<DateRangePicker.Overlay />
<DateRangePicker.Content presentation="popover" width="trigger">
<DateRangePicker.Calendar>
<RangeCalendar.Header>
<RangeCalendar.Heading />
<RangeCalendar.NavButton slot="previous" />
<RangeCalendar.NavButton slot="next" />
</RangeCalendar.Header>
<RangeCalendar.Grid>
<RangeCalendar.GridHeader>
{(day) => <RangeCalendar.HeaderCell day={day} />}
</RangeCalendar.GridHeader>
<RangeCalendar.GridBody>
{(date) => <RangeCalendar.Cell date={date} />}
</RangeCalendar.GridBody>
</RangeCalendar.Grid>
</DateRangePicker.Calendar>
</DateRangePicker.Content>
</DateRangePicker.Portal>
</DateRangePicker.Select>
<Description>Pick a start date, then an end date.</Description>
</DateRangePicker>
</View>
);
}| prop | type | default | description |
|---|---|---|---|
children | React.ReactNode | - | Children elements (Label, DateRangePicker.Select, Description, FieldError) |
value | DateRangePickerOption | - | Controlled selected option |
defaultValue | DateRangePickerOption | - | Default selected option for uncontrolled usage |
isDisabled | boolean | false | Whether the entire field is disabled |
isInvalid | boolean | false | Whether the field is in an invalid state |
isRequired | boolean | false | Whether the field is required |
isOpen | boolean | - | Controlled open state of the calendar overlay |
isDefaultOpen | boolean | - | Initial open state for uncontrolled usage |
dateDisplayFormat | DateRangePickerDateDisplayFormat | 'medium' | Preset date label format; ignored when formatDateRange is set |
locale | string | - | BCP 47 locale for label formatting and calendar grid |
formatDateRange | (start: CalendarDate, end: CalendarDate) => string | - | Custom formatter that overrides dateDisplayFormat and locale for labels |
rangeSeparator | string | '–' | Separator between start and end dates when using presets; same-day ranges collapse to a single date |
className | string | - | Additional CSS classes for the root container |
animation | AnimationRootDisableAll | - | Animation configuration for the date range picker subtree |
onValueChange | (value: DateRangePickerOption | undefined) => void | - | Handler called when the selected option changes |
onOpenChange | (open: boolean) => void | - | Handler called when the open state changes |
...ViewProps | ViewProps | - | All standard React Native View props are supported |
| property | type | description |
|---|---|---|
value | string | JSON string encoding start/end ISO dates (e.g. {"start":"2026-04-01","end":"2026-04-07"}) |
label | string | Display string shown in the trigger (e.g. "Apr 1, 2026 – Apr 7, 2026") |
Built-in date label presets (maps to Intl.DateTimeFormat dateStyle):
'short' — e.g. "4/1/26 – 4/7/26"'medium' — e.g. "Apr 1, 2026 – Apr 7, 2026" (default)'long' — e.g. "April 1, 2026 – April 7, 2026"'full' — e.g. "Wednesday, April 1, 2026 – Tuesday, April 7, 2026"Animation configuration for the DateRangePicker root. Can be:
"disable-all": Disable all animations including children (cascades down)undefined: Use default animations| prop | type | default | description |
|---|---|---|---|
children | React.ReactNode | - | Select content (Trigger, Portal) |
isDisabled | boolean | - | Overrides the root isDisabled when set |
presentation | 'popover' | 'dialog' | 'bottom-sheet' | 'popover' | Presentation mode for the select content |
className | string | - | Additional CSS classes |
...ViewProps | ViewProps | - | All standard React Native View props are supported |
| prop | type | default | description |
|---|---|---|---|
children | React.ReactNode | - | Trigger content (Value, TriggerIndicator) |
isDisabled | boolean | - | Whether the trigger is disabled |
isInvalid | boolean | - | When true, applies a danger border; inherits from root when omitted |
className | string | - | Additional CSS classes for the trigger |
...PressableProps | PressableProps | - | All standard React Native Pressable props are supported |
| prop | type | default | description |
|---|---|---|---|
placeholder | string | 'Choose a date range' | Text shown when no range is selected |
className | string | - | Additional CSS classes for the value text |
...TextProps | TextProps | - | All standard React Native Text props are supported |
| prop | type | default | description |
|---|---|---|---|
children | React.ReactNode | - | Custom indicator content; defaults to a calendar icon when omitted |
iconProps | SelectTriggerIndicatorIconProps | - | Overrides for the default icon |
isAnimatedStyleActive | boolean | false | Whether animated rotation styles are applied |
className | string | - | Additional CSS classes for the indicator container |
animation | SelectTriggerIndicatorAnimation | false | Rotation animation configuration; disabled by default for calendar icon |
...ViewProps | ViewProps | - | All standard React Native View props are supported |
| prop | type | default | description |
|---|---|---|---|
size | number | 16 | Icon size in logical pixels |
color | string | muted | Icon fill color |
| prop | type | default | description |
|---|---|---|---|
children | React.ReactNode | - | Portal content (Overlay, Content) |
hostName | string | - | Optional name of the host element for the portal |
disableFullWindowOverlay | boolean | false | Use a regular View instead of FullWindowOverlay on iOS |
unstable_accessibilityContainerViewIsModal | boolean | false | Controls whether VoiceOver treats the overlay as a modal container (iOS) |
className | string | - | Additional CSS classes for the portal container |
| prop | type | default | description |
|---|---|---|---|
closeOnPress | boolean | true | Whether to close the picker when the overlay is pressed |
isAnimatedStyleActive | boolean | true | Whether animated opacity styles are applied |
className | string | - | Additional CSS classes for the overlay backdrop |
animation | SelectOverlayAnimation | - | Opacity animation configuration |
...PressableProps | PressableProps | - | All standard React Native Pressable props are supported |
The content component is a union type based on the presentation prop.
| prop | type | default | description |
|---|---|---|---|
children | React.ReactNode | - | Content (DateRangePicker.Calendar) |
presentation | 'popover' | - | Popover presentation mode |
width | 'content-fit' | 'trigger' | 'full' | number | 'content-fit' | Content width sizing strategy |
className | string | - | Additional CSS classes for the content container |
animation | SelectContentPopoverAnimation | - | Keyframe animation configuration for entering/exiting |
...ViewProps | ViewProps | - | All standard React Native View props are supported |
| prop | type | default | description |
|---|---|---|---|
children | React.ReactNode | - | Content (DateRangePicker.Calendar) |
presentation | 'dialog' | - | Dialog presentation mode |
isSwipeable | boolean | true | Whether the dialog can be swiped to dismiss |
className | string | - | Additional CSS classes for the content container |
animation | SelectContentAnimation | - | Keyframe animation configuration for scale/opacity |
...ViewProps | ViewProps | - | All standard React Native View props are supported |
| prop | type | default | description |
|---|---|---|---|
children | React.ReactNode | - | Content (DateRangePicker.Calendar) |
presentation | 'bottom-sheet' | - | Bottom sheet presentation mode |
...BottomSheetProps | BottomSheetProps | - | All @gorhom/bottom-sheet props are supported |
| prop | type | default | description |
|---|---|---|---|
children | React.ReactNode | - | RangeCalendar compound parts (RangeCalendar.Header, RangeCalendar.Grid, etc.) |
value | RangeValue<DateValue> | null | - | Overrides the calendar value derived from the root selection |
locale | string | - | Overrides the root locale for the calendar grid |
accessibilityLabel | string | 'Pick a date range' | Screen reader label for the calendar container |
onChange | (value: RangeValue<DateValue> | null) => void | - | Side-effect handler called for range updates including null while restarting selection |
...RangeCalendarProps | RangeCalendarProps | - | All RangeCalendar root props are supported (minValue, maxValue, allowsNonContiguousRanges, etc.) |
Hook to access the DateRangePicker context. Must be used within a DateRangePicker component.
import { useDateRangePicker } from 'heroui-native-pro';
const { value, commitRange, isOpen, formatRangeLabel } = useDateRangePicker();| property | type | description |
|---|---|---|
value | DateRangePickerOption | undefined | Current select option (JSON range string + display label) |
onValueChange | (next: DateRangePickerOption | undefined) => void | Update the selected option |
isOpen | boolean | Whether the calendar overlay is open |
onOpenChange | (open: boolean) => void | Update the open state |
commitRange | (range: RangeValue<CalendarDate>) => void | Commit a range: updates the option, formats the label, closes the overlay |
formatRangeLabel | (start: CalendarDate, end: CalendarDate) => string | Format a range using root dateDisplayFormat / locale / formatDateRange |
isDisabledRoot | boolean | Whether the root is disabled |
locale | string | undefined | Root locale forwarded to DateRangePicker.Calendar |