A flexible and feature-rich React Native date range picker component supporting Week, Month, Year, and Custom date selection modes with smooth animations and comprehensive accessibility support.
- π 4 Selection Modes: Week, Month, Year, and Custom date range picking
- π― Interactive Selectors: Tap to select from lists and grids (not just arrow navigation)
- ποΈ Calendar View: Full calendar grid for custom date range selection
- π¨ Customizable Design: Extensive styling options and theme support
- β¨ Smooth Animations: Beautiful transitions powered by React Native Animated
- βΏ Accessibility First: Full screen reader and keyboard navigation support
- π Internationalization: Locale-aware formatting and date calculations
- π± TypeScript: 100% TypeScript with comprehensive type definitions
- π Production Ready: Optimized performance with memoization
Select from a scrollable list of weeks with date ranges.
Pick any month from a 12-month grid.
Choose from a scrollable grid of years.
Select custom date ranges with an interactive calendar.
npm install react-native-range-date-picker date-fns
# or
yarn add react-native-range-date-picker date-fnsnpm install react react-native date-fnsThe component also requires react-native-safe-area-context which is typically included in Expo projects.
import React, { useState } from 'react';
import { View } from 'react-native';
import { RangeDatePicker, RangeMode } from 'react-native-range-date-picker';
export default function App() {
const [startDate, setStartDate] = useState<Date | null>(null);
const [endDate, setEndDate] = useState<Date | null>(null);
const [mode, setMode] = useState<RangeMode>('month');
const handleRangeChange = (start: Date, end: Date, mode: RangeMode) => {
setStartDate(start);
setEndDate(end);
setMode(mode);
console.log(`Selected ${mode}:`, start, 'to', end);
};
return (
<View style={{ padding: 20 }}>
<RangeDatePicker
initialMode="month"
onRangeChange={handleRangeChange}
animationEnabled={true}
/>
{startDate && endDate && (
<View style={{ marginTop: 20 }}>
<Text>Start: {startDate.toLocaleDateString()}</Text>
<Text>End: {endDate.toLocaleDateString()}</Text>
<Text>Mode: {mode}</Text>
</View>
)}
</View>
);
}Perfect for hotel, flight, or event booking systems.
import React, { useState } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
import { RangeDatePicker, RangeMode } from 'react-native-range-date-picker';
export default function BookingScreen() {
const [checkIn, setCheckIn] = useState<Date | null>(null);
const [checkOut, setCheckOut] = useState<Date | null>(null);
const handleRangeChange = (start: Date, end: Date, mode: RangeMode) => {
setCheckIn(start);
setCheckOut(end);
};
const handleBooking = () => {
if (checkIn && checkOut) {
const nights = Math.ceil((checkOut.getTime() - checkIn.getTime()) / (1000 * 60 * 60 * 24));
console.log(`Booking ${nights} nights from ${checkIn.toDateString()}`);
// Process booking...
}
};
return (
<View style={styles.container}>
<Text style={styles.title}>Select Your Stay</Text>
<RangeDatePicker
initialMode="custom"
onRangeChange={handleRangeChange}
animationEnabled={true}
/>
{checkIn && checkOut && (
<View style={styles.summary}>
<Text>Check-in: {checkIn.toLocaleDateString()}</Text>
<Text>Check-out: {checkOut.toLocaleDateString()}</Text>
<Text>Total Nights: {Math.ceil((checkOut.getTime() - checkIn.getTime()) / (1000 * 60 * 60 * 24))}</Text>
<Button title="Book Now" onPress={handleBooking} />
</View>
)}
</View>
);
}
const styles = StyleSheet.create({
container: { padding: 20 },
title: { fontSize: 24, fontWeight: 'bold', marginBottom: 20 },
summary: { marginTop: 20, padding: 15, backgroundColor: '#f0f0f0', borderRadius: 8 },
});Select different time periods for data analysis.
import React, { useState, useEffect } from 'react';
import { View, Text, ActivityIndicator } from 'react-native';
import { RangeDatePicker, RangeMode } from 'react-native-range-date-picker';
export default function AnalyticsDashboard() {
const [startDate, setStartDate] = useState<Date>(new Date());
const [endDate, setEndDate] = useState<Date>(new Date());
const [loading, setLoading] = useState(false);
const [data, setData] = useState(null);
const fetchAnalytics = async (start: Date, end: Date) => {
setLoading(true);
try {
const response = await fetch(`/api/analytics?start=${start.toISOString()}&end=${end.toISOString()}`);
const result = await response.json();
setData(result);
} finally {
setLoading(false);
}
};
const handleRangeChange = (start: Date, end: Date, mode: RangeMode) => {
setStartDate(start);
setEndDate(end);
fetchAnalytics(start, end);
};
return (
<View style={{ padding: 20 }}>
<Text style={{ fontSize: 20, fontWeight: 'bold', marginBottom: 10 }}>
Analytics Dashboard
</Text>
{/* Quick period selection with Week/Month/Year modes */}
<RangeDatePicker
initialMode="month"
onRangeChange={handleRangeChange}
animationEnabled={true}
/>
<View style={{ marginTop: 20 }}>
{loading ? (
<ActivityIndicator size="large" />
) : data ? (
<Text>Revenue: ${data.revenue}</Text>
) : (
<Text>Select a date range to view analytics</Text>
)}
</View>
</View>
);
}Generate reports for different time periods.
import React, { useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import { RangeDatePicker, RangeMode } from 'react-native-range-date-picker';
type ReportType = 'sales' | 'inventory' | 'customers';
export default function ReportGenerator() {
const [reportType, setReportType] = useState<ReportType>('sales');
const [startDate, setStartDate] = useState<Date | null>(null);
const [endDate, setEndDate] = useState<Date | null>(null);
const [selectedMode, setSelectedMode] = useState<RangeMode>('month');
const handleRangeChange = (start: Date, end: Date, mode: RangeMode) => {
setStartDate(start);
setEndDate(end);
setSelectedMode(mode);
};
const generateReport = () => {
if (!startDate || !endDate) return;
console.log(`Generating ${reportType} report for ${selectedMode}`);
console.log(`Period: ${startDate.toDateString()} to ${endDate.toDateString()}`);
// Generate report logic...
};
return (
<View style={styles.container}>
<Text style={styles.title}>Generate Report</Text>
{/* Report Type Selection */}
<View style={styles.reportTypes}>
{['sales', 'inventory', 'customers'].map((type) => (
<TouchableOpacity
key={type}
style={[styles.typeButton, reportType === type && styles.typeButtonActive]}
onPress={() => setReportType(type as ReportType)}
>
<Text style={[styles.typeText, reportType === type && styles.typeTextActive]}>
{type.charAt(0).toUpperCase() + type.slice(1)}
</Text>
</TouchableOpacity>
))}
</View>
{/* Date Range Picker - Week for weekly reports, Month for monthly, Year for annual */}
<RangeDatePicker
initialMode="week"
onRangeChange={handleRangeChange}
animationEnabled={true}
style={styles.picker}
/>
{startDate && endDate && (
<TouchableOpacity style={styles.generateButton} onPress={generateReport}>
<Text style={styles.generateButtonText}>
Generate {selectedMode.charAt(0).toUpperCase() + selectedMode.slice(1)}ly Report
</Text>
</TouchableOpacity>
)}
</View>
);
}
const styles = StyleSheet.create({
container: { padding: 20 },
title: { fontSize: 24, fontWeight: 'bold', marginBottom: 20 },
reportTypes: { flexDirection: 'row', gap: 10, marginBottom: 20 },
typeButton: { flex: 1, padding: 12, backgroundColor: '#f0f0f0', borderRadius: 8, alignItems: 'center' },
typeButtonActive: { backgroundColor: '#007AFF' },
typeText: { fontWeight: '600', color: '#666' },
typeTextActive: { color: '#fff' },
picker: { marginBottom: 20 },
generateButton: { backgroundColor: '#007AFF', padding: 16, borderRadius: 8, alignItems: 'center' },
generateButtonText: { color: '#fff', fontSize: 16, fontWeight: '600' },
});Integrate with existing calendar or scheduling system.
import React, { useState } from 'react';
import { View, Text, FlatList, StyleSheet } from 'react-native';
import { RangeDatePicker, RangeMode } from 'react-native-range-date-picker';
interface Event {
id: string;
title: string;
date: Date;
}
export default function CalendarView() {
const [events, setEvents] = useState<Event[]>([]);
const [selectedStart, setSelectedStart] = useState<Date | null>(null);
const [selectedEnd, setSelectedEnd] = useState<Date | null>(null);
const handleRangeChange = (start: Date, end: Date, mode: RangeMode) => {
setSelectedStart(start);
setSelectedEnd(end);
// Filter events within selected range
const filtered = allEvents.filter(
event => event.date >= start && event.date <= end
);
setEvents(filtered);
};
return (
<View style={styles.container}>
<RangeDatePicker
initialMode="week"
onRangeChange={handleRangeChange}
animationEnabled={true}
/>
<View style={styles.eventsList}>
<Text style={styles.eventsTitle}>
Events ({events.length})
</Text>
<FlatList
data={events}
keyExtractor={item => item.id}
renderItem={({ item }) => (
<View style={styles.eventItem}>
<Text style={styles.eventTitle}>{item.title}</Text>
<Text style={styles.eventDate}>
{item.date.toLocaleDateString()}
</Text>
</View>
)}
/>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 20 },
eventsList: { marginTop: 20, flex: 1 },
eventsTitle: { fontSize: 18, fontWeight: 'bold', marginBottom: 10 },
eventItem: { padding: 12, backgroundColor: '#f9f9f9', borderRadius: 8, marginBottom: 8 },
eventTitle: { fontSize: 16, fontWeight: '600' },
eventDate: { fontSize: 14, color: '#666', marginTop: 4 },
});| Prop | Type | Default | Description |
|---|---|---|---|
initialMode |
'week' | 'month' | 'year' | 'custom' |
'month' |
Initial selection mode |
onRangeChange |
(start: Date, end: Date, mode: RangeMode) => void |
Required | Callback fired when range changes |
initialDate |
Date |
new Date() |
Initial date to display |
locale |
string |
undefined |
Locale for date formatting (e.g., 'en-US', 'fr-FR') |
theme |
'light' | 'dark' | 'auto' |
'light' |
Color theme |
animationEnabled |
boolean |
false |
Enable animated transitions |
style |
ViewStyle |
undefined |
Main container style |
segmentedControlStyle |
ViewStyle |
undefined |
Style for mode selector |
navigationButtonStyle |
ViewStyle |
undefined |
Style for navigation buttons |
rangeDisplayStyle |
TextStyle |
undefined |
Style for range display text |
type RangeMode = 'week' | 'month' | 'year' | 'custom';interface DateRange {
start: Date;
end: Date;
mode: RangeMode;
}<RangeDatePicker
onRangeChange={handleRangeChange}
style={{
backgroundColor: '#ffffff',
borderRadius: 12,
padding: 16,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 8,
elevation: 4,
}}
segmentedControlStyle={{
marginBottom: 16,
}}
navigationButtonStyle={{
backgroundColor: '#4A90E2',
width: 40,
height: 40,
}}
rangeDisplayStyle={{
fontSize: 18,
fontWeight: '700',
color: '#1F2937',
}}
animationEnabled={true}
/><RangeDatePicker
onRangeChange={handleRangeChange}
locale="fr-FR" // French locale
initialDate={new Date()}
/>
// Supported locales: any valid BCP 47 language tag
// Examples: 'en-US', 'es-ES', 'de-DE', 'ja-JP', etc.<View style={{ backgroundColor: '#1F2937', padding: 20 }}>
<RangeDatePicker
theme="dark"
onRangeChange={handleRangeChange}
style={{
backgroundColor: '#374151',
}}
rangeDisplayStyle={{
color: '#F9FAFB',
}}
/>
</View>For complete control, you can use individual components:
import {
WeekSelector,
MonthSelector,
YearSelector,
CalendarGrid,
SegmentedControl,
} from 'react-native-range-date-picker';
// Use WeekSelector standalone
<WeekSelector
currentDate={new Date()}
selectedWeek={selectedWeek}
onWeekSelect={(date) => setSelectedWeek(date)}
/>
// Use MonthSelector standalone
<MonthSelector
currentDate={new Date()}
selectedMonth={selectedMonth}
onMonthSelect={(date) => setSelectedMonth(date)}
/>
// Use CalendarGrid for custom implementations
<CalendarGrid
currentDate={new Date()}
selectedStart={startDate}
selectedEnd={endDate}
onDayPress={(date) => handleDayPress(date)}
/>import {
formatRange,
formatWeekRange,
formatMonthRange,
formatYearRange,
formatCustomRange,
getAccessibleDescription,
navigateNext,
navigatePrev,
getRangeForMode,
} from 'react-native-range-date-picker';
// Format different range types
const weekText = formatWeekRange(dateRange, { locale: 'en-US' });
const monthText = formatMonthRange(dateRange);
const yearText = formatYearRange(dateRange);
// Navigation
const nextMonth = navigateNext(currentDate, 'month');
const prevYear = navigatePrev(currentDate, 'year');
// Get range for any date and mode
const range = getRangeForMode(new Date(), 'week');
console.log(range.start, range.end);This component is built with accessibility as a priority:
- β Full screen reader support (VoiceOver, TalkBack)
- β Proper accessibility labels and hints
- β Semantic roles for all interactive elements
- β Keyboard navigation support
- β High contrast color ratios (WCAG AA compliant)
- β Focus management
// The component automatically provides accessibility features
<RangeDatePicker
onRangeChange={handleRangeChange}
// All buttons and interactive elements include:
// - accessibilityRole="button"
// - accessibilityLabel="descriptive label"
// - accessibilityState={{ selected: boolean }}
// - accessibilityHint="helpful hint"
/>Smooth animations are powered by React Native's Animated API:
<RangeDatePicker
animationEnabled={true}
onRangeChange={handleRangeChange}
/>Animation features:
- Segmented control indicator slides smoothly
- Date range display fades in/out on change
- Navigation buttons scale on press
- All animations use native driver for 60 FPS
Full TypeScript support with comprehensive type definitions:
import type {
RangeDatePickerProps,
DateRange,
RangeMode,
SegmentedControlProps,
NavigationButtonProps,
CalendarGridProps,
WeekSelectorProps,
MonthSelectorProps,
YearSelectorProps,
FormatOptions,
DateCalculationOptions,
} from 'react-native-range-date-picker';
// All types are exported and fully documented
const handleChange = (start: Date, end: Date, mode: RangeMode): void => {
const range: DateRange = { start, end, mode };
console.log(range);
};The component is fully testable. Here's an example using React Native Testing Library:
import { render, fireEvent } from '@testing-library/react-native';
import { RangeDatePicker } from 'react-native-range-date-picker';
test('calls onRangeChange when date is selected', () => {
const mockHandler = jest.fn();
const { getByText } = render(
<RangeDatePicker
initialMode="month"
onRangeChange={mockHandler}
/>
);
// Switch to month mode
fireEvent.press(getByText('Month'));
// Select a month
fireEvent.press(getByText('January'));
expect(mockHandler).toHaveBeenCalled();
});Contributions are welcome! Please feel free to submit a Pull Request.
MIT Β© React Native RangeDatePicker Team
- Built with date-fns for robust date calculations
- Inspired by native iOS and Android date pickers
- Designed for React Native and Expo projects
- π« Report Issues
- π¬ Discussions
- π Documentation
Made with β€οΈ for the React Native community




